Boss Arena Shape

Boss arenas are either 'circle' or 'rect' shaped. The shape is fixed per boss room and selected by the per-boss room definition, which also supplies the target dimensions the helpers read from.

For circle arenas the room exposes targetR (target radius) and currentR (live radius while the room is closing). For rect arenas it exposes targetW / targetH and currentW / currentH. The helpers in engine/boss/arena.ts route every spawn query through these so affixes, abilities, AI, and spawn profiles never reference screen size or world coordinates directly.

Per-helper shape handling

  • contains(x, y) uses the current boundary so leash + cull match the live footprint. Circle: squared-distance vs currentR. Rect: AABB vs currentW / currentH halves.
  • clampPoint(x, y, margin) snaps to the inside of the current boundary minus margin. Circle clamps to the disc, rect clamps to the inset AABB. Default margin is 20 world units.
  • randomPoint(margin) returns a uniform-distributed point inside the target footprint. Circle uses uniform-disc sampling (r = R * sqrt(u), uniform angle). Rect uses uniform AABB sampling ((u-1..u) * halfW, (u-1..u) * halfH).
  • cardinalPoints(fracOfRadius) returns E / S / W / N points on a ring of radius targetRadius() * fracOfRadius. Same output shape for circle and rect — rect arenas use min(targetW, targetH) / 2 as their effective radius.
  • ringPoints(count, fracOfRadius) is always circular regardless of arena shape: count evenly-spaced points around a ring of radius targetRadius() * fracOfRadius. Rect arenas still get a circular ring (not a perimeter walk).
  • edgePoints(count) walks the target perimeter. Circle: evenly-spaced points on the boundary circle. Rect: parametric 4-segment walk along top → right → bottom → left, evenly spaced by perimeter distance 2 * (targetW + targetH).
  • oppositePlayer(playerX, playerY, dist) mirrors the player across center, projects to dist from center, then clamps inside via clampPoint. Shape-agnostic at the surface, but the clamp picks up the shape rule.
  • radius() returns targetRadius() — for rect this is min(targetW, targetH) / 2.
  • diagonal() returns targetRadius() * 2 for circle, hypot(targetW, targetH) for rect.
  • bounds() returns the AABB of the target footprint. Circle: cx ± targetR, cy ± targetR. Rect: cx ± targetW/2, cy ± targetH/2.

Target vs current

Spawn placement (randomPoint, cardinalPoints, ringPoints, edgePoints, bounds, radius, diagonal) reads the target footprint so positions don’t drift while the room is closing in. Boundary checks (contains, clampPoint) read the current footprint so the leash and cull match what the player actually sees this frame.

Sealed-level arenas

Levels 3 (mini boss) and 5 (boss) of a normal run are sealed square arenas built from SEALED_ARENA_HALF_SIZE = 1400 (a 2800 × 2800 square footprint). These are rect arenas at the data layer; the wall thickness (SEALED_ARENA_WALL_THICKNESS = 2000) plus per-frame clamp + Rapier static cuboid colliders guarantees no entity escapes.

  • gameplay/concepts/sealed-arena-geometry — wall + clamp + collider construction for sealed levels.
  • gameplay/concepts/boss-arena-positions — semantic position keys consumers pass to these helpers.