Remote Console v2 — No-XP, Reset everything, crystal rollback, neon mission-control panel
Remote Console v2
Section titled “Remote Console v2”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)”1.1 No XP toggle
Section titled “1.1 No XP toggle”- Decision: “upgrades off” = no XP at all (not “suppress offers”, not “disable owned mods”).
Sim.dev_no_xp: bool = false. Gated inSim._bank_xp()(the singlepilot.xp += amountsite), so it covers bothAUTO_COLLECT_XPbanking and gem pickup; nothing touchesxp/pending_levelupswhile 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()reportsno_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.
1.2 Reset everything
Section titled “1.2 Reset everything”- 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"= thereset_upgradesarm followed by thecleararm’s body, in one command so there is no frame between them where enemies hit a fresh, low-HP ship. - The existing
reset_upgradesbutton stays (build only, arena kept).
1.3 Crystal removal with rung rollback
Section titled “1.3 Crystal removal with rung rollback”- 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_modadds,evolve()swaps form) and nothing is recorded, so there is no inverse (sim/sim.gddev_level_weaponcomment says exactly this). - Approach — mod log + rebuild (approved):
PilotArsenal.mod_log: Dictionary—wid → Array[{op:"mod"|"evolve", kind, mag, src}], wheresrcis"card"(UpgradeSystem.apply_weapon_mod/ aim card),"dev"(dev_level_weapon),"rung:<i>"(Sim._apply_weapon_thresholds) or"evolve"(UpgradeSystem.evolve_weapon). Every existingapply_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 recordedmagis the value actually applied, so a replay is exact.PilotArsenal.rebuild_weapon(wid, content, skip: Callable)constructs a fresh instance exactly as_initdoes for that wid (incl.element_idx), reassigns BOTH the named member (nova,beam, …,weaponfor pulse,aim) andweapon_by_id[wid], then replays the log entriesskipdoesn’t reject.weapon_levels[wid]is recomputed from the replayedcard/devmods. The match over wids is the single place that knows member names; a test asserts it covers every key ofweapon_by_idplusaim, 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 athresholds_donekey whose rule is no longer met (WeaponThresholds.rules_met), erases that key and rebuilds the weapon skipping thoserung:<i>entries. An evolve rung lost also drops the evolution. Touches no RNG stream.CrystalStategainsremove(element, n)(it only hasaddtoday).- 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_eventseventweapon_rebuilt). - Free follow-on (not in scope):
dev_level_weaponcan 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.
1.4 New caps for the panel
Section titled “1.4 New caps for the panel”build_caps() additionally publishes:
element_colors:element → "#rrggbb", frombible.jsonviaElementPalette.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}]fromWeaponThresholds.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*_DESCSdicts onDebugSettingsPanelnext 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. Panel redesign (control/src/)
Section titled “2. Panel redesign (control/src/)”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.
2.2 Organisation
Section titled “2.2 Organisation”| 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
TOOLTIPStable in the panel source, keyed by adata-tipid on each control; caps rosters usecaps.descs. Every panel-defined control must have an entry — the node test fails on adata-tipwith noTOOLTIPSentry 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.gdexactly: accent#33e6ff, text#d9f7ff, dim#99b8cc, danger#ff525c, warn#ff9e33, gold#ffd64d, HP#4dff80, XP#b3ff4d, success#66ff99; element colours fromcaps.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
StyleBoxFlathouse style. Faint grid + violet/cyan radial wash background.
2.5 Code structure
Section titled “2.5 Code structure”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.
3. Error handling
Section titled “3. Error handling”- Unknown element / non-positive n on
remove_crystals→ no-op, returns the current count (mirrorsdev_add_crystals). - Commands from a game that predates this build: the panel hides tiles whose caps/status keys are
absent (e.g. no
no_xpin 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.
4. Testing
Section titled “4. Testing”- GUT (TDD,
bh-write-test):- No XP: real spawn → kill →
_sweep_deadpath with the flag on awards 0 XP and no level; off awards the normal amount (CLAUDE.md XP-on-biomass rule — never hand-setpending_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_donekeys gone,weapon_levelscorrect; re-adding re-applies the rung once. rebuild_weaponcovers everyweapon_by_idkey +aim; named member and dict entry are the same object after a rebuild.- Caps:
element_colors,crystal_rungs,descsshape. - Determinism: all three baseline files unmoved.
- No XP: real spawn → kill →
- Node (
cd control && node --test):panelHtml()composes; everydata-tiphas 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): servepanelHtml()with stubbed/caps+/status, drive it headless, screenshot at 1440px and 390px, check every section renders rows, tooltips open, live column updates. Compare againstneon-look.html. - Device: Chris’s ATV — connect, exercise No XP / Reset everything / remove crystals across an evolve rung.
5. Delivery
Section titled “5. Delivery”- Implementer subagents in their own worktree (
.claude/worktrees/console-v2, offorigin/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 todocs/RELEASE-1.0.1.mdfirst (queue rule).