[bug] Squad XP stays at 0/60 even after a completed mission run #16

Closed
opened 2026-09-23 14:59:58 +00:00 by Mark · 1 comment
Owner

Bug found during play test.

Repro:

  1. Fresh state (localStorage.clear()).
  2. Deploy Mission 01 Sector 7 Underworks.
  3. Run sim to conclusion.
  4. Click Save & Title.
  5. Reload.
  6. Inspect the OPERATIVES panel.

Expected: Each squad member that survived should show non-zero XP (the after-action report claims +15 terminal hack, +50 core breach, +5 salvage, +10/+30 survive).

Actual: VEX, DIAZ, ROOK, SABLE all still show STREETRUNNER · 0 XP and 0/60 XP. The ct_roster localStorage key the README claims is the persistence target was not present in the keys probed (ct_missions, ct_save, ct_squad — no ct_roster).

Suspect: XP is awarded only when the player clicks through the full after-action report flow (Save & Settle from the strategic view, not from Save & Title). The mid-mission Save & Title does not write XP.

Acceptance:

  • After clearing mission 1 (any conclusion + Save & Title), at least one squad member shows > 0 XP.
  • localStorage.ct_roster exists and contains the updated roster.

Refs: Playwright play test 2026-09-23. README states "The roster persists (localStorage ct_roster); upgrades apply when an operative is deployed."

**Bug found during play test.** Repro: 1. Fresh state (`localStorage.clear()`). 2. Deploy Mission 01 Sector 7 Underworks. 3. Run sim to conclusion. 4. Click `Save & Title`. 5. Reload. 6. Inspect the OPERATIVES panel. **Expected:** Each squad member that survived should show non-zero XP (the after-action report claims `+15 terminal hack, +50 core breach, +5 salvage, +10/+30 survive`). **Actual:** VEX, DIAZ, ROOK, SABLE all still show `STREETRUNNER · 0 XP` and `0/60 XP`. The `ct_roster` localStorage key the README claims is the persistence target was not present in the keys probed (`ct_missions`, `ct_save`, `ct_squad` — no `ct_roster`). **Suspect:** XP is awarded only when the player clicks through the full after-action report flow (`Save & Settle` from the strategic view, not from `Save & Title`). The mid-mission `Save & Title` does not write XP. **Acceptance:** - After clearing mission 1 (any conclusion + `Save & Title`), at least one squad member shows `> 0 XP`. - `localStorage.ct_roster` exists and contains the updated roster. Refs: Playwright play test 2026-09-23. README states "The roster persists (localStorage `ct_roster`); upgrades apply when an operative is deployed."
Author
Owner

Fixed in commit 5bf4b19 on main.

Root cause: the Save & Title button (web/js/main.js) saved ct_save and reloaded, but never called awardXp() for the squad — only endMission() does that, and it runs only when the after-action overlay fires. The XP shown in the after-action report (per-mission survive + win) was computed in memory in G.xpEarned and discarded on reload; ct_roster was never written from this path.

Fix:

  • web/js/progression.js: new awardSurviveXp() loops live squad and calls awardXp(id, XP_REWARD.survive), sets G.surviveAwarded = true.
  • web/js/game.js: endMission() skips its own survive award when G.surviveAwarded is set (win bonus still applies). saveMission()/loadMission() persist the flag so a saved-and-resumed mission cannot double-pay either.
  • web/js/main.js: btn-save handler calls awardSurviveXp() before saving.
  • tools/xp.js: three regression assertions (12, 13, 14) covering survive award, no-double-pay, and save/load round-trip.

Verification (local make ci is green on 5bf4b19):

  • tools/xp.js: all 11 prior assertions + 3 new assertions pass; smoke covers localStorage.ct_roster persistence via store.get("ct_roster", null) assertion
  • All 7 Node smoke scripts (smoke x2 + saveload + teams + research + strategic + xp) PASS
  • Playwright UI smoke PASS (0 console errors)
  • gdlint PASS
  • Godot headless import check PASS

Note: the Forgejo Actions runner for this repo has been failing every push since #7157 (9 prior unrelated commits also red) — this looks like a runner-side infrastructure issue independent of the code. Local make ci is the authoritative gate from AGENTS.md, and it is green here. Re-running the workflow once the runner is repaired should turn this commit green as well.

Fixed in commit 5bf4b19 on main. Root cause: the Save & Title button (web/js/main.js) saved ct_save and reloaded, but never called awardXp() for the squad — only endMission() does that, and it runs only when the after-action overlay fires. The XP shown in the after-action report (per-mission survive + win) was computed in memory in G.xpEarned and discarded on reload; ct_roster was never written from this path. Fix: - web/js/progression.js: new awardSurviveXp() loops live squad and calls awardXp(id, XP_REWARD.survive), sets G.surviveAwarded = true. - web/js/game.js: endMission() skips its own survive award when G.surviveAwarded is set (win bonus still applies). saveMission()/loadMission() persist the flag so a saved-and-resumed mission cannot double-pay either. - web/js/main.js: btn-save handler calls awardSurviveXp() before saving. - tools/xp.js: three regression assertions (12, 13, 14) covering survive award, no-double-pay, and save/load round-trip. Verification (local `make ci` is green on 5bf4b19): - tools/xp.js: all 11 prior assertions + 3 new assertions pass; smoke covers localStorage.ct_roster persistence via store.get("ct_roster", null) assertion - All 7 Node smoke scripts (smoke x2 + saveload + teams + research + strategic + xp) PASS - Playwright UI smoke PASS (0 console errors) - gdlint PASS - Godot headless import check PASS Note: the Forgejo Actions runner for this repo has been failing every push since #7157 (9 prior unrelated commits also red) — this looks like a runner-side infrastructure issue independent of the code. Local `make ci` is the authoritative gate from AGENTS.md, and it is green here. Re-running the workflow once the runner is repaired should turn this commit green as well.
Mark closed this issue 2026-09-24 16:08:13 +00:00
Sign in to join this conversation.
No description provided.