Skip to content

Remote Console v2 — No-XP, Reset everything, crystal rollback, neon mission-control panel

Chris’s ask (2026-10-01): a toggle to turn upgrades off, reset-everything and remove-crystals controls, a bigger more graphical panel, better organisation, and graphical tooltips explaining each control. Brainstorm decisions are recorded inline; the approved visual mockups are in .superpowers/brainstorm/25548-1790883350/content/ (neon-look.html is the target look).

Existing architecture this builds on: docs/architecture/dev-tools.md § Remote Playtest Console.

1. Game-side changes (dev builds only, all default OFF)

Section titled “1. Game-side changes (dev builds only, all default OFF)”
  • Decision: “upgrades off” = no XP at all (not “suppress offers”, not “disable owned mods”).
  • Sim.dev_no_xp: bool = false. Gated in Sim._bank_xp() (the single pilot.xp += amount site), so it covers both AUTO_COLLECT_XP banking and gem pickup; nothing touches xp / pending_levelups while it is set. Gems still drop and kills/gold/telemetry are unaffected.
  • Console Level up / Grant / Crystals still work (they don’t go through _award_xp).
  • Dispatch arm "no_xp": {on: bool}; build_status() reports no_xp.
  • Also a Debug Settings row (parity rule, memory bullet-heaven-debug-settings-console-parity).
  • Determinism: a flag that defaults false and draws no RNG → baselines unmoved.
  • Decision: reset = existing reset_upgrades (weapons, stat mods, crystal wallet, level, HP, meta/hull re-apply) + clear enemies and gems. Run gold, dev toggles and the run clock are NOT touched.
  • Dispatch arm "reset_all" = the reset_upgrades arm followed by the clear arm’s body, in one command so there is no frame between them where enemies hit a fresh, low-HP ship.
  • The existing reset_upgrades button stays (build only, arena kept).
  • Decision: removing crystals below a rung undoes that rung’s mod / evolution (not just a cosmetic wallet decrease).
  • Today a rung mutates the weapon in place (apply_mod adds, evolve() swaps form) and nothing is recorded, so there is no inverse (sim/sim.gd dev_level_weapon comment says exactly this).
  • Approach — mod log + rebuild (approved):
    • PilotArsenal.mod_log: Dictionary — wid → Array[{op:"mod"|"evolve", kind, mag, src}], where src is "card" (UpgradeSystem.apply_weapon_mod / aim card), "dev" (dev_level_weapon), "rung:<i>" (Sim._apply_weapon_thresholds) or "evolve" (UpgradeSystem.evolve_weapon). Every existing apply_mod / evolve() call site outside the weapon classes is one of these five (verified: upgrade_system.gd:295/324/344, sim.gd:991/994); each appends to the log next to its existing call. The recorded mag is the value actually applied, so a replay is exact.
    • PilotArsenal.rebuild_weapon(wid, content, skip: Callable) constructs a fresh instance exactly as _init does for that wid (incl. element_idx), reassigns BOTH the named member (nova, beam, …, weapon for pulse, aim) and weapon_by_id[wid], then replays the log entries skip doesn’t reject. weapon_levels[wid] is recomputed from the replayed card/dev mods. The match over wids is the single place that knows member names; a test asserts it covers every key of weapon_by_id plus aim, so a new weapon can’t silently skip it.
    • Sim.dev_remove_crystals(element, n) -> int: lowers the wallet (floor 0), then for each pilot and each wid with a thresholds_done key whose rule is no longer met (WeaponThresholds.rules_met), erases that key and rebuilds the weapon skipping those rung:<i> entries. An evolve rung lost also drops the evolution. Touches no RNG stream.
    • CrystalState gains remove(element, n) (it only has add today).
    • Dispatch arms "remove_crystals": {element, n} and "clear_crystals" (all elements to 0, same rollback path).
  • Accepted cost: a rebuilt weapon loses its live runtime state (in-flight projectiles, orbit phase, deployed turrets, cooldown timers). The panel tooltip says so.
  • Risk to check during implementation: any render node that caches a weapon object (rather than reading pilot.arsenal.<member> each frame) would hold the stale instance after a rebuild. Grep render/ui for stored weapon refs before Task 3; if one exists, re-read on a rebuild signal (sim.render_fx_events event weapon_rebuilt).
  • Free follow-on (not in scope): dev_level_weapon can go DOWN using the same log.
  • Determinism: the log is extra plain data that nothing on the baseline path reads; rebuilds only run from dev commands → baselines unmoved. If a state hash ever includes the arsenal, the log is excluded from it.

build_caps() additionally publishes:

  • element_colors: element → "#rrggbb", from bible.json via ElementPalette.vivid (the same colour the game draws), so the panel never hard-codes the palette.
  • crystal_rungs: element → [{amount, wid, weapon_name, kind, evolve:bool}] from WeaponThresholds.TABLE — drives the rung ladder.
  • descs: one-line descriptions for caps-driven roster entries (graphics toggles, touch flags, dev flags, audio buses), from new parallel *_DESCS dicts on DebugSettingsPanel next to the existing roster consts. A roster entry with no desc gets no tooltip, never a made-up one; a test lists the uncovered ones so they’re visible.

2.1 Layout — “Mission control” (chosen: A)

Section titled “2.1 Layout — “Mission control” (chosen: A)”

