PURPOSE
Orchestrates the 5-phase level lifecycle for chunk-based level data. Bakes the full world once at mission load (hubs, spokes, zone classification, illumination, triggers) into a single LevelData blob, then exposes cheap per-frame queries that determine which chunks fall into the active, spawn, and sim range rings around the camera. Per-frame logic only reads precomputed data — no generation happens after load.
OWNS
ChunkStateinterface — per-chunk runtime state (key,cx,cy,terrainActive,simActive).LevelDatainterface — full precomputed bundle (config,generation,zoneGrid,illuminationMap,eventCompletionMap,triggers,precomputeMs).- Range constants —
ACTIVE_RANGE(2 chunks beyond viewport, widest),SPAWN_RANGE(1 chunk),SIM_RANGE(0.5 chunk, tightest). SCRIPTED_CHUNK_SIZE— fixed 2000 px chunk size for scripted mode.bakeLevel(config, spawnX, spawnY, gridRadius)— the BAKE entry point; returns aLevelData. DefaultgridRadiusis 2 (a 5×5 grid = 25 chunks, ~15–50 hubs).computeActiveChunks(levelData, cameraX, cameraY, viewW, viewH)— per-frame chunk-key sets for active / spawn / sim rings.getVisibleHubs(levelData, activeKeys)andgetVisibleSpokes(levelData, activeKeys)— chunk-keyed lookups.findNearestHub(levelData, x, y)— linear scan to place the player near a hub at spawn.- Internal helpers:
buildScriptedGeneration(config),_buildAutoTriggers(hubs, config),lerp(a, b, t).
READS FROM
../../data/level-config—LevelConfig,Hub,Spoke,PrecomputedWorld,ScriptedHub,ScriptedSpoke,LevelTriggertypes andDEFAULT_LEVEL_CONFIG../hub-spoke-gen—precomputeWorld,calcChunkSize,GenerateResult(the procedural generator forworldMode === 'precomputed')../zone-classifier—buildZoneGrid,ZoneGrid(called with a 64 px cell size against generated hubs and spokes).config.scriptedHubs/config.scriptedSpokes— explicit hub layouts for story missions.config.worldMode— selects'scripted'vs'precomputed'generation path (defaults to'precomputed').config.triggers— designer-authored triggers, merged with auto-generated ones (config wins on ID collision).config.spokeWMin,config.spokeWMax,config.spokeWidth— interpolated to derive the spoke half-width passed tobuildZoneGrid.performance.now()— used to measure bake duration intoprecomputeMs.
PUSHES TO
- Returns a
LevelDataobject that downstream systems hold for the entire run (zone grid, illumination map, event completion map, triggers list). computeActiveChunksreturns threeSet<string>of chunk keys consumed by terrain rendering, event/spawn systems, and enemy AI tickers.getVisibleHubsandgetVisibleSpokesproduce arrays consumed by the renderer and any hub-aware systems.illuminationMapis initialized with every hub id set tofalse; downstream gameplay flips entries.eventCompletionMapstarts empty; downstream systems write completion state into it.
DOES NOT
- Does not render anything — produces data only.
- Does not spawn enemies or tick AI — only marks which chunks are in the sim ring.
- Does not run any generation work per frame — all hub/spoke/zone computation happens inside
bakeLevel. - Does not mutate
LevelDataafter creation aside from the maps that downstream systems write to. - Does not place warp puddles in scripted mode (
warpPuddlesandwarpPuddleGroupsare returned empty). - Does not auto-generate dialogue triggers — only
spawn_wavetriggers on every other hub. Dialogue is designer-authored viaconfig.triggersor playground BUILD MODE. - Does not perform any I/O, asset loading, or network calls.
Signals
None. Module exposes plain functions and types; no event bus, listeners, or callbacks. Downstream systems poll LevelData and the per-frame chunk sets.
Entry points
bakeLevel(config, spawnX, spawnY, gridRadius)— called once at mission load to produceLevelData.computeActiveChunks(levelData, cameraX, cameraY, viewW, viewH)— called every frame by the main loop.getVisibleHubs(levelData, activeKeys)— called every frame by hub-aware consumers.getVisibleSpokes(levelData, activeKeys)— called every frame by spoke renderers.findNearestHub(levelData, x, y)— called at spawn (and on demand) to place the player.
Pattern notes
- Five-phase lifecycle is explicit in the file header: BAKE / GENERATE / POPULATE / SIMULATE / CULL. Phases 1–3 collapse into
bakeLevel; phases 4–5 are driven bycomputeActiveChunksplus downstream readers. - Two world-generation modes share the same downstream shape (
GenerateResult):'precomputed'callsprecomputeWorldfromhub-spoke-gen;'scripted'builds the same struct from explicitscriptedHubs/scriptedSpokeswith a fixed 2000 px chunk size. - Zone-aware generation is precomputed once via
buildZoneGridat 64 px cell resolution — trades spatial precision for ~16× fewer cells, enabling cheap per-frame zone lookups. - World bounds for the zone grid are computed differently per mode: scripted uses hub-position extents plus 2000 px padding; precomputed uses the chunk grid (
gridRadiuschunks in each direction from the spawn-centered chunk). - Range rings are concentric —
ACTIVE_RANGE(2) ⊃SPAWN_RANGE(1) ⊃SIM_RANGE(0.5). Each frame,computeActiveChunksrebuilds the three sets from camera position and viewport size; the math is O(ring area), not O(world). - Chunk keys are stringified as
"cx,cy"(e.g."3,-1") and used asMap/Setkeys throughout. - Triggers merge two sources: every other hub gets an auto
spawn_wavetrigger via_buildAutoTriggers; designer-authoredconfig.triggerstake precedence on ID collision (auto-triggers with conflicting IDs are filtered out). bakeLeveldefaults are conservative for mobile:gridRadius = 2keeps hub count low becauseEventManagerticks every hub event every frame. Target bake time is under 5 s on mobile and under 2 s on desktop.- Scripted mode validates referential integrity — throws if
scriptedHubsis empty or ifscriptedSpokesreferences an unknown hub id. chunkHubIndicesis built during generation and used by bothgetVisibleHubsandgetVisibleSpokesfor chunk-keyed hub lookups; spokes are included when either endpoint hub is in an active chunk.findNearestHubis a linear O(hubs) scan — acceptable because hub counts are small (15–50 typical) and it’s called on demand, not per frame.- Performance timing (
precomputeMs) is captured on the returnedLevelDatafor diagnostics.