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
LevelDataproduced at mission load: fullGenerateResult(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 ofLevelTriggerrecords, 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
EventManagerstate machine — phase, charge progress, exit timer, mandala phase, completion flash, hub-tagged permanence, and the default weighted event-type pool. - The
EventSpawnerchunk 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-configforLevelConfig, hub/spoke types, scripted hub + spoke definitions, level triggers, default config, zone-type enum, and per-pattern knob ranges.data/terrain-shapesfor shape definitions (base radius, scale range, type tag) used to size and seed terrain pieces.data/planet-configfor the per-planet destructible sliders that scale crate / debris / prop active counts.data/weaponsfor weapon specs, weapon map, scaling curves, stepped-stat lookups, fractional-level resolution, rarity resolver, legendary-pair lookup, and weapon-tag enum.data/modifiersfor modifier definitions, modifier map, per-effect value resolution, and the modifier-effect type.data/artifactsfor artifact definitions, artifact map, tier color table, max tier constant, per-tier value lookup, flat-bonus extraction, and tier label.data/propsfor prop type definitions and the prop catalog accessors.engine/corefor sharedworld/ship/game/camera/ canvas-dim references, signals bus, set-pool helper, mulberry32 helper, clock, fixed-timestep configuration constants, modifier system, and theswapRemoveutility.engine/core/spatial-grid(enemyGrid) for artifact AoE / signal-driven enemy queries.engine/player/statesfor the Star Power exclusive-state hook.engine/combatfordamageEnemy(artifact + magnetar-pull chains) anddamagePlayer(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/vfxfor 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/audiomicro-SFX for the wave-incoming sting and prop-break per-type cues.engine/renderingfor the screen-space camera transform, the WebGL sprite-batch and atlas-region lookups, and the artifact-banner push helper.engine/telemetrycollector for artifact, director-phase, and prop-break records.engine/affixespalette for the burning-aura color used by the volatile-crystal afterburn proc.stores/artifactUnlocksStorefor the persistent legendary-unlock check.
PUSHES TO
engine/combatviadamageEnemy(artifact AoE chains, magnetar-pull does not damage but consumers do) anddamagePlayer(debris contact, volatile-crystal proximity tick).engine/effectsviaEffectEngine.register/unregister(artifact effect lifecycle) and the flame-zone / delayed-AoE tick + clear hooks.engine/vfxparticle / sonar-ring / explosion / damage-number / juice channels and the artifact-banner queue.engine/renderingcamera (no direct shake — the wave telegraph uses the boss-VFX layer kit, which the rendering compositor reads).engine/core/signals(Sig.fireforcrate_break,prop_break,level_up;Sig.on/Sig.offforenemy_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/audiovia juice fires for crate / prop / event / collection cues.engine/telemetryfor 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 atship._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, thetracking.eventsCompleted/weaponsFound/upgradesChosen/artifactsCollected/newlyUnlockedArtifactIdsrollups).
DOES NOT
- Run combat collision or damage math against in-flight projectiles or enemies. Prop and debris collision write
damagePlayerand signalprop_break/crate_break; everything else is handed off toengine/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 aspawn_wavecallback 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
EventSpawnerseeded-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_breakon crate pop andprop_breakon prop pop so artifacts (Crate Buster and friends) can chain. - Fires
level_upon every player level-up so VFX and effect-engine listeners can react. - Subscribes to
enemy_kill,shield_hit,crate_break,tbone_hit, andevent_completefor 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 precomputedLevelData), 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: buildsGenerateResult(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 bybakeLevelfor 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 aRewardChoice(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/setArtifactFlashand 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.generatering-based path (still used by the city biome and any caller that doesn’t supplyLevelData) and the zone-aware path driven bybakeLevel+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
chunkKeywritten at creation, and a separate spatial chunk map is rebuilt bybuildTerrainChunksafter every mutation (event placement, dynamic expansion, GC, sub-event terrain clearing). Collision queries broad-phase through the chunk map; the per-piecechunkKeyis 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_generatedChunksset, separate from the zone-awareLevelData.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
genHubsForPatternandbuildSpokesForPattern; 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.seedHubsscatter pass), then potentially spawned in off-screen spawn-torus chunks byEventSpawner.tick. Hub events are tagged withhubIdand 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
clearTerrainAroundper event and rebuilds the chunk index once at the end if anything was removed. The legacyplaceEventspass 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_buildAutoTriggersare 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 theModifierssystem so laterrecalcpasses 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
forceSpawnAtfor 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
WarpPuddleGroupformed at bake time by union-find over overlapping circles. The ship’s warp state tracks awarpGroupId, not a puddle id, and the per-frame override (applyWarpPuddleOverrides) always writes absolute values fromship._baseso it stays idempotent — atwarpT = 0the writes restore the base stats exactly. The feature is currently flag-gated off viaWARP_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 singleMap.setflip changes the gameplay zone of every cell that depends on it without rebuilding the grid. - Artifacts use both an exclusive flat-state record (
_stkeyed by artifact-specific strings) and the sharedEffectEngineregistry. 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.