Full browser width (today capped at 760px). Three columns:

  • Left icon sidebar: Run · Spawn · Build · Crystals · Visuals · Perf · Settings.
  • Centre workspace: the selected section, as large graphical tiles (icon + label, ON badge / value badge, glow when active; red family for destructive tiles).
  • Right live column (always visible): fps sparkline (last ~60 status samples), HP bar, level + XP bar, gold, enemies, run time, area, crystal mini-gems, chips for every active dev toggle, and a collapsed “raw status JSON” drawer (replaces the Readout tab).
  • Below ~900px the live column drops under the header as a compact strip and the sidebar becomes a top icon row, so a phone still works.
Section Contents
Run Invincible · Pause spawns · No XP · Clear arena · Time scale · Damage mult · Level up · +Gold · Dimension · Reset upgrades · Reset everything
Spawn count / placement / elite → enemy tiles → boss tiles → custom-enemy builder + saved presets
Build weapon cards (level pips, evolved badge, grant / remove / +N) + player stat sliders (merges old Player + Weapons)
Crystals one card per element in its game colour: count, −10/−5/−1 · +1/+5/+10, rung ladder (ticks at each rung amount, labelled amount · weapon effect, lit when met), Clear all crystals
Visuals graphics toggles, background, tilt, weapon looks, Funzo lab
Perf perf scenario form + results table (unchanged behaviour, wider)
Settings audio buses, touch flags, dev flags

2.3 Tooltips (chosen: A — hover / long-press popover card)

Section titled “2.3 Tooltips (chosen: A — hover / long-press popover card)”
  • Hover (desktop) or long-press ~400ms (touch) on any control opens a card: icon + title, what it does, “Touches:” line, optional amber caveat line, and an optional tiny inline SVG diagram where it helps (No XP frozen bar, Reset everything, crystal rollback, time scale).
  • Copy for panel-defined controls lives in one TOOLTIPS table in the panel source, keyed by a data-tip id on each control; caps rosters use caps.descs. Every panel-defined control must have an entry — the node test fails on a data-tip with no TOOLTIPS entry and vice versa.
  • Rendered with textContent / DOM nodes only (stored-XSS rule); the SVGs are static panel markup.

2.4 Look — the game’s neon language (approved: neon-look.html)

Section titled “2.4 Look — the game’s neon language (approved: neon-look.html)”
  • Colours = ui/tokens.gd exactly: accent #33e6ff, text #d9f7ff, dim #99b8cc, danger #ff525c, warn #ff9e33, gold #ffd64d, HP #4dff80, XP #b3ff4d, success #66ff99; element colours from caps.element_colors. Declared once as CSS custom properties.
  • Fonts: Orbitron (headers, labels) + JetBrains Mono (numbers, body) via Google Fonts — the same faces the game ships (fonts/).
  • Panels: dark translucent fill, 12px radius, 1px accent border, soft accent glow — the in-game StyleBoxFlat house style. Faint grid + violet/cyan radial wash background.

panel.html.js (879 lines, one template literal) is split into string modules composed by panelHtml(): panel/styles.js, panel/markup.js, panel/client.js, panel/tooltips.js. Same constraints as today: no backticks or ${ inside the template strings, textContent only, caps published at connect. worker.js routes unchanged.

  • Unknown element / non-positive n on remove_crystals → no-op, returns the current count (mirrors dev_add_crystals).
  • Commands from a game that predates this build: the panel hides tiles whose caps/status keys are absent (e.g. no no_xp in status → No XP tile disabled with tooltip “needs build ≥ N”), instead of showing a control that silently does nothing (the “empty section = stale build” trap).
  • Flag-write hold-off (~1.8s) kept for every new toggle.
  • GUT (TDD, bh-write-test):
    • No XP: real spawn → kill → _sweep_dead path with the flag on awards 0 XP and no level; off awards the normal amount (CLAUDE.md XP-on-biomass rule — never hand-set pending_levelups).
    • Reset everything: enemies + gems empty, build fresh, gold unchanged.
    • Rollback: cross a mod rung and an evolve rung via dev_add_crystals, also apply a card mod; remove below both → weapon stats equal a fresh weapon + only the card mod, evolved == false, thresholds_done keys gone, weapon_levels correct; re-adding re-applies the rung once.
    • rebuild_weapon covers every weapon_by_id key + aim; named member and dict entry are the same object after a rebuild.
    • Caps: element_colors, crystal_rungs, descs shape.
    • Determinism: all three baseline files unmoved.
  • Node (cd control && node --test): panelHtml() composes; every data-tip has a tooltip and vice versa; no backtick / ${ in the string modules.
  • Real browser: the stub-server recipe (memory bullet-heaven-remote-playtest-console § Verification recipe): serve panelHtml() with stubbed /caps + /status, drive it headless, screenshot at 1440px and 390px, check every section renders rows, tooltips open, live column updates. Compare against neon-look.html.
  • Device: Chris’s ATV — connect, exercise No XP / Reset everything / remove crystals across an evolve rung.
  • Implementer subagents in their own worktree (.claude/worktrees/console-v2, off origin/main).
  • Game changes need a new dev build on device; panel changes need a Worker deploy (docs/architecture/dev-tools.md). Update that doc’s Remote Console section and the console memory when done. Add the item to docs/RELEASE-1.0.1.md first (queue rule).