PURPOSE
Bridges the precomputed zone grid with the existing GameMaster enemy spawner. The spawner picks WHERE to spawn (ring around camera); this adapter classifies that position’s zone and returns the appropriate enemy pool, base spawn rate, and director-driven multipliers. When no LevelConfig is active, callers receive null and fall back to the legacy spawner behavior unchanged.
OWNS
ZoneSpawnResultinterface — zone type, base rate, and weighted pool returned for a queried spawn position.querySpawnZone(worldX, worldY, levelData)— classifies a candidate spawn position and returns its zone config bundle.pickFromPool(pool, roll)— weighted-random selector over{ type, weight }[]; weights do not need to sum to any constant.shouldSpawn(baseRate, directorMult, dt, roll)— per-frame probability check combining zone rate with the AI Director multiplier.DirectorZoneInputs/DirectorZoneOutputsinterfaces — the input snapshot and output knobs for the difficulty curve.computeDirectorZoneOutputs(inputs)— the canonical 4-minute difficulty curve producingspawnRateMultiplierandeliteChance.- Internal helpers
getZonePool,lerp,clamp.
READS FROM
../../data/level-config—Hub,Spoke,ZoneType,SpawnZoneConfig,ZonePooltypes.../world/chunk-manager—LevelDatatype (the precomputed bundle holdingzoneGrid,generation.hubs,generation.spokes,illuminationMap, andconfig.spawnZones).../world/zone-classifier—lookupZonefor grid-based zone classification at a world position.- Director inputs supplied by the caller: run timer, player HP fraction, hubs illuminated, current player zone, recent damage.
PUSHES TO
- Returns
ZoneSpawnResult | nullto the caller (the GameMaster spawner) — pool, base rate, and zone label for the requested position. - Returns
DirectorZoneOutputsto the caller —spawnRateMultiplier(clamped 0.3–3.0) andeliteChance(clamped 0–0.3) to scale spawn cadence and elite roll. - Returns selected enemy type IDs (strings) from
pickFromPool.
DOES NOT
- Does not pick spawn positions; the GameMaster spawner still owns ring-around-camera placement.
- Does not instantiate, register, or own enemy entities; only returns IDs and rates.
- Does not mutate
LevelData, the zone grid, the illumination map, or any director state. - Does not read time, RNG, input, or rendering — all randomness and
dtare passed in by the caller. - Does not handle the elite roll itself; it only emits the
eliteChancevalue. - Does not apply when
levelDatais null — returnsnullso callers can passthrough to legacy behavior.
Signals
ZoneSpawnResult—{ zone: ZoneType, baseRate: number, pool: Array<{ type: string; weight: number }> }.DirectorZoneInputs—{ runTimer, playerHpFrac, hubsIlluminated, playerZone, recentDamage }.DirectorZoneOutputs—{ spawnRateMultiplier, eliteChance }.ZoneTypecases handled bygetZonePool:wilds,spoke_dark,spoke_lit,hub_dark,hub_lit(the latter two map tohub_illuminated/spoke_illuminatedkeys inSpawnZoneConfig).
Entry points
querySpawnZone(worldX, worldY, levelData)— main spawner integration point; returns pool+rate for a candidate position ornull.pickFromPool(pool, roll)— pool resolution; caller supplies the random roll.shouldSpawn(baseRate, directorMult, dt, roll)— per-frame gate combining zone base rate with director multiplier.computeDirectorZoneOutputs(inputs)— director extension that produces the current frame’s rate multiplier and elite chance from the inputs snapshot.
Pattern notes
- Pure module: no side effects, no module-level state, no I/O. All time, randomness, and game state arrive as arguments.
- Passthrough fallback when
levelDatais null preserves the legacy spawner code path without branching on a feature flag. - Difficulty curve is normalized over a 240-second (4-minute) run via
t = runTimer / 240and assembled from piecewise linear segments usinglerp. - Rate curve breakpoints: 0.5x at 0s, 1.0x at 30s, 1.2x at 120s, 1.5x at 180s, 2.0x at 210s, 3.0x at 240s.
- Elite chance breakpoints: 0 until 60s, ramps to 0.10 by 120s, 0.15 by 180s, then 0.30 by 240s.
- Pressure-relief clause: when
playerHpFrac < 0.3andrecentDamage > 20, the rate multiplier is capped at 0.5x before the illumination scaling. - Illumination compensation: rate is scaled by
1 + (hubsIlluminated / 20) * 0.5, hardening remaining dark territory as the player claims hubs (assumes ~20 reachable hubs). - Outputs are hard-clamped:
spawnRateMultiplierto[0.3, 3.0],eliteChanceto[0, 0.3]. pickFromPoolfalls back to the last pool entry when floating-point error leavestarget > 0after iteration, so callers never receiveundefined.