engine/world

PURPOSE — The level-baking + per-frame world layer: deterministically lays out a hub-and-spoke playable area at run start (hubs, spokes, terrain clusters, zone grid, triggers, warp puddles), seeds and ticks all in-world content the ship interacts with outside combat (events, crates, debris, props, XP orbs, shooting stars, warp puddles, illumination lanterns, artifacts), grants player progression (XP / levels / reward choices / weapon merges), and streams terrain in and out as the player explores an infinite plane.

OWNS

  • The precomputed LevelData produced at mission load: full GenerateResult (hubs, spokes, chunk size, per-chunk hub index, warp puddles, warp-puddle groups), the structural zone grid, the live illumination map, the event-completion map, the list of LevelTrigger records, and the bake-time measurement.
  • Hub & spoke layout for every pattern (grid / zigzag / rings / chaotic): per-chunk RNG seeding, per-pattern hub generation, planar or grid-restricted spoke graph, and the scripted-mode fallback that reads hubs and spokes verbatim from LevelConfig.
  • The four-phase terrain fill on the world state: cluster grid pool (center / medium ring / small ring weighted by the active terrain mix), legacy hub/spoke carving, deduplication pass, spatial chunk index for collision broad-phase, the dynamic super-chunk expansion + GC loop that keeps terrain populated around the moving ship, the city-grid variant (block interiors and road spokes), and the legacy ring-based hub placer.
  • Biome registry: per-biome terrain pool, enemy pool, event pool, level radius, density, intensity, terrain mix, and hub topology knobs.
  • The runtime EventManager state machine — phase, charge progress, exit timer, mandala phase, completion flash, hub-tagged permanence, and the default weighted event-type pool.
  • The EventSpawner chunk tracker for the seed-and-scatter pass + per-frame mark-as-seen / spawn-torus eligibility logic, plus the seeded-hub bookkeeping that prevents re-seeding completed events on revisit.
  • The level TriggerSystem: trigger list, fired-id set, currently-inside set, callback table.
  • The IlluminationSystem: per-hub lantern records (fill, in-range flag, activated flag, activation timestamp), the illumination count, and the activation-callback list. Permanent once lit.
  • The XP-orb columnar store (typed arrays for position, velocity, amount, radius, age, pull accumulator, magnet timer, flags), the active count, the throttled merge / offscreen-merge accumulators, and the per-orb XP cap derived from the player’s current level cost.
  • The XP-leveling system: hand-tuned threshold table for levels 1–20 with an on-demand compounding extension beyond level 20, the rarity roll table with luck biasing, the weighted level-up reward pool (NEW modifiers, modifier upgrades, weapon upgrades, weapon merges) with slot-based selection, the banished-key check, the weapon-cache reward pool, the shooting-star reward pool, and the cumulative-modifier display math.
  • The weapon merge resolver: eligibility scan, unordered pair enumeration, legendary-id lookup, parent removal, legendary insertion at the leftmost parent’s slot.
  • Crate pool (sticker-emoji sprite slots with bob phase / amp, density-driven target count, off-screen spawn torus with velocity-cone bias, on-screen never-cull rule, per-crate XP-orb drop + three-layer break VFX).
  • Debris field (cluster registry with fog records, per-piece polygon and spike geometry as typed arrays, per-piece bob phase + rotation seed, contact-damage rule).
  • Prop pool (typed slots with per-type hp, bob state, last-hit timestamp; ship-contact + bullet-hit collision; per-prop cross-system synergy procs; force-spawn primitive for chain cascades).
  • Warp puddle records and the union-find-merged warp groups, plus the per-frame enter/exit detection with ramp-in tween and instant exit, and the idempotent thrust/heat overrides that fold the warp tween into the ship’s base stats.
  • Shooting-star records (position, base velocity, speed-modulation state, approach timer, trail buffer, collection animation state), the spawn timer + log-normal interval roller for the second-half-of-level spawn window, and the ring-pop VFX state.
  • The wave-incoming telegraph kit (lazy VfxLayerKit singleton, intensity curve from wave size).
  • The active artifact registry: instances list, id→index map, mutable per-artifact scratch state, HUD flash timers, registered signal listeners, and the per-tier value lookup.

