Reset Shop (respec) — design
Reset Shop (respec) — design
Section titled “Reset Shop (respec) — design”Date: 2026-07-05
Status: approved (design), pre-implementation
Determinism: unaffected. MetaState is meta-progression, applied to a fresh run’s player
render-side at run start (apply_to) — the same category as buy()/from_dict(), never called
from inside Sim.tick(). No baseline re-pin needed.
1. Concept & scope
Section titled “1. Concept & scope”Add a player-facing “Reset Shop” button to the meta shop: a full respec that refunds every gold coin ever spent (stat levels AND one-time unlocks — weapons, the Obsidian hull, decoy flavours, drone classes), clears all of it back to zero, and reverts the currently-selected ship/decoy/drone-loadout to their starter defaults (since whatever was selected may no longer be owned after the wipe). Requires a confirmation dialog first — this is a real, irreversible loss of purchased content, not a cosmetic toggle.
Confirmed with Chris:
- Respec, not a full save wipe —
seen_enemies(bestiary codex) andtutorial_doneare untouched; they aren’t shop purchases. - Refunds EVERYTHING (stats and unlocks), not stats-only.
- Real player-facing button in the shop UI, not gated behind dev-tools.
- Requires a confirm/cancel step before it happens.
Out of scope
Section titled “Out of scope”- Per-item respec (refunding a single upgrade line only) — this is a full reset, not selective.
- Any “are you sure, really?” second confirmation — one confirm dialog is enough.
- Undo — once confirmed, it’s saved immediately, matching how every other shop action already
saves immediately (
MetaStore.save_stateafterbuy()).
2. Data model — sim/meta_state.gd
Section titled “2. Data model — sim/meta_state.gd”New method, mirroring the existing apply_to(player, defs) / purchased_unlocks(defs) pattern of
taking the meta-upgrade defs as a parameter (pure logic, no ContentDB reference held on MetaState
itself):
# Full respec: refunds every gold coin ever spent (stats AND unlocks) and clears all of it back# to zero. Reverts ship/decoy/drone-loadout selection to starter defaults, since whatever was# selected may no longer be owned after the wipe. Does NOT touch seen_enemies/tutorial_done --# those aren't shop purchases.func reset_shop(defs: Array) -> void: var refund := 0 for def in defs: if not (def is Dictionary): continue var lvl := level_of(def["id"]) var growth: float = float(def.get("cost_growth", 1.5)) var base: float = float(def["base_cost"]) for i in range(lvl): refund += int(round(base * pow(growth, i))) banked_gold += refund levels = {} selected_ship = ShipBonuses.DEFAULT_SHIP selected_decoy = "basic" drone_loadout = ["sentinel"]The refund loop reuses cost()’s exact formula (base_cost * cost_growth^level) evaluated at each
already-owned level, so the refund is always exactly what was spent — not a flat “give back some
gold” heuristic. This makes it naturally correct if per-upgrade costs are rebalanced later (no
separate “spent” ledger to keep in sync).
Render side (wherever the shop calls buy() today) calls MetaStore.save_state(_meta) immediately
after reset_shop(), matching the existing buy-then-save pattern — the reset is durable right away,
not deferred to the next natural save point.
3. UI — ui/meta_shop_panel.gd
Section titled “3. UI — ui/meta_shop_panel.gd”-
A “RESET SHOP” button on the shop’s root view (near the banked-gold header), in a distinct warning color (not the shop’s normal cyan/gold palette) so it reads as a different KIND of action from browsing categories.
-
Pressing it opens a new confirmation overlay (no such pattern exists anywhere in this codebase yet, so this introduces the first one): a dimmed full-screen backdrop behind a centered neon-styled panel (matching the existing Panel + StyleBoxFlat convention used throughout the shop), reading:
RESET SHOP? Refunds all gold spent and clears every stat level and unlock — including owned ships, weapons, and drones. This cannot be undone.
with CONFIRM / CANCEL buttons.
-
CONFIRM →
_meta.reset_shop(defs)→MetaStore.save_state(_meta)→ close the dialog →_rebuild_current()(the panel’s existing full-refresh path, already used after every purchase) so the gold display and every category’s levels/costs reflect the reset immediately. -
CANCEL → close the dialog, no state change at all.
-
Confirm/Cancel are ordinary focusable
Buttons — Godot’s built-in Control focus system already handles d-pad/arrow-key movement between them and controller-A/Enter to activate, the same as every other button in this panel. No bespoke nav state machine needed for a 2-button dialog. -
The confirm overlay is implemented inline in
meta_shop_panel.gd(not a new reusableConfirmDialogclass) — there is exactly one caller today, and building a generic reusable component for a single use site is premature; if a second confirm-dialog need shows up later, extract one then.
4. Testing
Section titled “4. Testing”tests/test_meta_state.gd:
reset_shoprefunds exactly the sum of what was spent (buy a few levels of a couple of upgrades at known costs, callreset_shop, assertbanked_goldmatches the pre-purchase balance).levelsis empty afterward.selected_ship/selected_decoy/drone_loadoutrevert to their defaults, even when a purchased-and-selected ship/decoy/drone class had been active beforehand.seen_enemiesandtutorial_doneare untouched byreset_shop.
tests/test_meta_shop_panel.gd:
- The RESET SHOP button exists on the root view.
- Pressing it opens the confirm dialog without changing any state yet.
- CANCEL closes the dialog with
_metacompletely unchanged. - CONFIRM resets
_meta, persists it, closes the dialog, and the panel’s displayed gold/levels reflect the reset (i.e._rebuild_current()ran).