Skip to content

Element Crystals — an alternate element ruleset (A/B vs reactions)

Element Crystals — an alternate element ruleset (A/B vs reactions)

Section titled “Element Crystals — an alternate element ruleset (A/B vs reactions)”

Status: DESIGN APPROVED 2026-06-25 — not yet implemented. Build after the other agent’s cycle-21 sim.gd churn settles (avoid conflicts). Author: Opus, from Toby’s playtest analysis + Chris’s direction.

Background — the problem with the current (reactions) system

Section titled “Background — the problem with the current (reactions) system”

Toby’s playtest conclusion, confirmed by the project’s own notes: with many fast auto-firing weapons of different elements, elemental reactions fire constantly and collapse into “permanent status effects + a ton of damage” — the strategy evaporates (CLAUDE.md cycle 12-13: “reactions are VERY lethal … constant AoE bursts + terrain DoT; ~350 kills/min, player untouched … trivializes challenge”). Toby framed it as combinatorial blow-up (17 elements → 17C2 = 136 pairs). Technical nuance: the sim is actually single-active-aura (1 aura + innate base, react-then-replace — not 136 live pairs), so the mechanism is constant re-triggering under auto-fire, not simultaneous coexistence. The outcome Toby describes is exactly right either way: reactions become background noise, not a decision.

Prototype a fundamentally different element system — Element Crystals — that turns elements into a build economy driving weapon upgrades instead of real-time reactions. Run it as an A/B against the current reactions system in the SAME repo, both playable, telemetry-tagged, so real data decides which ships.

Why this fits the game’s pillars: it preserves Pillar 1 (one-stick, auto-fire/auto-target — no manual element management) and leans into Pillar 2 (deep transformative build-craft). (The rejected alternative — “one player-controlled weapon at a time + richer reactions” — fights Pillar 1.)

  1. Vehicle: in-repo A/B behind a ruleset flag (NOT a separate game; reuses the deterministic sim / enemies / renderers / telemetry — a true apples-to-apples comparison).
  2. Upgrade model: weapons upgrade automatically when crystal thresholds are met; the manual per-weapon mods (wm:) become the threshold table. Level-up offers crystals (~1/3) + the existing weapon-grant / stat-mod / transformative-mod pool (~2/3). High thresholds trigger evolutions.
  3. Enemy elements: pure economy — reactions OFF, enemy element is cosmetic only. Crystals are the player’s build resource; no resistances, no status effects.
  4. Crystal types: start with a curated ~6 core types (e.g. FIRE / COLD / LIGHTNING / VOID / BLOOD / LIGHT) for a legible portfolio; expandable later.
  • Sim.ruleset: int = RULESET_REACTIONS (enum: RULESET_REACTIONS default | RULESET_CRYSTALS). Mirrors the proven Sim.story null-object pattern. Defaults to reactions and is set only by main after construction → the determinism baseline is byte-identical (the determinism test never sets it). A Sim.enable_crystals() (or a setter) flips it before the run, like enable_story().
  • In RULESET_CRYSTALS, the reaction path short-circuits. Sim._apply_element(ei, el) still sets the cosmetic aura tint for visuals but skips _on_reaction / _reaction_burst / terrain zones / priming. StatusEffects DoT + shock-vuln are off (they’re part of the reaction system). Enemies take direct weapon damage only. Every reaction hook is guarded if ruleset == RULESET_REACTIONS.
  • Determinism for crystals mode itself: crystal drops draw from upgrade_rng (NOT the spawn rng, same discipline as today). A separate test_determinism_crystals.gd can pin a crystals-mode trace once built; the existing baseline test stays on reactions.

sim/crystal_state.gdCrystalState (pure /sim, RefCounted)

Section titled “sim/crystal_state.gd — CrystalState (pure /sim, RefCounted)”