READS FROM

  • data/level-config for LevelConfig, hub/spoke types, scripted hub + spoke definitions, level triggers, default config, zone-type enum, and per-pattern knob ranges.
  • data/terrain-shapes for shape definitions (base radius, scale range, type tag) used to size and seed terrain pieces.
  • data/planet-config for the per-planet destructible sliders that scale crate / debris / prop active counts.
  • data/weapons for weapon specs, weapon map, scaling curves, stepped-stat lookups, fractional-level resolution, rarity resolver, legendary-pair lookup, and weapon-tag enum.
  • data/modifiers for modifier definitions, modifier map, per-effect value resolution, and the modifier-effect type.
  • data/artifacts for artifact definitions, artifact map, tier color table, max tier constant, per-tier value lookup, flat-bonus extraction, and tier label.
  • data/props for prop type definitions and the prop catalog accessors.
  • engine/core for shared world / ship / game / camera / canvas-dim references, signals bus, set-pool helper, mulberry32 helper, clock, fixed-timestep configuration constants, modifier system, and the swapRemove utility.
  • engine/core/spatial-grid (enemyGrid) for artifact AoE / signal-driven enemy queries.
  • engine/player/states for the Star Power exclusive-state hook.
  • engine/combat for damageEnemy (artifact + magnetar-pull chains) and damagePlayer (debris / volatile-crystal contact damage).
  • engine/effects (EffectEngine, flame-zone + delayed-AoE handlers) for artifact effect registration and per-frame tick + teardown of artifact-spawned zones.
  • engine/vfx for particle spawning, XP-accumulator, juice fires, sonar-ring shockwaves, explosion FX, damage-number popups, and the boss-VFX layer kit (consumed by the wave telegraph).
  • engine/audio micro-SFX for the wave-incoming sting and prop-break per-type cues.
  • engine/rendering for the screen-space camera transform, the WebGL sprite-batch and atlas-region lookups, and the artifact-banner push helper.
  • engine/telemetry collector for artifact, director-phase, and prop-break records.
  • engine/affixes palette for the burning-aura color used by the volatile-crystal afterburn proc.
  • stores/artifactUnlocksStore for the persistent legendary-unlock check.

PUSHES TO

  • engine/combat via damageEnemy (artifact AoE chains, magnetar-pull does not damage but consumers do) and damagePlayer (debris contact, volatile-crystal proximity tick).
  • engine/effects via EffectEngine.register / unregister (artifact effect lifecycle) and the flame-zone / delayed-AoE tick + clear hooks.
  • engine/vfx particle / sonar-ring / explosion / damage-number / juice channels and the artifact-banner queue.
  • engine/rendering camera (no direct shake — the wave telegraph uses the boss-VFX layer kit, which the rendering compositor reads).
  • engine/core/signals (Sig.fire for crate_break, prop_break, level_up; Sig.on / Sig.off for enemy_kill, shield_hit, crate_break, tbone_hit, event_complete).
  • engine/core/modifiers (Modifiers.add, Modifiers.recalc) — every level-up modifier pick and the shooting-star “all ship mods” reward goes through this so picks are not silently wiped by later recalc passes.
  • engine/audio via juice fires for crate / prop / event / collection cues.
  • engine/telemetry for artifact events, director phases (prop-break per-type + per-synergy), and the wave-telegraph cadence.
  • Ship state (ship.warpGroupId, ship.warpT, ship.invulnerable, ship.invulnTimer, ship.vx / ship.vy, the shooting-star trail-buff timer, the base-stat snapshot at ship._base) when warp-puddle, drone-invuln, comet-speed-boost, and trail-buff procs land.
  • Game state (game.xp, game.level, game.xpToNext, game.rewardQueue, game.upgradeCounts, game.modifierTotals, game.artifacts, game.weaponsAcquired, game.rerolls / banishes / refuels, the tracking.eventsCompleted / weaponsFound / upgradesChosen / artifactsCollected / newlyUnlockedArtifactIds rollups).

