diff --git a/README.md b/README.md index a8f1012..8ee1bfc 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ packs or regenerate art on that machine: ```bash npm install # pulls classic-level, used to write the LevelDB packs npm run build # rebuilds packs/ from tools/content.mjs -npm run check # the eight guards; the build refuses to run if they fail +npm run check # the nine guards; the build refuses to run if they fail ``` To update a deployed server: `git pull` and restart Foundry. If the pull touches @@ -157,7 +157,7 @@ icons/ fonts/ art/ generated art packs/ built LevelDB compendia (committed — see Deploying) tools/ content.mjs the catalogue: skills, weapons, armour, gear, vehicles, NPCs - build-packs.mjs builds packs/ — runs the eight guards first and refuses on failure + build-packs.mjs builds packs/ — runs the nine guards first and refuses on failure make-icons.mjs draws all 231 icons rules-text.mjs generates the rules journal FROM rules.mjs mission.mjs the case generator @@ -177,15 +177,15 @@ stops being identity-equal to what it aliases, if a constant is re-declared as a literal, or if an exported rule has no spot-check. The packs used to be built under one set of numbers and played under another; this makes that impossible to ship. -The build runs all eight guards before it writes anything: +The build runs all nine guards before it writes anything: ``` -check-rules: OK — 107 rules, 34 files scanned, 3 aliases + 46 constant sets checked, 381 formulas verified +check-rules: OK — 108 rules, 34 files scanned, 3 aliases + 46 constant sets checked, 387 formulas verified check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resolves, every posting can use what it carries, every loadout distinct check-lang: OK — en.json, 1016 keys, no leaf/branch collisions check-templates: OK — 19 templates compile -check-behaviour: OK — 75 behavioural tests +check-behaviour: OK — 76 behavioural tests check-scenarios: OK — 10 scenario files, 78 mechanic tags, every skill named resolves against 62 catalogue entries check-creatures: OK — 102 actor specs across 8 sources, every characteristic, skill, kit key, talent, species and style resolves, no duplicate keys check-anatomy: OK — 5 body plans, 36 locations, d20 complete in both modes, every location drawn, every kind handled and destructible, 20 plan changes carried 432 wounds with their severity intact, 1 flyer(s) with a survivable landing diff --git a/docs/BESTIARY.md b/docs/BESTIARY.md index 3680cad..cc0a531 100644 --- a/docs/BESTIARY.md +++ b/docs/BESTIARY.md @@ -12,8 +12,8 @@ Sorted by how much of a four-agent party each one puts on the floor, alone. **So | | Creature | Essence | SIZ | Arm | HP | Attack | Down of 4 | |---|---|---|---|---|---|---|---| | | **Quarantine unit** | Sent to contain the same thing you were, and it got here first | 14 | 9 | 17 | Unmaker 75% | 3.71 | +| | **The supporter** | The winged beast off a borough coat of arms, come down off it | 24 | 9 | 30 | Grasping limb 75% | 3.38 | | | **Long walker** | It has been coming here for a very long time and is nearly arrived | 22 | 10 | 24 | Grasping limb 80% | 3.18 | -| | **The supporter** | The winged beast off a borough coat of arms, come down off it | 24 | 7 | 30 | Grasping limb 75% | 2.84 | | | **The stanchion** | Something was driven through the floor here to keep the door from shutting | 21 | 12 | 23 | Grasping limb 45% | 2.29 | | | **Nuckelavee** | It came up out of the firth and everything it breathes on dies | 20 | 5 | 20 | Grasping limb 85% | 1.70 | | | **The arrears** | Something was borrowed from the far side in 1968 and this is the collection | 19 | 7 | 20 | Grasping limb 80% | 1.62 | @@ -72,17 +72,17 @@ Older than the department and largely uninterested in it. Most of these are arra *The winged beast off a borough coat of arms, come down off it* -> **Four-legged, winged** — as the four-legged, plus wings. **Disable either wing and it cannot fly**, which is the fight the players can win. Wings are 6 faces of 20 at range and 2 in melee. +> **Four-legged, winged** — as the four-legged, plus wings. **Disable either wing and it cannot fly**, which is the fight the players can win. Wings are 6 faces of 20 at range and 2 in melee, and carry no hide at all. Once it is grounded the hide halves everywhere except the chest, which it keeps turned towards you. | STR | CON | SIZ | INT | POW | DEX | Armour | HP | Major wound | Locations | |---|---|---|---|---|---|---|---|---|---| -| 26 | 36 | 24 | 8 | 17 | 12 | 7 | 30 | 15 | 7 | +| 26 | 36 | 24 | 8 | 17 | 12 | 9 | 30 | 15 | 7 | **Attacks with** Grasping limb at 75%, 1d6+1 · **dodges** 35% **ARGENT AND GULES.** While it stands on ground the borough holds, it takes half damage from anything the borough owns — which includes the department's vehicles and any weapon signed out of Stores. WING IT. -*Measured alone against the frozen party: 2.84 of 4 down, 46.8% wiped, 14 rounds.* +*Measured alone against the frozen party: 3.38 of 4 down, 69.3% wiped, 17 rounds.* ### Nuckelavee diff --git a/docs/REVIEW_LOG.md b/docs/REVIEW_LOG.md index e50c586..2f6b53a 100644 --- a/docs/REVIEW_LOG.md +++ b/docs/REVIEW_LOG.md @@ -5312,3 +5312,54 @@ It is also deliberately one-sided: it catches grounding being a setback, not gro being too generous. Stripping the hide to zero passes it. That is a balance question and this is a correctness guard, and conflating the two is how the six-point tolerance in check-lethality came to hide a real change for four commits. + +## R-255 — the belly rule was measured against the wrong question, and undersold + +R-254 called +6.4 points "modest on purpose" and I left it there. That was the wrong +reading of my own number. +6.4 measured whether grounding *helps*; the design question was +whether the borough still *matters*, and on that the belly rule had quietly gone too far — +the gap between fighting the supporter on borough ground and off it had fallen from 60.6 +points (1.7.8) to 44.9 (1.7.9). Half-damage from every departmental weapon is supposed to +be the fight's central problem. Compressing the penalty for ignoring it by sixteen points +is not modest, and calling it modest because a different statistic looked small is the +same error as R-251: reporting the measurement I happened to take instead of the one the +decision needed. + +**The fix: hide 9, grounded 4, vital exempt.** Three changes that only work together. + +- Hide 7 → 9. Restores the weight the belly rule removed, on the axis that is supposed to + hurt: being shot at with departmental kit. +- Grounded halves it, as R-254 — 9 becomes 4, still a number an ordinary round beats. +- `exposedHideFor` now takes the location's kind and returns natural armour **unhalved for + `vital`**. A beast on its belly is not presenting its chest to the floor; that plate is + facing you in every posture. Mechanically it keeps one hard target so grounding is a + change of problem, not a solved fight. + +| | flying | grounded | gap to off-borough | +|---|---|---|---| +| 1.7.8 (no belly rule) | 1.4% | — | 60.6 | +| 1.7.9 (hide 7, halved) | 45.3% | 51.7% | 44.9 | +| **1.7.10 (hide 9, grounded 4, vital exempt)** | **28.4%** | **68.4%** | **56.2** | + +Grounding is now worth 40 points and the borough is worth 56 again. Predicted 28.4 / 84.3 +/ 55.9 before building; measured 28.4 / 84.6 / 56.2. + +**Negative test.** Exempting every location rather than only the vital makes check-anatomy +guard 6 fail — "an average round gets 2.30 through it flying and only 0.50 once grounded" — +which is the guard doing its job on the version of this change that would have been wrong. + +**Cost.** The supporter moves from fifth to second deadliest creature in the book: 2.84 of +4 down to 3.38, 46.8% wiped to 69.3%, 14 rounds to 17. check-lethality refused the build +until that row was deliberately re-recorded, which is the behaviour R-251 bought. + +**The doc gap this exposed.** BESTIARY.md printed the new hide of 9 and said nothing about +it halving, because the body-plan blurb was written before the rule existed. A rule that +lives in `rules.mjs` and not on the page the GM reads at the table is the project's +signature defect wearing a different coat. The `winged` note now states both halves: the +wings carry no hide, and grounding halves the rest except the chest. + +**One more count that was written down and never read.** The README's guard block is +generated and validated against `npm run check`, and three sentences around it still said +"the eight guards" while nine ran. `update-readme` now rewrites the count word as well as +the block. The comment warning that a hardcoded list goes stale was sitting four lines +above the prose that had gone stale. diff --git a/packs/armour/000003.log b/packs/armour/000003.log new file mode 100644 index 0000000..9b6d27d Binary files /dev/null and b/packs/armour/000003.log differ diff --git a/packs/armour/000005.ldb b/packs/armour/000005.ldb deleted file mode 100644 index c982555..0000000 Binary files a/packs/armour/000005.ldb and /dev/null differ diff --git a/packs/armour/000006.log b/packs/armour/000006.log deleted file mode 100644 index 1a1bb27..0000000 Binary files a/packs/armour/000006.log and /dev/null differ diff --git a/packs/armour/CURRENT b/packs/armour/CURRENT index cacca75..1a84852 100644 --- a/packs/armour/CURRENT +++ b/packs/armour/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/armour/MANIFEST-000002 b/packs/armour/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/armour/MANIFEST-000002 differ diff --git a/packs/armour/MANIFEST-000004 b/packs/armour/MANIFEST-000004 deleted file mode 100644 index 9c05997..0000000 Binary files a/packs/armour/MANIFEST-000004 and /dev/null differ diff --git a/packs/expedition/000003.log b/packs/expedition/000003.log new file mode 100644 index 0000000..263295b Binary files /dev/null and b/packs/expedition/000003.log differ diff --git a/packs/expedition/000005.ldb b/packs/expedition/000005.ldb deleted file mode 100644 index 4ff460d..0000000 Binary files a/packs/expedition/000005.ldb and /dev/null differ diff --git a/packs/expedition/000006.log b/packs/expedition/000006.log deleted file mode 100644 index 268b187..0000000 Binary files a/packs/expedition/000006.log and /dev/null differ diff --git a/packs/expedition/CURRENT b/packs/expedition/CURRENT index cacca75..1a84852 100644 --- a/packs/expedition/CURRENT +++ b/packs/expedition/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/expedition/MANIFEST-000002 b/packs/expedition/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/expedition/MANIFEST-000002 differ diff --git a/packs/expedition/MANIFEST-000004 b/packs/expedition/MANIFEST-000004 deleted file mode 100644 index 47fbcd4..0000000 Binary files a/packs/expedition/MANIFEST-000004 and /dev/null differ diff --git a/packs/gear/000003.log b/packs/gear/000003.log new file mode 100644 index 0000000..629ba6d Binary files /dev/null and b/packs/gear/000003.log differ diff --git a/packs/gear/000005.ldb b/packs/gear/000005.ldb deleted file mode 100644 index d9bac03..0000000 Binary files a/packs/gear/000005.ldb and /dev/null differ diff --git a/packs/gear/000006.log b/packs/gear/000006.log deleted file mode 100644 index d1deb9b..0000000 Binary files a/packs/gear/000006.log and /dev/null differ diff --git a/packs/gear/CURRENT b/packs/gear/CURRENT index cacca75..1a84852 100644 --- a/packs/gear/CURRENT +++ b/packs/gear/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/gear/MANIFEST-000002 b/packs/gear/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/gear/MANIFEST-000002 differ diff --git a/packs/gear/MANIFEST-000004 b/packs/gear/MANIFEST-000004 deleted file mode 100644 index f7cd73c..0000000 Binary files a/packs/gear/MANIFEST-000004 and /dev/null differ diff --git a/packs/lastadmission/000003.log b/packs/lastadmission/000003.log new file mode 100644 index 0000000..8198bc0 Binary files /dev/null and b/packs/lastadmission/000003.log differ diff --git a/packs/lastadmission/000005.ldb b/packs/lastadmission/000005.ldb deleted file mode 100644 index e5b019a..0000000 Binary files a/packs/lastadmission/000005.ldb and /dev/null differ diff --git a/packs/lastadmission/000006.log b/packs/lastadmission/000006.log deleted file mode 100644 index 79bcc33..0000000 Binary files a/packs/lastadmission/000006.log and /dev/null differ diff --git a/packs/lastadmission/CURRENT b/packs/lastadmission/CURRENT index cacca75..1a84852 100644 --- a/packs/lastadmission/CURRENT +++ b/packs/lastadmission/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/lastadmission/MANIFEST-000002 b/packs/lastadmission/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/lastadmission/MANIFEST-000002 differ diff --git a/packs/lastadmission/MANIFEST-000004 b/packs/lastadmission/MANIFEST-000004 deleted file mode 100644 index 0bf96a0..0000000 Binary files a/packs/lastadmission/MANIFEST-000004 and /dev/null differ diff --git a/packs/npcs/000003.log b/packs/npcs/000003.log new file mode 100644 index 0000000..748b78a Binary files /dev/null and b/packs/npcs/000003.log differ diff --git a/packs/npcs/000005.ldb b/packs/npcs/000005.ldb deleted file mode 100644 index 566f8de..0000000 Binary files a/packs/npcs/000005.ldb and /dev/null differ diff --git a/packs/npcs/000006.log b/packs/npcs/000006.log deleted file mode 100644 index aa1c847..0000000 Binary files a/packs/npcs/000006.log and /dev/null differ diff --git a/packs/npcs/CURRENT b/packs/npcs/CURRENT index cacca75..1a84852 100644 --- a/packs/npcs/CURRENT +++ b/packs/npcs/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/npcs/MANIFEST-000002 b/packs/npcs/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/npcs/MANIFEST-000002 differ diff --git a/packs/npcs/MANIFEST-000004 b/packs/npcs/MANIFEST-000004 deleted file mode 100644 index 5e31df4..0000000 Binary files a/packs/npcs/MANIFEST-000004 and /dev/null differ diff --git a/packs/powers/000003.log b/packs/powers/000003.log new file mode 100644 index 0000000..d723043 Binary files /dev/null and b/packs/powers/000003.log differ diff --git a/packs/powers/000005.ldb b/packs/powers/000005.ldb deleted file mode 100644 index 7f540c6..0000000 Binary files a/packs/powers/000005.ldb and /dev/null differ diff --git a/packs/powers/000006.log b/packs/powers/000006.log deleted file mode 100644 index 43e306f..0000000 Binary files a/packs/powers/000006.log and /dev/null differ diff --git a/packs/powers/CURRENT b/packs/powers/CURRENT index cacca75..1a84852 100644 --- a/packs/powers/CURRENT +++ b/packs/powers/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/powers/MANIFEST-000002 b/packs/powers/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/powers/MANIFEST-000002 differ diff --git a/packs/powers/MANIFEST-000004 b/packs/powers/MANIFEST-000004 deleted file mode 100644 index 20de082..0000000 Binary files a/packs/powers/MANIFEST-000004 and /dev/null differ diff --git a/packs/pregens/000003.log b/packs/pregens/000003.log new file mode 100644 index 0000000..4639dc5 Binary files /dev/null and b/packs/pregens/000003.log differ diff --git a/packs/pregens/000005.ldb b/packs/pregens/000005.ldb deleted file mode 100644 index 6957180..0000000 Binary files a/packs/pregens/000005.ldb and /dev/null differ diff --git a/packs/pregens/000006.log b/packs/pregens/000006.log deleted file mode 100644 index 01cc480..0000000 Binary files a/packs/pregens/000006.log and /dev/null differ diff --git a/packs/pregens/CURRENT b/packs/pregens/CURRENT index cacca75..1a84852 100644 --- a/packs/pregens/CURRENT +++ b/packs/pregens/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/pregens/MANIFEST-000002 b/packs/pregens/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/pregens/MANIFEST-000002 differ diff --git a/packs/pregens/MANIFEST-000004 b/packs/pregens/MANIFEST-000004 deleted file mode 100644 index dc2195a..0000000 Binary files a/packs/pregens/MANIFEST-000004 and /dev/null differ diff --git a/packs/primer/000003.log b/packs/primer/000003.log new file mode 100644 index 0000000..4a43c24 Binary files /dev/null and b/packs/primer/000003.log differ diff --git a/packs/primer/000005.ldb b/packs/primer/000005.ldb deleted file mode 100644 index 75e5695..0000000 Binary files a/packs/primer/000005.ldb and /dev/null differ diff --git a/packs/primer/000006.log b/packs/primer/000006.log deleted file mode 100644 index bb91941..0000000 Binary files a/packs/primer/000006.log and /dev/null differ diff --git a/packs/primer/CURRENT b/packs/primer/CURRENT index cacca75..1a84852 100644 --- a/packs/primer/CURRENT +++ b/packs/primer/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/primer/MANIFEST-000002 b/packs/primer/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/primer/MANIFEST-000002 differ diff --git a/packs/primer/MANIFEST-000004 b/packs/primer/MANIFEST-000004 deleted file mode 100644 index 7f1100d..0000000 Binary files a/packs/primer/MANIFEST-000004 and /dev/null differ diff --git a/packs/prologue/000003.log b/packs/prologue/000003.log new file mode 100644 index 0000000..3b576db Binary files /dev/null and b/packs/prologue/000003.log differ diff --git a/packs/prologue/000005.ldb b/packs/prologue/000005.ldb deleted file mode 100644 index 88477dc..0000000 Binary files a/packs/prologue/000005.ldb and /dev/null differ diff --git a/packs/prologue/000006.log b/packs/prologue/000006.log deleted file mode 100644 index 199b3af..0000000 Binary files a/packs/prologue/000006.log and /dev/null differ diff --git a/packs/prologue/CURRENT b/packs/prologue/CURRENT index cacca75..1a84852 100644 --- a/packs/prologue/CURRENT +++ b/packs/prologue/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/prologue/MANIFEST-000002 b/packs/prologue/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/prologue/MANIFEST-000002 differ diff --git a/packs/prologue/MANIFEST-000004 b/packs/prologue/MANIFEST-000004 deleted file mode 100644 index 57a863b..0000000 Binary files a/packs/prologue/MANIFEST-000004 and /dev/null differ diff --git a/packs/rules/000003.log b/packs/rules/000003.log new file mode 100644 index 0000000..602aa25 Binary files /dev/null and b/packs/rules/000003.log differ diff --git a/packs/rules/000005.ldb b/packs/rules/000005.ldb deleted file mode 100644 index b4e6a9a..0000000 Binary files a/packs/rules/000005.ldb and /dev/null differ diff --git a/packs/rules/000006.log b/packs/rules/000006.log deleted file mode 100644 index e5ce67c..0000000 Binary files a/packs/rules/000006.log and /dev/null differ diff --git a/packs/rules/CURRENT b/packs/rules/CURRENT index cacca75..1a84852 100644 --- a/packs/rules/CURRENT +++ b/packs/rules/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/rules/MANIFEST-000002 b/packs/rules/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/rules/MANIFEST-000002 differ diff --git a/packs/rules/MANIFEST-000004 b/packs/rules/MANIFEST-000004 deleted file mode 100644 index bbb24de..0000000 Binary files a/packs/rules/MANIFEST-000004 and /dev/null differ diff --git a/packs/skills/000003.log b/packs/skills/000003.log new file mode 100644 index 0000000..d985ef5 Binary files /dev/null and b/packs/skills/000003.log differ diff --git a/packs/skills/000005.ldb b/packs/skills/000005.ldb deleted file mode 100644 index 2ac258b..0000000 Binary files a/packs/skills/000005.ldb and /dev/null differ diff --git a/packs/skills/000006.log b/packs/skills/000006.log deleted file mode 100644 index 3318040..0000000 Binary files a/packs/skills/000006.log and /dev/null differ diff --git a/packs/skills/CURRENT b/packs/skills/CURRENT index cacca75..1a84852 100644 --- a/packs/skills/CURRENT +++ b/packs/skills/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/skills/MANIFEST-000002 b/packs/skills/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/skills/MANIFEST-000002 differ diff --git a/packs/skills/MANIFEST-000004 b/packs/skills/MANIFEST-000004 deleted file mode 100644 index 39883ac..0000000 Binary files a/packs/skills/MANIFEST-000004 and /dev/null differ diff --git a/packs/slice/000003.log b/packs/slice/000003.log new file mode 100644 index 0000000..39926e8 Binary files /dev/null and b/packs/slice/000003.log differ diff --git a/packs/slice/000005.ldb b/packs/slice/000005.ldb deleted file mode 100644 index ac4df01..0000000 Binary files a/packs/slice/000005.ldb and /dev/null differ diff --git a/packs/slice/000006.log b/packs/slice/000006.log deleted file mode 100644 index c25ac75..0000000 Binary files a/packs/slice/000006.log and /dev/null differ diff --git a/packs/slice/CURRENT b/packs/slice/CURRENT index cacca75..1a84852 100644 --- a/packs/slice/CURRENT +++ b/packs/slice/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/slice/MANIFEST-000002 b/packs/slice/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/slice/MANIFEST-000002 differ diff --git a/packs/slice/MANIFEST-000004 b/packs/slice/MANIFEST-000004 deleted file mode 100644 index 0966a50..0000000 Binary files a/packs/slice/MANIFEST-000004 and /dev/null differ diff --git a/packs/starter/000003.log b/packs/starter/000003.log new file mode 100644 index 0000000..df928ff Binary files /dev/null and b/packs/starter/000003.log differ diff --git a/packs/starter/000005.ldb b/packs/starter/000005.ldb deleted file mode 100644 index 368bf7e..0000000 Binary files a/packs/starter/000005.ldb and /dev/null differ diff --git a/packs/starter/000006.log b/packs/starter/000006.log deleted file mode 100644 index a884f4d..0000000 Binary files a/packs/starter/000006.log and /dev/null differ diff --git a/packs/starter/CURRENT b/packs/starter/CURRENT index cacca75..1a84852 100644 --- a/packs/starter/CURRENT +++ b/packs/starter/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/starter/MANIFEST-000002 b/packs/starter/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/starter/MANIFEST-000002 differ diff --git a/packs/starter/MANIFEST-000004 b/packs/starter/MANIFEST-000004 deleted file mode 100644 index 5cdb872..0000000 Binary files a/packs/starter/MANIFEST-000004 and /dev/null differ diff --git a/packs/tables/000003.log b/packs/tables/000003.log new file mode 100644 index 0000000..f125b7d Binary files /dev/null and b/packs/tables/000003.log differ diff --git a/packs/tables/000005.ldb b/packs/tables/000005.ldb deleted file mode 100644 index 64baa1c..0000000 Binary files a/packs/tables/000005.ldb and /dev/null differ diff --git a/packs/tables/000006.log b/packs/tables/000006.log deleted file mode 100644 index d5e06f3..0000000 Binary files a/packs/tables/000006.log and /dev/null differ diff --git a/packs/tables/CURRENT b/packs/tables/CURRENT index cacca75..1a84852 100644 --- a/packs/tables/CURRENT +++ b/packs/tables/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/tables/MANIFEST-000002 b/packs/tables/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/tables/MANIFEST-000002 differ diff --git a/packs/tables/MANIFEST-000004 b/packs/tables/MANIFEST-000004 deleted file mode 100644 index 004ff30..0000000 Binary files a/packs/tables/MANIFEST-000004 and /dev/null differ diff --git a/packs/talents/000003.log b/packs/talents/000003.log new file mode 100644 index 0000000..6257c26 Binary files /dev/null and b/packs/talents/000003.log differ diff --git a/packs/talents/000005.ldb b/packs/talents/000005.ldb deleted file mode 100644 index 4ac65dd..0000000 Binary files a/packs/talents/000005.ldb and /dev/null differ diff --git a/packs/talents/000006.log b/packs/talents/000006.log deleted file mode 100644 index b484ea0..0000000 Binary files a/packs/talents/000006.log and /dev/null differ diff --git a/packs/talents/CURRENT b/packs/talents/CURRENT index cacca75..1a84852 100644 --- a/packs/talents/CURRENT +++ b/packs/talents/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/talents/MANIFEST-000002 b/packs/talents/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/talents/MANIFEST-000002 differ diff --git a/packs/talents/MANIFEST-000004 b/packs/talents/MANIFEST-000004 deleted file mode 100644 index 4c77b74..0000000 Binary files a/packs/talents/MANIFEST-000004 and /dev/null differ diff --git a/packs/throughtrain/000003.log b/packs/throughtrain/000003.log new file mode 100644 index 0000000..868c3dd Binary files /dev/null and b/packs/throughtrain/000003.log differ diff --git a/packs/throughtrain/000005.ldb b/packs/throughtrain/000005.ldb deleted file mode 100644 index 72544b1..0000000 Binary files a/packs/throughtrain/000005.ldb and /dev/null differ diff --git a/packs/throughtrain/000006.log b/packs/throughtrain/000006.log deleted file mode 100644 index 643165a..0000000 Binary files a/packs/throughtrain/000006.log and /dev/null differ diff --git a/packs/throughtrain/CURRENT b/packs/throughtrain/CURRENT index cacca75..1a84852 100644 --- a/packs/throughtrain/CURRENT +++ b/packs/throughtrain/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/throughtrain/MANIFEST-000002 b/packs/throughtrain/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/throughtrain/MANIFEST-000002 differ diff --git a/packs/throughtrain/MANIFEST-000004 b/packs/throughtrain/MANIFEST-000004 deleted file mode 100644 index 923360d..0000000 Binary files a/packs/throughtrain/MANIFEST-000004 and /dev/null differ diff --git a/packs/vehicles/000003.log b/packs/vehicles/000003.log new file mode 100644 index 0000000..9950ce5 Binary files /dev/null and b/packs/vehicles/000003.log differ diff --git a/packs/vehicles/000005.ldb b/packs/vehicles/000005.ldb deleted file mode 100644 index f6b2cd4..0000000 Binary files a/packs/vehicles/000005.ldb and /dev/null differ diff --git a/packs/vehicles/000006.log b/packs/vehicles/000006.log deleted file mode 100644 index 47800e2..0000000 Binary files a/packs/vehicles/000006.log and /dev/null differ diff --git a/packs/vehicles/CURRENT b/packs/vehicles/CURRENT index cacca75..1a84852 100644 --- a/packs/vehicles/CURRENT +++ b/packs/vehicles/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/vehicles/MANIFEST-000002 b/packs/vehicles/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/vehicles/MANIFEST-000002 differ diff --git a/packs/vehicles/MANIFEST-000004 b/packs/vehicles/MANIFEST-000004 deleted file mode 100644 index d4d73b7..0000000 Binary files a/packs/vehicles/MANIFEST-000004 and /dev/null differ diff --git a/packs/weapons/000003.log b/packs/weapons/000003.log new file mode 100644 index 0000000..e20483e Binary files /dev/null and b/packs/weapons/000003.log differ diff --git a/packs/weapons/000005.ldb b/packs/weapons/000005.ldb deleted file mode 100644 index cb5fdfb..0000000 Binary files a/packs/weapons/000005.ldb and /dev/null differ diff --git a/packs/weapons/000006.log b/packs/weapons/000006.log deleted file mode 100644 index a7be3fe..0000000 Binary files a/packs/weapons/000006.log and /dev/null differ diff --git a/packs/weapons/CURRENT b/packs/weapons/CURRENT index cacca75..1a84852 100644 --- a/packs/weapons/CURRENT +++ b/packs/weapons/CURRENT @@ -1 +1 @@ -MANIFEST-000004 +MANIFEST-000002 diff --git a/packs/weapons/MANIFEST-000002 b/packs/weapons/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/weapons/MANIFEST-000002 differ diff --git a/packs/weapons/MANIFEST-000004 b/packs/weapons/MANIFEST-000004 deleted file mode 100644 index 6a582af..0000000 Binary files a/packs/weapons/MANIFEST-000004 and /dev/null differ diff --git a/ringbrp.mjs b/ringbrp.mjs index c985f9f..1e8db71 100644 --- a/ringbrp.mjs +++ b/ringbrp.mjs @@ -578,8 +578,7 @@ class RingBRPActor extends Actor { // --- Armour: what is worn, and what each piece actually reaches --- // Every piece used to add to every location, so a Brodie helmet protected your // legs. Pieces now declare coverage and the total is computed per location. - // Down on its belly, half the hide is no longer between you and the animal. - const naturalArmour = exposedHideFor(sys.naturalArmour?.value, sys.grounded); + const naturalArmour = Number(sys.naturalArmour?.value) || 0; // A shield is armour you HOLD: its points count only while it is raised and only // while it is still a shield. Lowered or broken it is weight and nothing else. const wornArmour = this.items.filter(i => i.type === "armour" && i.system.equipped) @@ -619,8 +618,10 @@ class RingBRPActor extends Actor { ranged: `${loc.ranged[0]}${loc.ranged[1] > loc.ranged[0] ? `-${loc.ranged[1]}` : ""}`, max, taken: t, remaining: Math.max(0, max - t), // The body's own armour here, plus every worn piece that reaches this kind. - // Hide stops at the edge of a wing; worn barding does not. - armour: locationArmourFor((Number(loc.armour) || 0) + naturalArmourOn(loc, naturalArmour), + // Hide stops at the edge of a wing, and halves on a grounded creature's belly + // — but never on its vital. Worn barding is unaffected by either. + armour: locationArmourFor((Number(loc.armour) || 0) + + naturalArmourOn(loc, exposedHideFor(naturalArmour, sys.grounded, loc.kind)), wornArmour, loc.kind), armourFrom: wornArmour.filter(p => armourCoversKind(p.coverage, loc.kind)) .map(p => ({ name: p.name, points: p.points })), @@ -1112,7 +1113,8 @@ export async function hitLocationRoll({ target, damage, mode = "ranged", const locArmour = live ? Number(live.armour) || 0 : locationArmourFor((Number(loc.armour) || 0) - + naturalArmourOn(loc, exposedHideFor(sys.naturalArmour?.value, sys.grounded)), + + naturalArmourOn(loc, + exposedHideFor(sys.naturalArmour?.value, sys.grounded, loc.kind)), sys.wornArmour ?? [], loc.kind); effective = Math.max(0, effective - armourAgainst(level, locArmour)); } diff --git a/rules.mjs b/rules.mjs index 6575ee3..c9b0110 100644 --- a/rules.mjs +++ b/rules.mjs @@ -613,8 +613,15 @@ export function groundedPlanFor(speciesId = "baseline") { * it. That restores something an ordinary round can get through, which is the thing * grounding took away, and it does it without pretending a grounded animal is harmless. */ -export function exposedHideFor(naturalArmour = 0, grounded = false) { +export function exposedHideFor(naturalArmour = 0, grounded = false, kind = "") { const a = Number(naturalArmour) || 0; + // The vital is the one plate that does not care what posture it is in. A heraldic + // beast on its belly has its flanks and its legs against the ground and its chest + // still facing you, and exempting it is what keeps the fight on borough ground a + // losing proposition: halving EVERYTHING took that fight from 22% to 47% winnable + // and closed a third of the gap between fighting it on its own ground and getting + // it off. With the chest still armoured it sits at 28%. + if (kind === "vital") return a; return grounded ? Math.floor(a / 2) : a; } diff --git a/system.json b/system.json index be34d5b..3c31e19 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "ringbrp", "title": "The Custodians", "description": "A Basic Roleplaying game of agents who cross the crossing to contain what should not be there — and who come back a little less from here each time.", - "version": "1.7.9", + "version": "1.7.10", "compatibility": { "minimum": 13, "verified": "14.364" diff --git a/tools/bestiary.mjs b/tools/bestiary.mjs index 14aa5df..bce967b 100644 --- a/tools/bestiary.mjs +++ b/tools/bestiary.mjs @@ -76,7 +76,7 @@ const SPECIES_NOTE = { vesh: "**Vesh** — no head, so no killing shot; the sensory ridge is the vital and is hit on 3 of 20.", cadence: "**Cadence** — five bodies, no vital, no head. Cannot be dropped; each body lost costs it −10% to everything.", quadruped: "**Four-legged** — four legs, no arms. The forequarters are the vital and take the most hits at range; the neck is worth aiming at and the head is hard to reach (1 face in melee).", - winged: "**Four-legged, winged** — as the four-legged, plus wings. **Disable either wing and it cannot fly**, which is the fight the players can win. Wings are 6 faces of 20 at range and 2 in melee.", + winged: "**Four-legged, winged** — as the four-legged, plus wings. **Disable either wing and it cannot fly**, which is the fight the players can win. Wings are 6 faces of 20 at range and 2 in melee, and carry no hide at all. Once it is grounded the hide halves everywhere except the chest, which it keeps turned towards you.", baseline: null }; diff --git a/tools/check-anatomy.mjs b/tools/check-anatomy.mjs index bfa4853..0ebd62b 100644 --- a/tools/check-anatomy.mjs +++ b/tools/check-anatomy.mjs @@ -288,7 +288,7 @@ for (const [id, table] of Object.entries(LOCATION_TABLES)) { through a bare wing than through a halved hide; a 9-point round does far less. The first version of this check measured only at 4 and duly reported that the fix had failed. Small arms prefer it airborne; anything heavier wants it down. */ - const HIDE = 7, RANGE = 12; + const HIDE = 9, RANGE = 12; const through = (planId, grounded) => { const t = LOCATION_TABLES[planId]; let total = 0; @@ -296,7 +296,8 @@ for (const [id, table] of Object.entries(LOCATION_TABLES)) { for (let face = 1; face <= 20; face++) { const loc = t.locations.find(l => face >= l.ranged[0] && face <= l.ranged[1]); if (!loc) continue; - total += Math.max(0, shot - naturalArmourOn(loc, exposedHideFor(HIDE, grounded))); + total += Math.max(0, shot + - naturalArmourOn(loc, exposedHideFor(HIDE, grounded, loc.kind))); } } return total / (20 * RANGE); diff --git a/tools/check-behaviour.mjs b/tools/check-behaviour.mjs index 5eff34f..958df6f 100644 --- a/tools/check-behaviour.mjs +++ b/tools/check-behaviour.mjs @@ -693,6 +693,51 @@ test("an opened body cavity bleeds, because that is what its text promises", () assert.equal(e.physical, -30); }); +/* R-255. The hide rules are three functions that only mean anything composed, and the + composition lives in prepareDerivedData where no test could reach it. So compose them + here the same way the sheet does — from the real supporter spec and the real tables — + because a hide that halves correctly in `rules.mjs` and arrives wrong on the sheet is + this project's signature defect, and check-anatomy's guard 6 is deliberately one-sided + and would not catch the vital losing its exemption. */ +test("R-255 the supporter's hide reaches each location at the right value, flying and grounded", async () => { + const { NPCS } = await import("./content.mjs"); + const spec = NPCS.find(n => n.key === "supporter"); + assert.ok(spec, "there is no supporter in the bestiary any more"); + const hide = spec.naturalArmour; + + // Exactly the expression at ringbrp.mjs:624, with no worn armour on the beast. + const onSheet = (loc, grounded) => + RULES.locationArmourFor((Number(loc.armour) || 0) + + RULES.naturalArmourOn(loc, RULES.exposedHideFor(hide, grounded, loc.kind)), + [], loc.kind); + + const flying = TABLES[spec.bodyPlan]; + for (const loc of flying.locations) { + const got = onSheet(loc, false); + if (loc.kind === "wing") assert.equal(got, 0, `a wing carries no hide, and ${loc.id} shows ${got}`); + else assert.equal(got, hide, `${loc.id} should show the full hide of ${hide} in the air, not ${got}`); + } + + // Grounded, it is a different body plan: the one groundedPlanFor names. + const landed = TABLES[RULES.groundedPlanFor(spec.bodyPlan)]; + assert.ok(landed, "the winged plan no longer declares where it lands"); + let softened = 0; + for (const loc of landed.locations) { + const got = onSheet(loc, true); + if (loc.kind === "vital") { + assert.equal(got, hide, + `the chest faces you in every posture; it should still show ${hide} grounded, not ${got}`); + } else { + assert.equal(got, Math.floor(hide / 2), + `${loc.id} should halve to ${Math.floor(hide / 2)} on the ground, not ${got}`); + softened++; + } + } + assert.ok(softened >= 4, "grounding softened almost nothing, so it buys the players nothing"); + assert.ok(Math.floor(hide / 2) < 5, + `grounded hide is ${Math.floor(hide / 2)}, which an ordinary round still cannot beat`); +}); + /* ---------------------------------------------------------------- */ await runAll(); diff --git a/tools/check-rules.mjs b/tools/check-rules.mjs index 91c26d7..9588c5e 100644 --- a/tools/check-rules.mjs +++ b/tools/check-rules.mjs @@ -550,10 +550,12 @@ const checks = [ RULES.naturalArmourOn(null, 7), 7], // On its belly, half the hide is no longer in the way — which is what gives an // ordinary round somewhere to land once grounding takes the bare wings off the table. - ["exposedHideFor(flying) is the whole hide", RULES.exposedHideFor(7, false), 7], - ["exposedHideFor(grounded) is half of it", RULES.exposedHideFor(7, true), 3], - ["exposedHideFor(grounded, thin hide)", RULES.exposedHideFor(1, true), 0], - ["exposedHideFor(no hide at all)", RULES.exposedHideFor(0, true), 0], + ["exposedHideFor(flying) is the whole hide", RULES.exposedHideFor(9, false, "leg"), 9], + ["exposedHideFor(grounded) is half of it", RULES.exposedHideFor(9, true, "leg"), 4], + ["exposedHideFor(grounded VITAL) is exempt", RULES.exposedHideFor(9, true, "vital"), 9], + ["exposedHideFor(flying vital) is unchanged", RULES.exposedHideFor(9, false, "vital"), 9], + ["exposedHideFor(grounded, thin hide)", RULES.exposedHideFor(1, true, "leg"), 0], + ["exposedHideFor(no hide at all)", RULES.exposedHideFor(0, true, "leg"), 0], // A deletion and an assignment of the same key do not both apply: the deletion wins. // quadruped and winged share every location but the wings, so this is where a wound // was silently thrown away instead of carried. diff --git a/tools/content.mjs b/tools/content.mjs index 7a51cfd..489b4e5 100644 --- a/tools/content.mjs +++ b/tools/content.mjs @@ -809,12 +809,13 @@ export const NPCS = [ tactics: "It does not leave the ground it was put in and it does not sleep. Inside its ground it cannot be surprised and does not need to roll to notice anything. POWER: THE GROUND IS MINE. Any agent who digs, opens, or removes something from its ground takes 1d6 with no roll to avoid and no armour, every round they continue. It has no interest in anybody who leaves." }, { key: "supporter", name: "The supporter", role: "FOLKLORE. The winged beast off a borough coat of arms, come down off it", - chosenStyle: "reflex", naturalArmour: 7, species: "baseline", bodyPlan: "winged", - // Six hundred years of being drawn accurately makes a thing hard to kill, so the - // hide goes up and the constitution with it; the wings are `bare` in anatomy.mjs - // and carry none of it. Measured: grounding it goes from 9% of fights to 67%, the - // borough-ground fight stays a losing one (22%), and the fight off borough ground - // keeps its teeth at 84% with a 17% wipe. See docs/REVIEW_LOG.md. + chosenStyle: "reflex", naturalArmour: 9, species: "baseline", bodyPlan: "winged", + // Six hundred years of being drawn accurately makes a thing hard to kill. The hide + // is 9 and the wings carry none of it (`bare`, anatomy.mjs); on the ground it halves + // to 4 everywhere except the chest, which faces you whatever posture it is in. + // Measured over 20,000 fights: grounding it happens in 67% and is worth having, + // the borough-ground fight stays a losing one at 28%, and off borough ground it is + // 84% with real casualties. See R-254 and R-255 in docs/REVIEW_LOG.md. ch: { str: 26, siz: 24, con: 36, int: 8, pow: 17, dex: 12, cha: 14, edu: 2 }, skills: [{fam:"brawl",spec:"",val:75},{fam:"dodge",spec:"",val:35},{fam:"spot",spec:"",val:70}, {fam:"athletics",spec:"",val:70},{fam:"insight",spec:"",val:45},{fam:"track",spec:"",val:55}], diff --git a/tools/lethality-baseline.json b/tools/lethality-baseline.json index 180c44c..3613ff0 100644 --- a/tools/lethality-baseline.json +++ b/tools/lethality-baseline.json @@ -66,9 +66,9 @@ "rounds": 4 }, "supporter": { - "wipe": 46.8, - "down": 2.84, - "rounds": 14 + "wipe": 69.3, + "down": 3.38, + "rounds": 17 }, "hollow_man": { "wipe": 0, diff --git a/tools/update-readme.mjs b/tools/update-readme.mjs index 2730be8..8450f0e 100644 --- a/tools/update-readme.mjs +++ b/tools/update-readme.mjs @@ -91,6 +91,16 @@ const withGuards = replaceRegion(withBox, "guards", "```\n" + guardLines.join("\n") + "\n```"); if (!withGuards) { console.error("update-readme: guard markers missing"); process.exit(1); } -await writeFile(readme, withGuards, "utf8"); +/* The guard BLOCK was generated and the sentences introducing it were not, so the README + listed nine guards while three places in the prose still said eight. Count words go + stale the same way a hardcoded list does — the lesson two comments up, one layer out. */ +const COUNT_WORD = ["zero", "one", "two", "three", "four", "five", "six", "seven", + "eight", "nine", "ten", "eleven", "twelve"]; +const word = COUNT_WORD[guardLines.length]; +if (!word) { console.error(`update-readme: ${guardLines.length} guards has no count word`); process.exit(1); } +const counted = withGuards.replace(/\b(zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve) guards\b/g, + `${word} guards`); + +await writeFile(readme, counted, "utf8"); console.log(`update-readme: OK — ${items} items, ${Object.keys(ROLES).length} postings, ` + `${Object.keys(TRADES).length} trades, ${guardLines.length} guard lines`);