Per-run crystal wallet. counts: Dictionary (element_id → int). add(element_id, n), count(element_id) -> int, total(), to_dict/from_dict. Lives on Sim.crystals. Per-run only (lost at game over — NOT meta-persisted).

  • ~6 core crystal types to start (subset of existing bible.json elements; expandable).
  • At level-up, roll_upgrade_choices (crystals mode) gives ~1/3 chance one of the 3 choices is an Element Crystals grant: a randomised bundle (e.g. 2-5 crystals across 1-3 types) drawn from upgrade_rng. Picking it calls crystals.add(...). Randomisation forces build variety.
  • The other ~2/3 of choices come from the existing pool: weapon grants + stat mods + transformative mods. The wm: per-weapon mechanical mods are NOT offered in crystals mode (thresholds do that job). Manual evolve: offers are likewise replaced by threshold-driven evolutions.

Weapon thresholds — sim/weapon_thresholds.gd + data

Section titled “Weapon thresholds — sim/weapon_thresholds.gd + data”
  • Data-driven table (in bible.json, per weapon): a list of rules {element, amount, effect}, where effect is an existing weapon-mod kind (e.g. power, shards, radius, chain, …) or evolve. Example (orbit): [{cold:5,"shards"}, {cold:12,"spin"}, {lightning:8,"reach"}, {void:15,"evolve"}]. Each weapon keys off several element types → varied builds.
  • Sim._eval_thresholds() runs whenever crystals change, when a weapon is granted (a late weapon instantly inherits thresholds already met by your existing pile), and once at run start. For each owned weapon, any rule whose crystals.count(element) >= amount AND not-yet-applied fires the effect via the weapon’s existing apply_mod(kind, mag) / evolve() (mechanics already exist — we just trigger them from crystals instead of manual picks). A per-weapon “applied thresholds” set makes it idempotent (only newly-crossed rules fire).
  • Non-consuming + shared: thresholds only read crystal counts; one FIRE pile can satisfy thresholds on multiple weapons simultaneously → emergent synergy (the headline feature).

Sim.roll_upgrade_choices(n) branches on ruleset. Crystals mode: inject a crystal grant with ~1/3 weight, fill remaining slots from weapon-grant + stat-mod + transformative-mod pools (exclude wm: and evolve:). apply_upgrade handles a new crystals:<bundle> id by adding to CrystalState then calling _eval_thresholds().

Removing reactions removes a large AoE-clear contribution → player DPS drops; the threshold ramp compensates but on a different curve. Crystals mode gets its own enemy HP/count tuning (it’s an independent ruleset, free to diverge from reactions mode). Start by reusing current enemy stats, measure via telemetry, adjust. Expect to tune: crystal drop amounts, threshold magnitudes, and enemy HP for crystals mode.

  • Add ruleset (“reactions” | “crystals”) to the gameplay summary (gameplay_telemetry.report_run) and a runs.ruleset column; dashboard groups runs by ruleset.
  • Comparison metrics: median run length, level reached, build variety (build_picks / distinct weapon-evolution paths), kills/min, and — once we add it — session count / replay rate. The reactions-vs-crystals verdict comes from this data, not opinion.
  • Launch menu gains a Crystals entry (alongside Survival / Story) so Chris + Toby can play either; reactions stays the default.

No enemy resistances/weaknesses; no crystals dropped from kills (level-up only); no meta-persistence of crystals (per-run); no new elements beyond the core ~6; no crystals-only enemies or arenas. This is a clean swap of the element layer only — everything else (movement, weapons, enemies, bosses, spawning, meta-progression) is shared between the two rulesets.

The crystals path touches sim.gd heavily (ruleset guards, _eval_thresholds, level-up branch). The other agent is currently deep in sim.gd (cycle 21). Build this after their churn settles, on a clean tree, and keep every change behind the ruleset guard so the reactions baseline never moves.

Open / tuning items (resolve during implementation)

Section titled “Open / tuning items (resolve during implementation)”
  • Exact 6 core elements + their visual identities.
  • Crystal bundle size + per-type distribution; level-up crystal-offer probability (~1/3 start).
  • Per-weapon threshold tables (amounts + which effect at each tier) for all 6 weapons.
  • Crystals-mode enemy HP/count curve.
  • Whether _eval_thresholds runs every crystal change (simple) vs batched per level-up (cheaper) — start simple (per change), it’s bounded by weapon count × rules.