step 10.1: done

This commit is contained in:
bepis
2026-10-22 22:08:22 +11:00
parent d115f49eaf
commit 120cf88015
4 changed files with 68 additions and 19 deletions
+12 -11
View File
@@ -4,9 +4,9 @@
> If this file disagrees with the code, trust the code and fix this file. > If this file disagrees with the code, trust the code and fix this file.
## 1. Where we are ## 1. Where we are
- **Last completed step:** 9.6 (Update README with real run instructions) — TICKED in STATUS.md - **Last completed step:** 10.1 (Fix expedition START corrupting energy) — TICKED in STATUS.md
- **Next step:** Phase 9 is complete. Ready to start Stretch Goals (S1–S7) or address Known Issues from STATUS.md. - **Next step:** Phase 10.2 — Fix expedition CLAIM overwriting XP
- **Updated:** 2026-10-21 - **Updated:** 2026-10-22
- **Was the last session cut off mid-task?** No - **Was the last session cut off mid-task?** No
## 2. Verified state (run these first) ## 2. Verified state (run these first)
@@ -14,7 +14,7 @@
|---|---| |---|---|
| `npm install` | succeeds | | `npm install` | succeeds |
| `npm run migrate` | applies 001–006 / "nothing to do" | | `npm run migrate` | applies 001–006 / "nothing to do" |
| `npm test` | **176 passing, 0 failing** (85 unit + 91 integration incl. 1 e2e) | | `npm test` | **178 passing, 0 failing** (85 unit + 93 integration incl. 2 new 10.1 tests) |
| `npm run seed:dev` | creates demo user "demo"/"demo123", hero "Aldric" (warrior), starter gear | | `npm run seed:dev` | creates demo user "demo"/"demo123", hero "Aldric" (warrior), starter gear |
| `npm start` + `curl localhost:3000/api/health` | `{"ok":true}` | | `npm start` + `curl localhost:3000/api/health` | `{"ok":true}` |
@@ -31,10 +31,12 @@
- `public/js/auth.js` — Shared API wrapper + showError. - `public/js/auth.js` — Shared API wrapper + showError.
- `scripts/seed-dev.js` — Creates demo account + hero + starter gear. - `scripts/seed-dev.js` — Creates demo account + hero + starter gear.
- `src/app.js` — Express app with helmet, rate limiters, catch-all 404, 500 HTML serving. - `src/app.js` — Express app with helmet, rate limiters, catch-all 404, 500 HTML serving.
- `src/services/expeditionService.js` — **Fixed in 10.1:** transaction param order corrected `(cid, ne, eat, defid, sat, endAt)`.
- `src/services/*` — All game services. - `src/services/*` — All game services.
- `src/routes/*` — All route handlers. - `src/routes/*` — All route handlers.
- `src/game/*` — Pure game logic. - `src/game/*` — Pure game logic.
- `tests/integration/*.test.js` — 91 integration tests (incl. e2e-playable.test.js). - `tests/integration/expeditions.test.js` — 16 expedition tests (incl. 2 new 10.1 DB-level assertions).
- `tests/integration/*.test.js` — 93 integration tests (incl. 1 e2e).
- `tests/unit/*.test.js` — 85 unit tests. - `tests/unit/*.test.js` — 85 unit tests.
- `README.md` — Updated with real run instructions, play-through steps, project structure, known issues. - `README.md` — Updated with real run instructions, play-through steps, project structure, known issues.
@@ -47,6 +49,7 @@
- Starter gear documented but not auto-created on character creation. - Starter gear documented but not auto-created on character creation.
- `GET /api/zones` documented in API.md but not implemented (Issue #2). - `GET /api/zones` documented in API.md but not implemented (Issue #2).
- Mobile nav uses emoji icons (⚔️ 🏕️ 🎒) — verify they render acceptably on all platforms. - Mobile nav uses emoji icons (⚔️ 🏕️ 🎒) — verify they render acceptably on all platforms.
- **10.2 next:** expedition claim overwrites XP instead of adding to it; needs shared `applyXpGain` function.
## 5. Decisions & deviations from the docs ## 5. Decisions & deviations from the docs
- D-004..D-007 in `docs/DECISIONS.md` (combat rounds, equipment counted once, session store, clock). - D-004..D-007 in `docs/DECISIONS.md` (combat rounds, equipment counted once, session store, clock).
@@ -56,7 +59,6 @@
- Inventory response uses `itemId` (item.id) and `inventoryId` (inventory row id) to avoid confusion. - Inventory response uses `itemId` (item.id) and `inventoryId` (inventory row id) to avoid confusion.
- Consumable effect parsing: regex `^(heal|energy):(\d+)$` on items.effect field. - Consumable effect parsing: regex `^(heal|energy):(\d+)$` on items.effect field.
- Qty decrement avoids CHECK constraint violation: delete directly when qty==1, decrement when qty>1. - Qty decrement avoids CHECK constraint violation: delete directly when qty==1, decrement when qty>1.
- D-008..D-013 from previous steps.
## 6. Gotchas learned ## 6. Gotchas learned
- `npm test` must be `node --test` with NO path/glob; files must be named `*.test.js`. - `npm test` must be `node --test` with NO path/glob; files must be named `*.test.js`.
@@ -65,7 +67,7 @@
- Verify test COUNT, not just "all passing". - Verify test COUNT, not just "all passing".
- Node.js `fetch` doesn't persist cookies across calls — extract `Set-Cookie` header and pass manually in integration tests. - Node.js `fetch` doesn't persist cookies across calls — extract `Set-Cookie` header and pass manually in integration tests.
- SQL `SELECT id` from inventory returns the auto-increment row id; rename with `AS inventory_id` when you also need `item_id`. - SQL `SELECT id` from inventory returns the auto-increment row id; rename with `AS inventory_id` when you also need `item_id`.
- Transaction callback args must match the call order exactly. - Transaction callback args must match the call order exactly. (This is the 10.1 bug — double-check param/call order in every new transaction.)
- Error responses from route-level `AppError`s do NOT include `data` field. - Error responses from route-level `AppError`s do NOT include `data` field.
- `setNow` affects ALL `now()` calls including those in service logic. - `setNow` affects ALL `now()` calls including those in service logic.
- Shop API returns `{ ok: true, data: { stock: [...], character: {...} } }` — field is `stock`, not `stocks`. - Shop API returns `{ ok: true, data: { stock: [...], character: {...} } }` — field is `stock`, not `stocks`.
@@ -86,9 +88,8 @@
- `src/utils/time` exports `{ now, setNow, resetNow }` directly — `const time = require(...)`, not `const { time }`. - `src/utils/time` exports `{ now, setNow, resetNow }` directly — `const time = require(...)`, not `const { time }`.
- Inventory sell endpoint (`POST /api/shop/sell`) only accepts `material`-type items. - Inventory sell endpoint (`POST /api/shop/sell`) only accepts `material`-type items.
- `handleListInventory` in `src/routes/inventory.js` — parameter name must be `res`, not `_res`. - `handleListInventory` in `src/routes/inventory.js` — parameter name must be `res`, not `_res`.
- Character profile response has `energy` at `data.energy.current`, NOT `data.character.energy`.
## 7. Next-step briefing ## 7. Next-step briefing
- **Phase 9 is complete.** All hardening and polish done. - **Next step: 10.2** — Fix expedition CLAIM overwriting XP. The `claimExpedition` transaction uses `updateCharacterXp.run(xp, lvl, pts, cid)` which OVERWRITES xp/level/points instead of ADDING to current values. Needs a shared `applyXpGain` function used by BOTH `huntService` and `expeditionService`.
- **Stretch Goals available:** S1 (PvP duels), S2 (Skills/talents), S3 (Guilds), S4 (Player market), S5 (Daily rewards/quests), S6 (In-game chat), S7 (Achievements). - **Before 10.2:** check `src/services/expeditionService.js` `claimExpedition` transaction — same param-order bug pattern as 10.1 may be present.
- **Or address Known Issues:** Issue #2 (`GET /api/zones` missing — major), Issue #5 (equipment not in combat — minor), Issue #3 (`npm run seed` dead script — cosmetic).
- **Recommended next step:** Fix Known Issue #2 (`GET /api/zones`) since it's a documented API gap and would improve the hunt UI. Then consider S7 (Achievements) as it's a self-contained, low-risk stretch goal that adds meaningful gameplay.
+7 -5
View File
@@ -7,16 +7,16 @@
> - Keep Session Log entries to one or two lines. > - Keep Session Log entries to one or two lines.
## Current Focus ## Current Focus
- **Phase:** 10.1z - **Phase:** 10.1
- **Step:** Stretch Goals - **Step:** 10.2 — Fix expedition CLAIM overwriting XP
- **Blocked?** No - **Blocked?** No
- **Last verified working command:** `npm test` — 176/176 passing (85 unit + 91 integration + 1 e2e), 2026-10-21 step 9.6 - **Last verified working command:** `npm test` — 178/178 passing (85 unit + 93 integration incl. 2 new 10.1 tests), 2026-10-22 step 10.1
## Project Health ## Project Health
| Item | State | | Item | State |
|---|---| |---|---|
| App starts (`npm start`) | ✅ smoke-tested: register → create hero → 12 hunts → level-up (real seeded zones) | | App starts (`npm start`) | ✅ smoke-tested: register → create hero → 12 hunts → level-up (real seeded zones) |
| Tests pass (`npm test`) | ✅ 93/93 (runs EVERY *.test.js; auth/combat/health were previously skipped by a bad glob) | | Tests pass (`npm test`) | ✅ 178/178 (85 unit + 93 integration incl. 2 new 10.1 tests) |
| Migrations run clean on empty DB | ✅ 001_users, 002_characters, 003_content (also seeds zones/monsters/items), 004_inventory, 006_logs (005 reserved for expeditions) | | Migrations run clean on empty DB | ✅ 001_users, 002_characters, 003_content (also seeds zones/monsters/items), 004_inventory, 006_logs (005 reserved for expeditions) |
| Can register + login | ✅ verified by tests | | Can register + login | ✅ verified by tests |
| Data persists across restart | ⚠️ sessions use the real SQLite store (also in tests); a restart test is still missing | | Data persists across restart | ⚠️ sessions use the real SQLite store (also in tests); a restart test is still missing |
@@ -138,8 +138,9 @@
--- ---
### A. Expedition bugs (verified live) ### A. Expedition bugs (verified live)
- [ ] **10.1** Fix expedition START corrupting energy. In `startExpedition` (`src/services/expeditionService.js`) the transaction function's parameters are in a different order than the call, so the DB gets `energy = def.energy_cost` and `energy_updated_at = endsAt`. Fix so the DB stores `energy - cost` and the timestamp returned by `computeEnergy`. - [x] **10.1** Fix expedition START corrupting energy. In `startExpedition` (`src/services/expeditionService.js`) the transaction function's parameters are in a different order than the call, so the DB gets `energy = def.energy_cost` and `energy_updated_at = endsAt`. Fix so the DB stores `energy - cost` and the timestamp returned by `computeEnergy`.
*Acceptance:* test: hero energy 100, start the 10-energy expedition → DB `energy` is 90 and `energy_updated_at` is NOT in the future; `GET /api/character` shows 90; response energy equals DB energy. Test also starting at energy 55 → 45. *Acceptance:* test: hero energy 100, start the 10-energy expedition → DB `energy` is 90 and `energy_updated_at` is NOT in the future; `GET /api/character` shows 90; response energy equals DB energy. Test also starting at energy 55 → 45.
*Verified:* 2 new integration tests (DB-level assertions) + 14 existing expedition tests pass; transaction params `(cid, ne, eat, defid, sat, endAt)` now match call `(characterId, newEnergy, updatedAtMs, defId, startedAt, endsAt)`.
- [ ] **10.2** Fix expedition CLAIM overwriting XP. Claim must ADD the reward to current XP, run `checkLevelUp`, ADD (not overwrite) unspent points, and heal to the new max HP on level-up. Extract ONE shared function (e.g. `applyXpGain` in `src/game/leveling.js` or a small progression service) and use it from BOTH `huntService` and `expeditionService`. - [ ] **10.2** Fix expedition CLAIM overwriting XP. Claim must ADD the reward to current XP, run `checkLevelUp`, ADD (not overwrite) unspent points, and heal to the new max HP on level-up. Extract ONE shared function (e.g. `applyXpGain` in `src/game/leveling.js` or a small progression service) and use it from BOTH `huntService` and `expeditionService`.
*Acceptance:* tests: xp 80 + reward 30 at level 1 → level 2, xp 10, unspent_points +3; xp 0 + 30 → xp 30, level 1; a reward spanning two levels → both levels gained, points +6; double claim still rejected; gold added correctly. Existing hunt tests still pass. *Acceptance:* tests: xp 80 + reward 30 at level 1 → level 2, xp 10, unspent_points +3; xp 0 + 30 → xp 30, level 1; a reward spanning two levels → both levels gained, points +6; double claim still rejected; gold added correctly. Existing hunt tests still pass.
- [ ] **10.3** Make expeditions testable: use `time.now()` and an injectable `rng` (no `Date.now()` / `Math.random()` in `expeditionService.js`). - [ ] **10.3** Make expeditions testable: use `time.now()` and an injectable `rng` (no `Date.now()` / `Math.random()` in `expeditionService.js`).
@@ -227,6 +228,7 @@ Severity: `blocker` / `major` / `minor` / `cosmetic`. Status: `open` / `fixing`
## Session Log ## Session Log
_Format:_ `YYYY-MM-DD | step | what changed | result` _Format:_ `YYYY-MM-DD | step | what changed | result`
- 2026-10-22 | 10.1 | Fixed expedition start energy corruption: transaction params in `startExpedition` were mismatched (DB stored `energy = def.energy_cost` and `energy_updated_at = endsAt`); corrected param order to `(cid, ne, eat, defid, sat, endAt)` matching call `(characterId, newEnergy, updatedAtMs, defId, startedAt, endsAt)`; added 2 DB-level integration tests | 178/178 tests pass
- 2026-10-21 | 9.6 | Rewrote README.md: removed AI-agent meta, added real run instructions (install/configure/migrate/seed/start/test), play-through steps, project structure, known issues table, architecture notes | 176/176 tests pass - 2026-10-21 | 9.6 | Rewrote README.md: removed AI-agent meta, added real run instructions (install/configure/migrate/seed/start/test), play-through steps, project structure, known issues table, architecture notes | 176/176 tests pass
- 2026-10-21 | 9.5 | Created `tests/integration/e2e-playable.test.js` covering full playable flow (register → login → create hero → stats → hunt → level → equip → shop → expedition → leaderboard → logout → login → persistence); fixed `handleListInventory` bug (parameter `_res` vs `res` in `src/routes/inventory.js`); 176/176 tests pass - 2026-10-21 | 9.5 | Created `tests/integration/e2e-playable.test.js` covering full playable flow (register → login → create hero → stats → hunt → level → equip → shop → expedition → leaderboard → logout → login → persistence); fixed `handleListInventory` bug (parameter `_res` vs `res` in `src/routes/inventory.js`); 176/176 tests pass
- 2026-10-21 | 9.4 | Mobile-responsive pass: game.html 3-col grid (stats | action | combat), mobile bottom nav (⚔️ Hunt / 🏕️ Trip / 🎒 Items), desktop tabs hidden < 700px, game-container padding for nav clearance, mobile stat-row stacking, inventory container stacks < 600px; 175/175 tests pass - 2026-10-21 | 9.4 | Mobile-responsive pass: game.html 3-col grid (stats | action | combat), mobile bottom nav (⚔️ Hunt / 🏕️ Trip / 🎒 Items), desktop tabs hidden < 700px, game-container padding for nav clearance, mobile stat-row stacking, inventory container stacks < 600px; 175/175 tests pass
+3 -3
View File
@@ -129,12 +129,12 @@ function startExpedition(characterId, defId) {
const endsAt = startedAt + (def.duration_sec * 1000); const endsAt = startedAt + (def.duration_sec * 1000);
// Execute inside a transaction. // Execute inside a transaction.
const doStart = db.transaction((nc, ne, eat, cid, defid, sat) => { const doStart = db.transaction((cid, ne, eat, defid, sat, endAt) => {
s.updateEnergy.run(ne, eat, cid); s.updateEnergy.run(ne, eat, cid);
s.insertExpedition.run(cid, defid, sat, eat); s.insertExpedition.run(cid, defid, sat, endAt);
}); });
doStart(newEnergy, def.energy_cost, endsAt, characterId, defId, startedAt); doStart(characterId, newEnergy, updatedAtMs, defId, startedAt, endsAt);
// Read back the just-inserted expedition (latest active for this character). // Read back the just-inserted expedition (latest active for this character).
const inserted = s.activeExpedition.get(characterId); const inserted = s.activeExpedition.get(characterId);
+46
View File
@@ -404,4 +404,50 @@ describe('expeditions', () => {
assert.equal(res.status, 401); assert.equal(res.status, 401);
assert.equal(body.ok, false); assert.equal(body.ok, false);
}); });
// ─── 10.1: expedition start must deduct energy correctly ──────────────
it('10.1: starts expedition → DB energy = energy - cost, timestamp NOT in future', async () => {
const { cookie, characterId } = await setupHero({ energy: 100, gold: 500 });
const res = await fetch(`${baseUrl}/api/expeditions/start`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders(cookie) },
body: JSON.stringify({ defId: 1 }), // scout, cost=10
});
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.ok, true);
// Assert DB state directly — this is the core of 10.1.
const db = require('../../src/db').getDb();
const ch = db.prepare('SELECT energy, energy_updated_at FROM characters WHERE id = ?').get(characterId);
assert.equal(ch.energy, 90, 'DB energy must be 100 - 10 = 90');
assert.ok(ch.energy_updated_at <= Date.now(), 'energy_updated_at must NOT be in the future');
// Assert GET /api/character returns the same energy.
const charRes = await fetch(`${baseUrl}/api/character`, {
headers: authHeaders(cookie),
});
assert.equal(charRes.status, 200);
const charBody = await charRes.json();
assert.equal(charBody.data.energy.current, 90);
});
it('10.1: starting at energy 55 → DB energy = 45', async () => {
const { cookie, characterId } = await setupHero({ energy: 55, gold: 500 });
const res = await fetch(`${baseUrl}/api/expeditions/start`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders(cookie) },
body: JSON.stringify({ defId: 1 }), // scout, cost=10
});
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.ok, true);
const db = require('../../src/db').getDb();
const ch = db.prepare('SELECT energy FROM characters WHERE id = ?').get(characterId);
assert.equal(ch.energy, 45, 'DB energy must be 55 - 10 = 45');
});
}); });