DOES NOT

  • Run combat collision or damage math against in-flight projectiles or enemies. Prop and debris collision write damagePlayer and signal prop_break / crate_break; everything else is handed off to engine/combat.
  • Render anything. Terrain, hubs, spokes, events, crates, debris, props, XP orbs, shooting stars, warp puddles, lanterns, particles, banners, and trigger zones are all drawn by engine/rendering (with the exceptions of crate / debris / prop / XP-orb draw helpers that the rendering layer calls into — the modules write geometry the renderer reads, they don’t own the compositor).
  • Pick the player’s aim angle or fire weapons. Weapons live in engine/weapons; this module only grants and merges them.
  • Decide enemy spawning, AI, or wave composition. The wave telegraph fires a UI cue 1.5 s ahead of a big wave but the wave itself is owned by engine/enemies/spawner. The trigger system can dispatch a spawn_wave callback but the spawner consumes the payload.
  • Author level layouts or trigger payloads. Layouts come from data/level-config (preset planets and playground overrides); triggers are either auto-built from hubs at bake time or carried verbatim from config.
  • Hand out artifacts on its own. Artifact grants are routed through applyReward (level-up + shooting-star) and through bridge collection handlers; this module owns the per-run instances and the tier-up resolver.
  • Reposition events on the second pass. The EventSpawner seeded-hub pass and the scatter pass clear terrain around event placements (so the player can always reach the ring) but the event itself is anchored at placement time and never moves.
  • Snapshot or restore ship._base. The warp-puddle override module reads it but the bridge is responsible for snapshotting it at mission start.

Signals fired / Signals watched

  • Fires crate_break on crate pop and prop_break on prop pop so artifacts (Crate Buster and friends) can chain.
  • Fires level_up on every player level-up so VFX and effect-engine listeners can react.
  • Subscribes to enemy_kill, shield_hit, crate_break, tbone_hit, and event_complete for the artifact runtime’s signal-triggered effects. Listener refs are cached at init and dropped at teardown.

Entry points

  • WorldGenerator.generate — top-level world layout call; routes to the city-grid filler, the zone-aware path (using a precomputed LevelData), or the legacy ring-based hub placer + four-phase terrain fill.
  • WorldGenerator._generateHubAndSpoke / _fillTerrain / _fillCityGrid / _validateGeneration — the legacy and zone-aware generation phases.
  • WorldGenerator.placeEvents — the legacy grid-based event placer that seeds events in clear areas, carves terrain to keep them reachable, assigns sub-events to non-start hubs, and deletes near-duplicate events on overlap.
  • WorldGenerator.expandTerrain — per-frame super-chunk fill around the moving ship plus the distance-based terrain GC + chunk-index rebuild.
  • WorldGenerator.getEnemySpawnPos — pick a spawn position on a random spoke for enemy placement.
  • WorldGenerator.reset — clear all world state for a new run.
  • buildTerrainChunks — rebuild the spatial chunk index after a terrain mutation.
  • bakeLevel — the single-call BAKE phase: builds GenerateResult (scripted or precomputed), the structural zone grid, the illumination map, the auto + config trigger list, and the level data record.
  • computeActiveChunks — per-frame visibility ring calculator returning active / spawn / sim chunk-key sets.
  • getVisibleHubs / getVisibleSpokes / findNearestHub — query helpers over the precomputed level data.
  • precomputeWorld / generateForViewport / generateHubsAndSpokes — the framework-agnostic hub-and-spoke pipeline used by bakeLevel for the non-scripted path.
  • calcChunkSize / chunkRng / mulberry32 — per-pattern chunk-size derivation and seeded RNG factory.
  • classifyZone / buildZoneGrid / lookupZone — point-to-zone classification with the precomputed grid as the fast path.
  • EventSpawner.init / seedHubs / tick — event pool initialization, the once-per-level hub-centric event seeding (with runtime filters for artifact / regen eligibility), and the per-frame chunk-tracking + spawn-torus eligibility scan.
  • createEvent / EventManager.update / EventManager._updateCharge / EventManager.reset — event factory, the per-frame charge state machine (proximity, charge acceleration, exit drain, completion flash, hub-tagged permanence), and the inter-run reset.
  • buildDefaultEventPool — produce the weighted event-type pool by duplication.
  • createTriggerSystem / tickTriggers — trigger-system factory and the per-frame enter-edge dispatcher.
  • findMergeCandidates / mergeWeapons — weapon-merge eligibility scan and the parent-consumption + legendary-insertion resolver.
  • fireWaveTelegraph / intensityFor / _resetForTests — the wave-incoming cue trigger, intensity curve, and test-only singleton reset.
  • crates.init / clear / update / draw — crate-pool lifecycle, per-frame collision + density tick, and the WebGL-batch-or-Canvas-2D render pass.
  • debris.clear / update / draw — debris-field lifecycle, per-frame contact-damage pass + spawn cadence, and the composite-silhouette draw pass.
  • PickupSystem.shouldCollect / collectXP / spawnPickup / update / triggerGlobalMagnet / reset — the thin XP-orb adapter surface used by tests, magnet events, boss death, and the per-frame bridge tick.
  • xpOrbs.spawn / count / clear / triggerGlobalMagnet / update / draw / drawTutorialGlow / snapshot — the columnar XP-orb storage’s public API.
  • LevelingSystem.checkLevelUp / levelUp / update / getProgress / generateRewardChoices / generateWeaponChoices / reset — XP threshold checks, reward-pool generation for level-ups and weapon caches, and the inter-run reset.
  • applyReward — the unified dispatcher that applies a RewardChoice (new weapon, weapon upgrade with sympathetic-resonance cascade, weapon merge, modifier, legacy upgrade, artifact grant, shooting-star category).
  • applyEventRewardUpgrades — apply a caller-supplied list of modifier IDs (event-reward artifact path).
  • generateShootingStarChoices — produce up to two shooting-star reward cards filtered by current run state and banished keys.
  • resolveWeaponChestUpgrade — fractional weapon-level increment from a chest rarity.
  • xpForLevel / addXP / generateRewardChoices (module-level wrapper) / describeNewWeapon / banishKeyForChoice — test + caller compatibility helpers and the banish-key resolver.
  • WARP_PUDDLES_ENABLED / spawnPuddlesForHub / groupPuddles / puddleContainsPoint / groupContainsPoint / findContainingGroup — puddle authoring + grouping + containment.
  • tickWarpPuddles / getActiveWarpGroup / applyWarpPuddleOverrides — per-frame group enter/exit detection with ramp-in tween and instant exit, and the idempotent base-stat fold.
  • spawnShootingStar / updateShootingStars / updateRingPop / ShootingStarSpawner.init / ShootingStarSpawner.tick / ringPop — shooting-star factory, per-frame state machine (approach modulation, exit acceleration, trail sampling, trail-buff broadcast, collection fly-to-center, distance-based despawn), ring-pop timer, and the second-half-of-level spawn window.
  • createIlluminationSystem / tickIllumination / isHubLit / isSpokeLit / getLanternFill / isPlayerInLanternRange / getIlluminatedHubIds / getConnectedSpokes / setIllumination — lantern lifecycle, the per-frame fill / drain / activation loop, and read-only state queries.
  • ParticleHelpers.spawnImpact / spawnDeath / spawnHeal / spawnShieldHit / spawnStun — convenience particle-burst spawners.
  • getPropPool / _resetPropPoolForTests / PropPool.clear / count / forEachEdgeArrowProp / forceSpawnAt / update / draw / triggerSupplyPodCascade / triggerMagnetarPull — prop-pool singleton accessor, lifecycle, edge-arrow iteration, the public force-spawn primitive for chain cascades, the per-frame collision + density tick, the Canvas-2D draw, and the public synergy wrappers used by the bridge.
  • initArtifacts / tickArtifacts / teardownArtifacts / grantArtifact / hasArtifact / countOwnedArtifacts / getArtifactTier / setArtifactFlash and the rest of the artifact runtime — per-run artifact lifecycle, signal-listener wiring, and the tier-up resolver.

