loop: baseline
This commit is contained in:
+2
-2
@@ -27,7 +27,7 @@ On level-up: `hp_current = maxHp`, loop to handle multiple level-ups from one re
|
||||
|
||||
## Derived stats
|
||||
```
|
||||
maxHp = 50 + vit*10 + level*5 + itemBonusVit*10
|
||||
maxHp = 50 + totalVit*10 + level*5 // totalVit = base vit + equipment vit (counted ONCE)
|
||||
primary = STR | INT | AGI (by class)
|
||||
attack = 5 + primary*2 + level + equipment.bonus_attack
|
||||
defense = floor(vit*1.5) + equipment.bonus_defense (+10% if Warrior)
|
||||
@@ -35,7 +35,7 @@ speed = 10 + agi
|
||||
critChance% = min(40, 5 + floor(agi*0.5)) (+8 if Ranger)
|
||||
critMult = 1.5
|
||||
```
|
||||
Equipment `bonus_str/agi/int/vit` are added to the base stats BEFORE the derived formulas.
|
||||
Equipment `bonus_str/agi/int/vit` are added to the base stats BEFORE the derived formulas, and nowhere else (never count an equipment bonus twice).
|
||||
|
||||
## Damage
|
||||
```
|
||||
|
||||
+18
-1
@@ -31,4 +31,21 @@ Format:
|
||||
- Decision: Static HTML/CSS/JS served by Express.
|
||||
- Consequences: Slightly more DOM boilerplate; far fewer moving parts.
|
||||
|
||||
<!-- Add D-004 onward below -->
|
||||
## D-004: Both combatants attack every round
|
||||
- Date: 2026-10-20 (review)
|
||||
- Context: Combat treated "higher speed acts first" as "only the faster side acts", so the slower side never attacked.
|
||||
- Decision: Each round, both attack; faster first; hero wins speed ties; a kill ends the round.
|
||||
- Consequences: Round log has up to 2 entries per round (max 40). Win rates need re-tuning (see STATUS issue 1).
|
||||
|
||||
## D-005: Equipment stats are counted once in derived stats
|
||||
- Context: maxHp added equipment VIT twice (once in totalVit, once as a separate term).
|
||||
- Decision: `calcDerived` in `src/game/stats.js` is the ONLY derived-stat formula; combat and services call it.
|
||||
|
||||
## D-006: Real SQLite session store everywhere; unref its cleanup timer
|
||||
- Context: better-sqlite3-session-store ignores `expired.clear:false` (`clear || true`), so its setInterval kept tests alive; the project had used a memory store in tests, so the real store was never exercised.
|
||||
- Decision: Wrap store creation so its interval is unref'd. Tests now use the real store.
|
||||
|
||||
## D-007: `src/utils/time.js` is the only clock
|
||||
- Decision: Services call `time.now()`; tests use `time.setNow()`. Game functions take `nowMs`/`rng` as parameters.
|
||||
|
||||
<!-- Add D-008 onward below -->
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ Each level grants **3 unspent stat points** (player allocates via API).
|
||||
- Derived stats formulas: see BALANCE.md §Derived.
|
||||
|
||||
## Combat (auto-resolved, server-side, turn based)
|
||||
- Hero vs one monster, alternating attacks. Higher `speed` acts first each round.
|
||||
- Hero vs one monster. **Each round BOTH sides attack once**; the higher `speed` side attacks first (hero wins ties). If the first attacker kills the target, the second does not act.
|
||||
- Max 20 rounds; if no one dies, the one with higher HP% wins.
|
||||
- Each hit: damage formula in BALANCE.md, small random variance, crit chance.
|
||||
- Output a **round-by-round log** (array of `{round, actor, damage, crit, targetHpAfter}`) for the UI to replay.
|
||||
|
||||
+6
-1
@@ -1,7 +1,9 @@
|
||||
# TESTING.md
|
||||
|
||||
Runner: built-in `node:test` + `node:assert/strict`. No Jest/Mocha.
|
||||
Script: `"test": "node --test tests/"` (add `--test-concurrency=1` if DB tests collide).
|
||||
Script: `"test": "NODE_ENV=test node --test --test-concurrency=1"`.
|
||||
**Do NOT pass a directory or an unquoted glob** (`node --test tests/` finds nothing on Node 21+; `tests/**/*.test.js` is expanded by `sh` and skips top-level files). Plain `node --test` auto-discovers every `*.test.js`.
|
||||
Test files MUST be named `*.test.js`.
|
||||
|
||||
## Layout
|
||||
- `tests/unit/` — pure functions in `src/game` and `src/utils`. No DB, no HTTP.
|
||||
@@ -37,6 +39,9 @@ Inject time: tests set `time.setNow(fn)` to fast-forward energy/expeditions inst
|
||||
`describe('energy.computeEnergy', () => { it('caps at max', ...) })`.
|
||||
|
||||
## Rules
|
||||
- Run `npm test` and check the TOTAL against the sum of tests in all `*.test.js` files. A bad glob once silently skipped 17 tests.
|
||||
- Integration tests use `setup()` from `tests/helpers.js`, which clears every `src/` module from the require cache so each test gets a fresh app and DB. Never hand-pick modules to clear.
|
||||
- Force game outcomes with test-only zones/monsters and the `{ rng, now }` options of services — don't rely on luck, and don't weaken assertions to "winner is hero or monster".
|
||||
- A failing test is never deleted to make the suite green. Fix the code or log a Known Issue.
|
||||
- New bug → write the failing test first, then fix.
|
||||
- Update the Test Summary table in STATUS.md after each phase.
|
||||
|
||||
Reference in New Issue
Block a user