PURPOSE
Defines the data model and pure helpers for warp puddles — circular purple zones scattered on terrain at level-gen time. A simplified replacement (v5.95+) for the earlier fbm-perturbed ellipse model: plain circles with cheap containment tests, with overlapping circles merged into groups via union-find. Currently parked behind a kill-switch (WARP_PUDDLES_ENABLED = false) since 2026-04-21, with all code paths retained in-repo for future iteration.
OWNS
WarpPuddleinterface — single circular zone withid,hubId,x,y,radius,colorSeed.WarpPuddleGroupinterface — merged set of overlapping puddles withid,members, axis-alignedbboxfor coarse culling.WARP_PUDDLES_ENABLEDmaster kill-switch constant.- Spawn tuning constants:
ANY_PUDDLE_CHANCE(0.30),SECOND_PUDDLE_CHANCE(0.10),MIN_RADIUS(120),MAX_RADIUS(280),MIN_PUDDLE_SEPARATION_MULT(1.1). puddleContainsPoint— squared-distance circle containment test.groupContainsPoint— bbox-rejected member-circle containment test.findContainingGroup— linear scan over groups returning the first containing group or null.groupPuddles— union-find over an O(n²) overlap matrix, returning merged groups with computed bboxes.spawnPuddlesForHub— deterministic 0–2 puddle spawner per hub, gated by the kill-switch and the spawn-chance rolls.
READS FROM
- Externally supplied deterministic
rng: () => number(passed in by the level generator) for all spawn rolls — placement angle/distance, radius, second-puddle chance, color seed. WARP_PUDDLES_ENABLEDflag at the top ofspawnPuddlesForHubto short-circuit to an empty array.- Hub parameters (
hubId,hubX,hubY,hubRadius) supplied by the caller for placement.
PUSHES TO
- Returns
WarpPuddle[]fromspawnPuddlesForHubto the level generator (consumed by the hub-spoke generator / chunk manager). - Returns
WarpPuddleGroup[]fromgroupPuddlesto whatever runtime owns the active group list for collision and rendering. - Provides boolean predicates and lookup results to runtime collision queries (e.g.
ship.warpGroupIdassignment when the player enters a group).
DOES NOT
- Does not render anything. Visual treatment (fill all member circles, stroke only the outer-edge trick, glow, color animation phasing) lives in the renderer that consumes these structs.
- Does not run per-frame logic, mutate any global state, or hold a registry — purely data + pure functions.
- Does not couple to the ship, collision system, telemetry, or audio.
- Does not despawn or recycle puddles — lifetime is governed by the owning chunk/hub.
- Does not seed its own randomness — all stochastic choices flow through the injected
rng. - Does not apply gameplay effects of being inside a puddle — the caller is responsible for mapping group containment to behaviour.
Signals
- Return value of
spawnPuddlesForHub(length 0, 1, or 2) signals to the level generator how many puddles were rolled for the hub. puddleContainsPoint,groupContainsPoint, andfindContainingGroupreturn booleans / group-or-null used by runtime systems to detect entry and resolveship.warpGroupId.- Group
idof the formwg:<firstMemberId>provides a stable identifier suitable for per-frame state comparisons. - Puddle
idof the form<hubId>:wp<index>provides a stable, hub-scoped identifier for culling. colorSeedper puddle is the signal to the renderer to phase-offset color/animation so neighboring puddles don’t lockstep.
Entry points
WARP_PUDDLES_ENABLED— re-exported feature flag read by external systems to skip all puddle work when off.spawnPuddlesForHub(hubId, hubX, hubY, hubRadius, rng)— called once per hub at level-gen time.groupPuddles(puddles)— called once after all puddles for a level/region are spawned to fold overlaps into groups.puddleContainsPoint(p, px, py)— used by collision tests and rendering masks.groupContainsPoint(g, px, py)— used for per-group containment checks with bbox pre-reject.findContainingGroup(groups, px, py)— used to resolve which (if any) group a world-space point falls in.
Pattern notes
- Pure data + pure functions module — no class instances, no hidden state, no side effects.
- Determinism is contract: spawning takes an injected
rng, so the same hub parameters and rng sequence always produce identical puddles. - Kill-switch is an early-return at the top of
spawnPuddlesForHub; downstream functions still work on whatever inputs they receive, so flipping the flag back on requires no other code changes. - Containment tests use squared distance (
dx*dx + dy*dy <= r*r) to avoidMath.sqrt. - Group containment does a coarse axis-aligned bbox reject before iterating members, exploiting the fact that most queried points are far from any group.
- Grouping uses union-find with path compression. The O(n²) overlap scan is acceptable because n is bounded to a few dozen puddles per level.
- Group bboxes are computed once at group-construction time from member centers ± radii and cached on the struct.
- Group
idderives from the first member’s id rather than a counter, keeping ids deterministic across runs. - Spawn placement uses a retry loop (up to 12 attempts) that respects
MIN_PUDDLE_SEPARATION_MULTagainst already-accepted puddles on the same hub — but still allows enough overlap for grouping to kick in across hubs. - Offset distance is bounded to
0.5 * hubRadius, keeping puddles roughly inside the hub footprint. - Radius range (120–280 px) is deliberately tighter and smaller than the previous v1 range (200–600) to keep puddles from dominating the level.
- Spawn probabilities are stacked:
ANY_PUDDLE_CHANCE(0.30) gates any spawn at all; conditional on that,SECOND_PUDDLE_CHANCE(0.10) gates the pair case — net ~27% single, ~3% pair per hub. - Module-level rationale comment captures why the simplification happened (fbm needed identical eval on every collision test and shader pixel, made overlap gnarly, didn’t look better) — a deliberate trade of fidelity for cheapness and clarity.