Elemental Dimensions v2 — design
Elemental Dimensions v2 — design
Section titled “Elemental Dimensions v2 — design”Status: brainstormed with Chris 2026-07-05, from a content note written by Toby (“Various Dimensions” — 8 dimensions: Generic, Fire, Ice, Void, Blood, Electricity, Light, Poison, each with a themed enemy roster, boss, environmental hazard, and weapon set). This spec covers the foundation needed to support all 8 long-term, plus the first content wave — the 4 dimensions buildable with zero new bosses/enemies/weapons. The other 4 are explicitly deferred (see Decision 8).
Replace the current 3-dimension Elemental Dimensions feature (pyre/null_dim/drift —
fire/void/aether via Graviton/FunZo/Eye, shipped BUILD 141-142, spec
docs/superpowers/specs/2026-07-02-elemental-dimensions-design.md) with Toby’s mapping, and
generalize the underlying system so each future dimension is a data-only addition: a new
AreaDefs entry, not new code.
Context (what already exists — verified via code, not memory)
Section titled “Context (what already exists — verified via code, not memory)”AreaDefs(sim/area_defs.gd) holdshome/aurora/ember_reach(the plain wormhole cycle) pluspyre/null_dim/drift(the 3 live Dimensions,DIMENSION_IDSarray). Each Dimension entry haselement,boss, and a flatenemy_types: Array[int](pool type ids).- Every enemy type’s element is otherwise hardcoded in
bible.json(_enemy_base_el[type_id]at load). The live Dimensions feature deliberately only rosters enemies that already carry the dimension’s native element (old spec’s Decision 6: “no new bespoke enemy types” — true here too, but it also never needed to reflavor an enemy’s element, which Toby’s design requires). - 3 bosses are parameterized by element today:
FunzoDirector/GravitonDirector/EyeDirectoreach takeelement_idx, dispatched fromSim._spawn_phase_boss()(_boss_gate_count % 3). Warden and Boss2 are NOT in that rotation — they were demoted to mid-wave “elite” spawns (Sim._spawn_due_elites, gatednot AreaDefs.is_dimension(current_area)). However, Warden already has a separate, real arena-clearing “sole boss” spawn path:Sim.spawn_story_boss(today story-mode-only, element hardcoded to"void") spawns the exact same pooledTYPE_BOSS/BEHAVIOR_BOSSentity with the full attack-pattern state machine that_spawn_due_elitesalso uses — same underlying mechanic, different call site and element. TYPE_BOSS’s death branch (Sim._sweep_dead, ~line 1654) already awards gold and reschedules the next boss, but deliberately does NOT call_award_dimension_crystals— because today Warden only ever dies as the mid-wave elite, never as a Dimension’s assigned boss.update_tank_fire(sim/enemy_attacks.gd) already makes thetank/“Pyromancer” archetype periodically fire a fanned volley of homingtank_missile/“Fireball” enemies — i.e. it is already a summon-style mechanic; “Pyromancer” is just its fire-flavored skin.scatterer(blood-element, 5-pellet fan) has nonamefield inbible.jsontoday.- The
orbiterenemy (orbit_shards/orbit_radius/orbit_spin/orbit_damage) and Graviton’s “orbiting satellite blobs” enrichment both already implement satellite-orbit math, independent of each other. Sentinelis an existing, unrelated name in this codebase — a drone class (ui/drone_bay_panel.gd,ui/shop_categories.gd,ui/shop_icons.gd).ContentDB.weapon_available_in(weapon_id, dimension_id)exists (old spec’s Decision 4) but is unused by any of the 7 shipped weapons today — a hook with no real restriction behind it yet.
Toby’s 8-dimension note (source material, verbatim intent)
Section titled “Toby’s 8-dimension note (source material, verbatim intent)”| Dimension | Enemies | Buffs | Boss | Hazard | Weapon(s) |
|---|---|---|---|---|---|
| Generic (temporary) | Swarmer, Shooter, Charger | none | Warden | none | all of them |
| Fire | Swarmer, Charger, Shooter, Lancer | speed, fire rate, proj speed | Warden | orbiting-fireball body | Nova |
| Ice | Swarmer, Orbitor, Weaver, Summoner | health, damage | Nautilus* | floating ice chunk | Orbit |
| Void | Charger, Orbitor, Wyrm*, Ghost, Weaver | size, damage | Graviton | asteroids + poor visibility | Scythe* |
| Blood | Swarmer, Brute, Accumulator | health, size, speed | The Queen* | player-slow zones | Scatter |
| Electricity | Lancer, Shooter, Scatterer, Summoner | fire rate, proj speed | The Last Computer* | warned lightning strikes | Pulse, Turret |
| Light | Swarmer, Lancer, Orbitor, Accumulator | fire rate, damage | Sentinel (=Eye, reskinned) | warned lasers | Beam |
| Poison | Weaver, Wyrm*, Swarmer, Brute | HP-drain-on-hit (new status) | FunZo | green FunZones | Chalice* |
* = not in the game yet (Toby’s own notation), except Sentinel, which is new as a boss
despite lacking a star — see Decision 3.
Decision 1 — this spec ships Generic, Fire, Void, Light; defers Ice, Blood, Electricity, Poison
Section titled “Decision 1 — this spec ships Generic, Fire, Void, Light; defers Ice, Blood, Electricity, Poison”Chosen because these 4 need zero new bosses, enemies, or weapons — only the foundation below plus reflavored existing content. The other 4 each need at least one hard blocker: Ice/Blood/Electricity need a brand-new boss (Nautilus/Queen/Last Computer — each a 5-part undertaking per this repo’s own documented lesson: death branch, nuke exemption, renderer+color LUT, HP hookup, movement skip); Poison needs a new player-status mechanic (HP-drain-on-hit, not a simple stat multiplier). Wyrm (Void, Poison) and Scythe/Chalice (Void, Poison, Electricity) are independently deferred and don’t block those dimensions shipping without them — Void ships with Charger/Orbitor/Ghost/Weaver (no Wyrm yet) and a temporary weapon set (no Scythe yet).
Decision 2 — retire pyre/null_dim/drift, replace with Toby’s mapping
Section titled “Decision 2 — retire pyre/null_dim/drift, replace with Toby’s mapping”The current live mapping (Pyre/fire←Graviton, Null/void←FunZo, Drift/aether←Eye) is superseded,
not kept alongside the new one. AreaDefs.DIMENSION_IDS becomes [fire, void_dim, light]
(same size as today — 3 — since Ice/Blood/Electricity/Poison aren’t added until their own specs
land; the “always offer all 3” rule needs no random-subset logic yet). The Drift/aether
dimension has no equivalent in Toby’s 8 — Eye moves to Light instead (see Decision 3). Aurora
and Ember Reach (the plain non-Dimension wormhole cycle) are untouched.
| New id | Element | Boss | Enemies (native element in parens) |
|---|---|---|---|
fire |
fire | Warden (new dispatch, see Decision 4) | Swarmer(fire, native), Charger(void→fire), Shooter(lightning→fire), Lancer(light→fire) |
void_dim |
void | Graviton (native — zero rework) | Charger(void, native), Orbitor(cold→void), Ghost(void, native), Weaver(decay→void) |
light |
light | Eye, renamed “Sentinel” (light←aether) | Swarmer(fire→light), Lancer(light, native), Orbitor(cold→light), Accumulator(fire→light) |
| Generic (not a portal dest.) | none | Warden (new dispatch) | Swarmer, Shooter, Charger — all native, no reflavor |
Decision 3 — “Sentinel” is Eye, renamed and re-elemented; flag the naming collision, don’t avoid it
Section titled “Decision 3 — “Sentinel” is Eye, renamed and re-elemented; flag the naming collision, don’t avoid it”Eye’s predictive-laser/blink/multi-beam moveset fits “a watching Sentinel” thematically better
than “The Eye” fits Light specifically, so I’m keeping Toby’s name. Sentinel is already a
drone-class display name elsewhere in the UI (ui/drone_bay_panel.gd etc.) — no code collision
(different systems, different ids), but it’s a real “will a player conflate these” risk (e.g. a
future “defeat the Sentinel” banner appearing near a drone-shop screen). Recorded here so it
isn’t rediscovered later as a surprise; a copy pass before wide release should sanity-check any
UI surface where both could appear together.
Decision 4 — wire Warden into the Dimension-boss system as a third parameterized call site
Section titled “Decision 4 — wire Warden into the Dimension-boss system as a third parameterized call site”Sim.spawn_story_boss already does 90% of this (pooled TYPE_BOSS, full attack pattern,
element parameter) — it’s just hardcoded to "void" and story-mode-only. Adding a
survival/Dimension dispatch means: (a) parameterize the element argument instead of the
hardcoded literal (same one-line change Decision 1 of the old spec made for FunZo/Graviton/Eye),
(b) add "warden" as a valid AreaDefs.boss_for(id) value and a matching dispatch arm
alongside the existing FunZo/Graviton/Eye rotation, gated to fire only when current_area is
Generic or Fire, (c) add a spawn-origin flag (_boss_is_dimension_boss: bool, set at spawn,
cleared on death) so TYPE_BOSS’s death branch only calls _award_dimension_crystals when
Warden died AS a Dimension’s assigned boss — not when it died as the ordinary
_spawn_due_elites mid-wave elite in Home/Aurora/Nebula, which must keep behaving exactly as it
does today. Both call sites remain; only their death-reward divergence is new.
Decision 5 — enemy element override + the Tank/TankMissile pairing
Section titled “Decision 5 — enemy element override + the Tank/TankMissile pairing”Each AreaDefs roster entry changes shape from Array[int] (pool type ids) to
Array[{type: int, element: String}], element optional (omitted = use the enemy’s bible-native
element — covers Void’s Graviton-native case and Generic/Fire’s Swarmer with zero special
casing). Sim._spawn_one’s element lookup becomes: check the current dimension’s per-type
override first, fall back to _enemy_base_el[type_id] otherwise. Because TYPE_TANK
(“Pyromancer”/“Summoner”) always fires TYPE_TANK_MISSILE (“Fireball”) as a linked pair
(update_tank_fire), overriding Tank’s element for a dimension implicitly overrides the
missile’s too — a roster entry for Tank carries a paired_with: EnemyPool.TYPE_TANK_MISSILE
note so a future dimension doesn’t have to redundantly list both. No dimension in this phase
uses the Tank/Summoner pairing (it’s Ice/Electricity content) — the mechanism is built now
because it’s the same code path as every other override, so the later Ice/Electricity specs
don’t need to reopen sim.gd’s spawn logic.
Decision 6 — per-dimension enemy stat-buff framework
Section titled “Decision 6 — per-dimension enemy stat-buff framework”New optional AreaDefs field per dimension: enemy_buffs: {speed_mult, fire_rate_mult, proj_speed_mult, hp_mult, damage_mult, size_mult}, every field defaulting to 1.0 (no-op) when
absent. Applied ONCE, at spawn time, in Sim._spawn_one — multiplying the relevant EnemyPool
columns immediately after the existing per-enemy _vary_stats jitter, and scaling the same
per-type fire-timer/proj_speed fields update_tank_fire/enemy_attacks.gd’s ranged loops
already read. Not a live per-tick multiplier, so it can’t interact with anything time-based or
threaten determinism beyond the existing boss-death gate. This phase’s actual dimensions use:
Fire → {speed_mult, fire_rate_mult, proj_speed_mult}; Void → {size_mult, damage_mult}; Light
→ {fire_rate_mult, damage_mult}; Generic → none (matches Toby’s “Enemy buffs: none”). Exact
magnitudes are placeholder-but-sane starting values, flagged as tunable consts (same convention
as FIRE_RATE_CAP/ELITE_HP_MULT elsewhere) — real numbers come from playtesting, not this spec.
Decision 7 — hazards (one new reusable primitive) and weapons (a real gate behind the existing hook)
Section titled “Decision 7 — hazards (one new reusable primitive) and weapons (a real gate behind the existing hook)”Hazards, dispatched from the existing per-dimension timer pattern (old spec’s Decision 4):
one new reusable primitive, a drifting hazard body (position, slow drift velocity,
contact-damage radius, optional list of orbiting child bodies) covers both Fire’s “large fireball
with smaller ones orbiting it” (reusing the orbiter enemy’s orbit-shard math and Graviton’s
“orbiting satellite blobs” enrichment, detached from an enemy/boss) and Void’s “asteroids” (the
same primitive, empty child list). Void’s “poor visibility” and Light’s “warned lasers” need no
new primitive at all: visibility reuses the render-only vision-flicker effect the old spec built
for the now-retired Drift/aether hazard (carried over rather than deleted and rebuilt); lasers
reuse the existing Lancer enemy’s telegraphed beam mechanic (enemy_attacks.gd +
lancer_telegraph_renderer.gd), fired environmentally instead of by an enemy — the same reuse
pattern the old spec used for its Fire/bomber hazard. Generic has no hazard (matches Toby).
Weapons: new optional AreaDefs field per dimension, weapons: Array[{id: String, temporary: bool}] — absent/empty means unrestricted (Generic’s case, matching home today).
ContentDB.weapon_available_in (currently unused by any real weapon) becomes a real gate: if a
dimension defines a list, roll_upgrade_choices’s weapon-grant filter only offers listed
weapons. Proposed sets for this phase (creative picks, not load-bearing architecture — Chris may
retune at spec review):
| Dimension | Weapons | Temporary? |
|---|---|---|
| Generic | (all, unrestricted) | — |
| Fire | Nova (native) + Turret, Scatter | Turret/Scatter |
void_dim |
Orbit, Blade | both — Void’s real weapon (Scythe) doesn’t exist yet |
| Light | Beam (native) + Pulse, Blade | Pulse/Blade |
temporary: true is a data-only marker (no runtime behavior difference) so a later pass can grep
for it to know what to retire once Scythe/Chalice and other elemental weapons ship, per Toby’s
own stated intent.
Decision 8 — explicitly deferred (documented so it isn’t silently forgotten)
Section titled “Decision 8 — explicitly deferred (documented so it isn’t silently forgotten)”- Ice, Blood, Electricity, Poison as playable dimensions — each needs its own spec once its blocking new content exists (a new boss for the first three; the HP-drain-on-hit status effect for Poison).
- 3 new bosses — Nautilus (Ice), The Queen (Blood), The Last Computer (Electricity). Not designed at all here beyond Toby’s one-line flavor notes (“water-themed”, “summons/dashes only”, “colourful bullet-hell”).
- Wyrm — the shared-health, individually-damageable multi-segment worm. No existing
EntityPool/EnemyPoolmechanic resembles this; needs its own design pass before any dimension that lists it (Void, Poison) gets it. Both dimensions ship without it in this phase. - Scythe and Chalice — new weapons (%-health damage; poison AOE respectively). Void and Poison both ship with temporary weapon substitutes until these exist.
- Poison’s HP-drain-on-hit mechanic — “10% of damage taken becomes temporary max-HP loss,
restored on leaving the dimension” is a new player-status effect, not a stat multiplier; out of
scope for the
enemy_buffsframework in Decision 6. - Random-subset portal selection —
DIMENSION_IDSstays “always offer all 3” in this phase; once Ice/Blood/Electricity/Poison bring the pool past 3, a later spec needs to decide how a boss-kill picks which 3 of N to offer.
Testing
Section titled “Testing”New tests/test_dimensions_v2.gd (extending, not replacing, tests/test_dimensions.gd —
tests/test_areas.gd/tests/test_wormhole.gd are flagged by CLAUDE.md as having bitten a prior
N-way generalization before; grep both for hardcoded assumptions before considering this pass
done):
AreaDefs.DIMENSION_IDS == [fire, void_dim, light];pyre/null_dim/driftno longer resolve as dimensions.- Enemy element override: a roster entry with an override spawns at the overridden element; an entry with none falls back to native (covers Void’s Graviton-native case with no special casing).
- Tank/TankMissile pairing: overriding Tank’s element for a dimension re-elements the missiles it fires too.
- Stat-buff framework: each of the 6 multiplier fields applies once at spawn; every field
defaults to a no-op (
1.0) when a dimension omits it. - Warden-as-dimension-boss: spawning via the new Generic/Fire dispatch sets the spawn-origin
flag and its death branch awards dimension crystals; spawning via the pre-existing
_spawn_due_elitesmid-wave path does not (regression guard against the two paths bleeding into each other). - The 3 hazards (Fire drift-and-orbit body, Void asteroids + visibility, Light warned laser) each fire at most once per interval and never while a boss is alive.
weapon_available_incorrectly restricts to each dimension’s list; Generic stays unrestricted.- Existing determinism tests (
tests/test_determinism_checksum.gd,tests/test_determinism_crystals.gd) still pass — every new spawn/rng path here is boss-death-gated exactly like the current live feature, so the baseline should hold by construction; re-verify anyway per this repo’s standing rule.
Rollout
Section titled “Rollout”Once green (full suite + both determinism tests + boot check), deploy via the established
bh-deploy skill to Apple TV + iPhone. Update CLAUDE.md’s “Current status” section and the
roadmap memory noting this replaced Pyre/Null/Drift, plus every Decision above so Chris (or
whoever designs Ice/Blood/Electricity/Poison next) doesn’t have to re-derive the reasoning.