Beacon System
Hubs are the only places in the world where events spawn. Every hub is a dormant beacon — dark by default, with one or more charge events anchored to its center. The player drives the run forward by visiting hubs, completing the events inside them, and lighting up the map.
What a beacon is
A beacon is just a hub treated as a one-shot quest target. Each hub on the generated map carries a hubId and a radius hub.r. Events tagged with that hubId use the full hub radius as their charge zone (engine/world/events.ts, “Hub events use full hub radius as the charge zone”), so the player can roam the hub freely while charging. There is no separate “beacon entity” — the hub itself is the beacon, and its center is the lantern.
The activation loop
- Player enters hub. Events tagged with the hub start charging while the player is inside the hub circle. Charge accelerates by +10% per second spent inside (
engine/world/events.ts). - Player stands at center. A separate lantern fill at
hub.x, hub.yticks up while the player is inside the inner activation circle —hub.r * 0.35(ACTIVATION_RADIUS_FRAC = 0.35inengine/world/illumination.ts). - Fill completes. Default fill time is 4 seconds (
DEFAULT_FILL_SECONDS = 4);LevelConfig.illuminationSpeedscales this between near-instant (0) and ~5s (1). Leaving the inner circle drains the meter atDRAIN_RATE = 0.5per second. - Hub illuminates.
system.illuminationMap.set(hub.id, true)flips. The hub is now permanently lit for the run, andsystem.hubsIlluminatedincrements.
What illumination changes
The moment a hub lights up, onHubIlluminated(hubId, hub) (engine/bridge-hub-illumination.ts) fires and cascades four effects:
- Dark-only events get purged. Every event in
world.eventswithhubId === hubIdandzoneConstraint === 'dark_only'is either spliced out (ifidle/failed) or markedfailed(ifactive). The completed event keeps itsdoneflash animation untouched. - Zone classification flips. The hub’s cells in the zone grid switch from
hub_darktohub_lit(engine/world/zone-classifier.ts). Connected spokes also light up —isSpokeLitreturns true if either endpoint hub is illuminated. - Enemies flee. Every enemy within
hub.r * 2of the hub center gets a radial push:force = 300 * (1 - dist / (hub.r * 2)). Closer enemies get shoved harder. - Counter increments.
world._hubsIlluminatedticks up. The director reads this as a run-progression input.
Spawn-pool consequence
Illumination drops the spawn rate floor at lit hubs and their connected spokes. Dark zones keep the higher hostile spawn pressure; lit zones become friendlier travel corridors. illuminated_only events become eligible to spawn at lit hubs, replacing the cleared dark_only set with a different rotation tuned for a player who has already cleared the area.
Permanence
Illumination is run-permanent. Once illuminationMap.get(hub.id) === true, nothing in the run resets it. The system header is explicit: “Illumination is permanent — once lit, always lit. This is the core progression metric of every run.” A new run rebuilds the level data and the map starts dark again.
Why this shape
Hubs as event beacons gives every run the same structural rhythm:
- Dark map → directed exploration. Hostile spawn rates and
dark_onlyevents push the player to find the next hub. - Hub clear → safe pocket. Completing the lantern collapses the local threat and clears the dark-only event clutter.
- Lit network grows. Each beacon connects through spokes, so successive clears thread safer paths back to old hubs.
There is no separate “quest log” or “event picker.” The map IS the quest log; the beacons are the steps.
Related code
engine/world/illumination.ts— lantern state, fill tick,setIllumination,getIlluminatedHubIdsengine/bridge-hub-illumination.ts—onHubIlluminatedcascadeengine/world/events.ts—hubId,zoneConstraint, hub-event charge logicengine/world/zone-classifier.ts—hub_dark/hub_lit/spoke_dark/spoke_litresolutionengine/world/event-spawner.ts— spawn-radius window around the ship