Files
Emberfall/HANDOFF.md
T
2026-10-23 05:54:46 +11:00

92 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HANDOFF.md — Session Handoff (OVERWRITE at the end of every phase or when context is getting long)
> **Read order for a new session:** `NOTES.md` → `STATUS.md` → this file → then work.
> If this file disagrees with the code, trust the code and fix this file.
## 1. Where we are
- **Last completed step:** 10.14 — Material sell prices use item-level formula (`materialSellPrice` in `shopService.js`)
- **Next step:** Phase 10.15 — Non-stackable items must be one inventory row per unit
- **Updated:** 2026-10-23
- **Was the last session cut off mid-task?** No
## 2. Verified state (run these first)
| Command | Expected |
|---|---|
| `npm install` | succeeds |
| `npm run migrate` | applies 001–007 / "nothing to do" |
| `npm test` | **260 passing, 0 failing** (97 unit + 163 integration incl. 7 material sell price tests) |
| `npm run seed:dev` | creates demo user "demo"/"demo123", hero "Aldric" (warrior), starter gear |
| `npm start` + `curl localhost:3000/` | 200 HTML landing page |
## 3. What exists now (files that matter)
- `src/game/rewards.js` — `materialSellPrice(rarity, level)` — item-level formula: `multiplier[rarity] * level`
- `src/services/shopService.js` — `getStock()` and `sellItem()` now use `materialSellPrice`; `deleteRowExact` added for CHECK constraint fix
- `tests/integration/step-10.14.test.js` — NEW: 7 integration tests (Rat Tail at 3 levels, Drake Scale at 2 levels, shop-listed = actual, multi-quantity sell)
- `docs/BALANCE.md` — Material sell prices corrected in table to match formula
## 4. Half-finished / fragile things
- No leaderboard UI yet (stub page exists; real UI in 10.17).
- No restart-persistence test for sessions.
- `npm run seed` script in package.json points to nonexistent `scripts/seed.js`.
- `GET /api/zones` documented in API.md but not implemented.
- Mobile nav uses emoji icons (⚔️ 🏕️ 🎒) — verify they render acceptably on all platforms.
- Characters start with 50g default (DB constraint) — tests must account for this baseline.
## 5. Decisions & deviations from the docs
- D-004..D-007, D-022 in `docs/DECISIONS.md` (combat rounds, equipment counted once, session store, clock, material sell prices)
- **D-014:** Mobile bottom nav uses emoji icons (⚔️ Hunt / 🏕️ Trip / 🎒 Items) instead of full text labels.
- **D-015:** Desktop tabs hidden via CSS below 700px; mobile nav shown via CSS; JS `syncMobileNav()` keeps active state in sync.
- **D-016:** `applyXpGain` in `src/game/leveling.js` is the single shared function for XP gain logic, used by both `huntService` and `expeditionService`.
- **D-017:** Landing page `public/index.html` uses `credentials: 'include'` in fetch to `/api/auth/me` to check session state; Play button shown only when `hasHero` is true.
- **D-018:** Link-checker test skips API endpoints (`/api/...`) since they're not static files.
- **D-019:** Rate limiters use `process.env.NODE_ENV === 'test' ? 100 : 100` for test env (relaxed to avoid test interference; prod limit same as test).
- **D-020:** `allocatePoints` in `leveling.js` accepts non-negative integers (0 allowed for unallocated stats).
- **D-021:** Allocation UI uses `container.replaceChildren()` instead of `innerHTML = ''` to avoid innerHTML entirely.
## 6. Gotchas learned
- `npm test` must be `node --test` with NO path/glob; files must be named `*.test.js`.
- better-sqlite3-session-store ignores `expired.clear: false`; that's why the timer is unref'd.
- Statement caches are per-DB-handle (WeakMap) in huntService; copy that pattern for new services.
- 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.
- 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.
- Error responses from route-level `AppError`s do NOT include `data` field.
- `setNow` affects ALL `now()` calls including those in service logic.
- Shop API returns `{ ok: true, data: { stock: [...], character: {...} } }` — field is `stock`, not `stocks`.
- Shop stock items have flat fields (`itemId`, `buyPrice`), not nested (`item.id`).
- New characters have starter gear (10.13) — tests must account for 2 baseline inventory rows.
- Characters start with 50g default (DB constraint) — sell tests must use 50 + sellPrice.
- `GET /api/zones` documented in API.md but not implemented.
- Hero profile endpoint is PUBLIC — no `requireAuth` middleware.
- Profile page is public: no auth.js guard needed; uses ?name= query param for lookup.
- Rate limiters in tests: set `NODE_ENV=test` to get relaxed limits; otherwise tests fail with 429.
- `seed-dev.js` must check `NODE_ENV=production` before any `require()` that loads config.
- Express `sendFile` rejects `../` path segments — use `path.resolve()` + `path.join()`.
- `express.static` sends its own 404 responses; catch-all must be placed BEFORE static middleware.
- Character profile GET returns `{ ok: true, data: { character, derived, energy } }`.
- Register returns 201 (Created), not 200.
- Character creation returns 201 (Created), not 200.
- Character creation response: `{ ok: true, data: { character: { id, name, class } } }`.
- Expedition start uses `defId` in request body.
- Expedition claim uses `expeditionId` in request body.
- `src/utils/time` exports `{ now, setNow, resetNow }` — `const time = require(...)`.
- Inventory sell endpoint only accepts `material`-type items.
- `handleListInventory` in `src/routes/inventory.js` — parameter name must be `res`.
- Character profile response has `energy` at `data.energy.current`.
- **10.3:** `expeditionService.js` import changed to `const time = require(...)` — destructured `now` was shadowed.
- **10.3:** `_opts` cannot be sent via HTTP; test rng via direct service call.
- **10.4:** `req.session.username` must be set in both `handleRegister` and `handleLogin`.
- **10.6:** Rate limiters add `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers.
- **10.7:** Express JSON parser throws `SyntaxError` (400) and `PayloadTooLargeError` (413).
- **10.7:** `req.path.startsWith('/api/')` is more reliable than `req.accepts('html')`.
- **10.7:** Session store callbacks must use `next(err)` not `throw`.
- **10.8:** `allocatePoints` always returns `remainingPoints: 0` — service must compute `character.unspent_points - total`.
- **10.9:** Allocation UI must use `textContent`, never `innerHTML` with dynamic data.
- **10.10:** `setupHero` helper must use `.then()` chain.
- **10.12:** Warrior maxHp at level 1 = 135 (50 + 8*10 + 1*5).
- **10.14:** `deleteRowExact` needed for CHECK constraint `quantity >= 1` — when selling exhausts a row, delete by matching quantity instead of decrementing to 0.
## 7. Next-step briefing
- **Next step: 10.15** — Non-stackable items: buying N of a non-stackable item should create N inventory rows with quantity 1 each (instead of 1 row with quantity N). Stackable items keep merging. Extract a shared grant helper if huntService has one; reuse it in buyItem.