step 10.15: done

This commit is contained in:
bepis
2026-10-23 06:16:38 +11:00
parent 2a79c4ea43
commit 81794c7dbd
7 changed files with 240 additions and 34 deletions
+7 -5
View File
@@ -8,9 +8,9 @@
## Current Focus
- **Phase:** 10
- **Step:** 10.15 — Non-stackable items must be one inventory row per unit
- **Step:** 10.16 — (next)
- **Blocked?** No
- **Last verified working command:** `npm test` — 260/260 passing (97 unit + 163 integration incl. 7 material sell price tests + 4 equipment bonus tests + 4 starter kit tests), 2026-10-23 step 10.14
- **Last verified working command:** `npm test` — 264/264 passing (97 unit + 167 integration incl. 4 non-stackable buy tests), 2026-10-23 step 10.15
## Project Health
| Item | State |
@@ -184,8 +184,9 @@
- [x] **10.14** Fix material sell prices. The code uses `defaultRate * heroLevel / itemMinLevel`; BALANCE.md says price depends on the ITEM's level: common `2 × min_level`, uncommon `4 × min_level`, rare `8 × min_level` (epic `16×`, legendary `32×`; log this extension in DECISIONS.md), minimum 1. Note BALANCE.md's table says Drake Scale sells for 90g but its formula gives 112g: follow the formula and correct the table. Use ONE function (`materialSellPrice` in `src/game/rewards.js`) for the shop list AND for selling.
*Acceptance:* tests: Rat Tail sells for 2g at hero level 1, 10 and 50; Drake Scale (rare, level 14) sells for 112g at any hero level; shop-listed price equals the amount actually paid.
*Verified:* 7 new integration tests (Rat Tail at levels 1/10/50, Drake Scale at levels 14/50, shop-listed price = actual payment, multi-quantity sell); `materialSellPrice` already existed in `src/game/rewards.js` — integrated into `shopService.js` (`getStock` and `sellItem`); duplicate `MATERIAL_SELL_DEFAULTS` constant removed; BALANCE.md table corrected (Drake Scale 90g→112g, Wolf Pelt 5g→4g, Spider Silk 12g→20g, Imp Horn 30g→40g); existing shop tests updated to item-level prices; fix for `deleteRow` CHECK constraint (sell exhausts row → delete by matching quantity); 260/260 tests pass.
- [ ] **10.15** Non-stackable items must be one inventory row per unit. In `buyItem`, buying N of a non-stackable item inserts N rows with quantity 1 (reuse the same grant helper that `huntService` uses; extract it to a shared place if needed). Stackable items keep merging.
- [x] **10.15** Non-stackable items must be one inventory row per unit. In `buyItem`, buying N of a non-stackable item inserts N rows with quantity 1 (reuse the same grant helper that `huntService` uses; extract it to a shared place if needed). Stackable items keep merging.
*Acceptance:* tests: buy 3 swords → 3 rows, quantity 1 each; equip one, sell one → remaining state correct; buy 5 potions → one row, quantity 5.
*Verified:* extracted shared `grantItem()` to `src/game/inventory.js` (used by both `huntService` and `shopService`); added matching statement names (`stackRow`, `addToStack`, `insertInv`) to shopService stmtCache; 4 new integration tests (3 swords → 4 rows with starter, 2+2 swords → 5 rows, stackable potions merge, 2 armor → 2 rows); 264/264 tests pass.
### F. Public pages
- [ ] **10.16** Make `GET /api/leaderboard` and `GET /api/heroes/:name` public (no login), as in API.md, and keep returning only public fields. Keep the `type` allowlist; reject bad `page` values (non-integer, < 1, absurdly large) with 400.
*Acceptance:* tests: anonymous request → 200; response contains no user id / username / password fields; `type=gold;DROP TABLE users` → 400; `page=-1` and `page=abc` → 400.
@@ -236,11 +237,12 @@ Severity: `blocker` / `major` / `minor` / `cosmetic`. Status: `open` / `fixing`
## Test Summary
| Suite | Passing | Failing | Last run |
|---|---|---|---|
| unit | 85 | 0 | 2026-10-23 step 10.8 |
| integration | 175 | 0 | 2026-10-23 step 10.14 (7 material sell price tests) |
| unit | 97 | 0 | 2026-10-23 step 10.15 |
| integration | 167 | 0 | 2026-10-23 step 10.15 (4 non-stackable buy tests) |
## Session Log
_Format:_ `YYYY-MM-DD | step | what changed | result`
- 2026-10-23 | 10.15 | Extracted shared `grantItem()` to `src/game/inventory.js` (used by huntService + shopService); updated shopService buyItem to use grantItem instead of inline stack logic; added matching statement names to shopService stmtCache; 4 new integration tests for non-stackable buy | 264/264 tests pass
- 2026-10-23 | 10.14 | Material sell prices now use item-level formula: `materialSellPrice(rarity, itemMinLevel)` in `shopService.js` replaces old `defaultRate * heroLevel / itemMinLevel`; BALANCE.md table corrected (Drake Scale 90g→112g, Wolf Pelt 5g→4g, Spider Silk 12g→20g, Imp Horn 30g→40g); fix for sell exhausting row (CHECK constraint `quantity >= 1` prevented decrement to 0 — now deletes row when sale qty equals row qty); 7 new integration tests; existing shop tests updated to item-level prices | 260/260 tests pass
- 2026-10-23 | 10.8 | Added `POST /api/character/allocate` endpoint: zod `.strictObject` with optional stat fields + `.refine` requiring at least one; service in `characterService.js` validates input, uses `allocatePoints` from `leveling.js`, computes `remainingPoints = character.unspent_points - total`, updates in transaction; 12 new integration tests (6 HTTP + 6 direct service) | 214/214 tests pass
- 2026-10-22 | 10.6 | Re-enabled real rate limiters in `src/routes/auth.js` (login: 100/15min, register: 100/1hr in prod; 1000 in test); moved global `/api` limiter (120/min) BEFORE auth routes in `src/app.js`; added 4 integration tests verifying limiter headers and 429 response shape | 197/197 tests pass