Pattern notes

  • Two distinct generation paths coexist: the legacy WorldGenerator.generate ring-based path (still used by the city biome and any caller that doesn’t supply LevelData) and the zone-aware path driven by bakeLevel + precomputeWorld + buildZoneGrid. The zone-aware path is the canonical mission-load flow; the legacy path remains for biome variants and tests.
  • Generation is split into BAKE (one-shot, deterministic, full precompute) and SIMULATE / CULL (per-frame, fast). Everything expensive — hub layout, spoke graph, zone grid, illumination map, trigger list, warp-puddle grouping — happens once at mission load and is treated as immutable afterwards.
  • Terrain is doubly indexed: every piece carries a chunkKey written at creation, and a separate spatial chunk map is rebuilt by buildTerrainChunks after every mutation (event placement, dynamic expansion, GC, sub-event terrain clearing). Collision queries broad-phase through the chunk map; the per-piece chunkKey is only used by readers that already have the piece.
  • Dynamic terrain expansion uses a coarse 800 px super-chunk grid keyed by sc_<scx>_<scy> in the generator’s own _generatedChunks set, separate from the zone-aware LevelData.generation.chunkSize. The two grids do not coordinate — the dynamic grid keeps un-baked terrain populated outside the precomputed level area, and the GC pass un-marks super-chunks far from the ship so they can regenerate on return.
  • Hub/spoke patterns self-route through genHubsForPattern and buildSpokesForPattern; adding a new pattern means adding two cases. Spoke graphs come in two flavors: orthogonal (grid only) and maximal planar (everything else), where the planar builder greedily adds non-crossing edges shortest-first.
  • Events are placed on hubs first (EventSpawner.seedHubs), then sprinkled in a level-wide jittered grid (EventSpawner.seedHubs scatter pass), then potentially spawned in off-screen spawn-torus chunks by EventSpawner.tick. Hub events are tagged with hubId and are permanent — they never decay, never GC, and revert to idle on fail. Non-hub events GC on completion flash.
  • Event placement always clears overlapping terrain. The seeding pass calls clearTerrainAround per event and rebuilds the chunk index once at the end if anything was removed. The legacy placeEvents pass and the sub-event pass on hubs follow the same pattern.
  • The event-type pool is encoded as a duplication list (e.g. 27 × levelup + 10 × magnet + …) so a uniform random pick reproduces the design-weighted distribution without a separate weight table.
  • Trigger payloads are heterogeneous (spawn_wave, dialogue, checkpoint, event, spawn_zone) and dispatched through a callback record so the trigger system itself stays type-flat. Auto-generated triggers from _buildAutoTriggers are filtered out when a config-supplied trigger shares the same ID — config always wins on collision.
  • The XP-orb pool is a strict structure-of-arrays with swap-remove; all per-frame logic lives in the module so call sites never see orb objects. Two throttled merge passes (nearby + offscreen) plus an on-screen soft cap keep the live count bounded; the per-orb XP cap is recomputed every merge pass against the player’s current level cost.
  • Level-up reward selection uses a slot-based pull: each card slot independently rolls weapon-pool vs modifier-pool with a fixed weapon-chance, with merge cards reserved for the first slots when eligible. Banished keys filter both the modifier and weapon pools; a separate padding pass fills any remaining slots from unused entries without duplicates.
  • Level-up modifier picks always route through _applyModifierPick, which writes through the Modifiers system so later recalc passes don’t silently wipe direct stat mutations. The shooting-star “all ship mods” and event-reward paths use the same helper.
  • Crates, debris, and props all share the same shape: fixed-size pool of typed slots, off-screen-only spawn with velocity-cone bias toward the player’s heading, never-cull-while-on-screen rule, density-driven target count read from PLANETS[planetId].destructibles, and a 200–250 ms spawn-cadence accumulator separate from the per-frame collision pass.
  • Props extend the crate pattern with per-type bullet collision and a catalog-driven bespoke-synergy system: a break can chain into the same pool’s forceSpawnAt for typed cascades (Mineral Vein, Supply Pod) or reach into sibling subsystems (XP orbs, ship state, enemy velocity, the burning-aura palette) for one-shot procs. Each synergy is a named relationship in the breakProp dispatch, not a generic data-table flag.
  • Warp puddles are individually circular but the collision + rendering unit is a WarpPuddleGroup formed at bake time by union-find over overlapping circles. The ship’s warp state tracks a warpGroupId, not a puddle id, and the per-frame override (applyWarpPuddleOverrides) always writes absolute values from ship._base so it stays idempotent — at warpT = 0 the writes restore the base stats exactly. The feature is currently flag-gated off via WARP_PUDDLES_ENABLED.
  • Shooting stars are reactive to the player: approach decelerates them toward a floor, recede or far distance lets them drift through an exit phase, and exit acceleration ramps them back to base speed. The trail buffer doubles as the active +30 % speed-buff zone for the ship; the renderer draws what the gameplay reads. Lifetime-based despawn was removed in favor of pure distance-based culling so a star never vanishes in front of the player.
  • The illumination system is permanent-on: hubs only transition dark → lit, never back. The structural zone grid built at bake stores zone type with all hubs dark; the live illumination map is consulted at lookup time, so a single Map.set flip changes the gameplay zone of every cell that depends on it without rebuilding the grid.
  • Artifacts use both an exclusive flat-state record (_st keyed by artifact-specific strings) and the shared EffectEngine registry. Signal handlers are registered once at init with a single listener per channel that dispatches to whichever active artifacts care, and tier-ups un-apply / re-apply stat effects so tier deltas don’t compound.
  • The wave-incoming telegraph is a thin shim over the boss-VFX layer kit: a lazy singleton, an intensity curve from wave size, two layer calls (screen tint + edge vignette), a micro-SFX trigger, and a telemetry record. Reusing the boss kit keeps the compositor agnostic and avoids a parallel pipeline.