PURPOSE

Owns the sealed boss arena lifecycle: spawning a large circular (or rect) room centered on the player, shrinking it to its final size over a fixed duration, constraining the ship inside, culling entities that fall outside the wall, clearing terrain inside the footprint, locking the camera to the room, and rendering the wall against a pure-black void mask outside.

OWNS

  • BossRoom lifecycle state machine (closing locked).
  • Ship-vs-wall positional clamp and velocity zero-out (with Rapier mirror).
  • Per-frame entity cull (enemies, pickups, enemy bullets, player bullets) against the current room bounds.
  • One-shot terrain clear inside the room footprint at spawn, including the Rapier static collider and the floater list, with spatial index rebuild after splices.
  • Camera target clamp so the viewport never strays past the wall + margin.
  • Wall rendering: black evenodd mask outside the rect arena, or a thick stroked ring for circles, plus the orange inner glow line.

READS FROM

  • core singletons: game.time, ship (position, velocity, radius), world.enemies, world.pickups, world.enemyBullets, world.playerBullets, world.terrain, world.floaters, world.chunks, camera.zoom, camera.targetX/Y, W, H.
  • CFG.BOSS_ROOM_INITIAL_SCALE, CFG.BOSS_ZOOM, CFG.BOSS_ROOM_SIZE_MULT, CFG.BOSS_ROOM_SHRINK_DURATION, CFG.BOSS_ROOM_MARGIN, CFG.BOSS_ROOM_WALL_THICKNESS.
  • RapierWorld.isReady() to gate physics-side mutations.
  • Enemy flags isBoss, sharesHealthWithBoss, _isBossAnchor to exempt boss-system entities from the cull.

PUSHES TO

  • ship.x/y/vx/vy (clamp and velocity cancel along the wall normal).
  • RapierShip.teleportKeepVelocity + RapierShip.setLinvel to mirror clamps into the physics world.
  • world.enemies[i].alive = false for non-boss enemies outside the room.
  • world.pickups, world.enemyBullets, world.playerBullets: swap-and-pop removal of out-of-bounds entries.
  • world.terrain, world.floaters: splice removal of pieces inside the footprint, with matching RapierTerrain.removePiece / RapierTerrain.removeFloater calls.
  • buildTerrainChunks(world) to rebuild the spatial index after terrain splices.
  • camera.targetX/Y clamp.
  • Canvas drawing commands on the supplied CanvasRenderingContext2D.

DOES NOT

  • Spawn the boss itself or drive boss AI (the encounter module owns that).
  • Read or apply biome / backdrop drawing; the wall renderer just masks them with black.
  • Manage XP orbs — those live in SoA storage and self-cull in xp-orbs.ts.
  • Persist any state outside the returned BossRoom object.
  • Run physics steps; it only teleports the ship body and removes terrain colliders.

Signals

  • room.state transition closing locked when the shrink ease reaches t = 1.
  • enemy.alive = false writes are the only signal emitted to gameplay; downstream death/loot is handled by the main update loop.

Entry points

  • spawnBossRoom(def) — constructs the BossRoom at ship position with the configured initial scale, computes targetR from viewport, boss zoom, and BOSS_ROOM_SIZE_MULT, clears terrain inside the final footprint, returns the room.
  • updateBossRoom(room, dt) — per-frame: tracks player while closing, eases the shrink with easeInOutQuad, transitions to locked, constrains the ship, runs the entity cull (every frame while closing, every third frame once locked).
  • clampCameraToRoom(room) — call after Camera.update() to keep camera.targetX/Y inside the room + margin.
  • renderBossRoomWalls(ctx, room) — draws the void mask and wall outline; circle path strokes a thick ring, rect path uses an evenodd-fill black mask plus inner stroke.

Pattern notes

  • Circle arena is the primary shape; rect paths exist as a fallback for room.shape === 'rect' and the terrain clear uses a square footprint of side targetW so a rect spawn still works.
  • Initial radius is max(W, H) * 0.4 / bossZoom * BOSS_ROOM_SIZE_MULT, sized so the closing ring is fully visible on portrait phones.
  • Shrink curve is easeInOutQuad over BOSS_ROOM_SHRINK_DURATION. The room starts at BOSS_ROOM_INITIAL_SCALE times target.
  • During closing, cx/cy track the ship so the arena collapses around it; on locked, they freeze.
  • Entity cull is intentionally branch-light: bounds are lifted to locals, outside() switches on shape once, swap-and-pop avoids array shifts. Cull is throttled to every third frame once the wall is static.
  • Enemies marked isBoss, sharesHealthWithBoss, or _isBossAnchor are exempt from the cull because boss anchors are placed at fixed arena-relative positions and the shrinking ring would otherwise destroy shielded / shielded_respawn / periodic_invuln affixes.
  • Ship clamp uses ship.outerRadius || ship.radius || 14 for the wall offset. Velocity is zeroed along the wall normal (rect: per-axis; circle: dot-product projection). Rapier teleport + linvel set keeps the physics body in sync.
  • Terrain clear uses an oversized circular bound (targetR * 2) to ensure pieces straddling the edge are removed. After splicing, the spatial index in world.chunks is rebuilt or nearbyTerrain() and pushOffTerrain will dereference stale indices.
  • Camera clamp first checks whether the arena is wider than the viewport; if not, it pins to the room center on that axis instead of clamping.
  • Rect wall rendering composites a fullscreen black rect with the arena rect cut out using evenodd fill — purely visual, the Rapier wall colliders still sit at SEALED_ARENA_WALL_THICKNESS extents.
  • Module-level _cullTick counter is the only mutable state outside the BossRoom object.