diff --git a/README.md b/README.md index 472ce6d..956f839 100644 --- a/README.md +++ b/README.md @@ -180,9 +180,9 @@ The build runs all five guards before it writes anything: ``` -check-rules: OK — 92 rules, 21 files scanned, 3 aliases + 45 constant sets checked, 291 formulas verified +check-rules: OK — 96 rules, 21 files scanned, 3 aliases + 46 constant sets checked, 306 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, 981 keys, no leaf/branch collisions -check-behaviour: OK — 59 behavioural tests +check-lang: OK — en.json, 995 keys, no leaf/branch collisions +check-behaviour: OK — 67 behavioural tests ``` diff --git a/lang/en.json b/lang/en.json index c289746..8907e2a 100644 --- a/lang/en.json +++ b/lang/en.json @@ -581,6 +581,8 @@ "RINGBRP.Heal.Fumbled": "First Aid fumbled — the patient loses a further hit point.", "RINGBRP.Exp.Title": "End of case — improvement", "RINGBRP.Exp.NoGain": "no gain", + "RINGBRP.Exp.Capped": "held at its ceiling", + "RINGBRP.Exp.AtCeiling": "already as far as training takes it", "RINGBRP.Exp.NoTicks": "No skill earned a tick this case.", "RINGBRP.Flavour.Gear": "{item}: +{mod}%", "RINGBRP.Target.Title": "Target", @@ -979,5 +981,17 @@ "RINGBRP.Panic.SceneEnd": "Scene over", "RINGBRP.Panic.SceneEndNote": "Panic clears when the scene does. Coherence does not.", "RINGBRP.Party.Title": "The team, right now", - "RINGBRP.Party.Who": "Agent" + "RINGBRP.Party.Who": "Agent", + "RINGBRP.Dev.Title": "Development", + "RINGBRP.Dev.SettingName": "Development phase open", + "RINGBRP.Dev.SettingHint": "While open, every agent's sheet offers a Development roll for the skills that failed them this case.", + "RINGBRP.Dev.Opened": "The case is closed. Roll for the skills that beat you.", + "RINGBRP.Dev.Closed": "The development phase is closed.", + "RINGBRP.Dev.Marked": "skills marked", + "RINGBRP.Dev.Toggle": "Development phase", + "RINGBRP.Dev.PressIt": "Each agent presses Development on their own sheet.", + "RINGBRP.Dev.GMOnly": "Only the GM can open the development phase.", + "RINGBRP.Dev.Button": "Development", + "RINGBRP.Dev.NotOpen": "The GM has not opened the development phase.", + "RINGBRP.Dev.Tooltip": "Roll to improve the skills that failed you this case. A skill improves if the roll is ANOTHER failure." } diff --git a/packs/armour/LOG b/packs/armour/LOG index c67ae16..3669d22 100644 --- a/packs/armour/LOG +++ b/packs/armour/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.799186 176b97000 Recovering log #3 -2026/08/18-22:37:32.799323 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.799504 176b97000 Level-0 table #5: 4591 bytes OK -2026/08/18-22:37:32.799868 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.799916 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.522827 16edc3000 Recovering log #3 +2026/08/18-23:21:42.522857 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.523016 16edc3000 Level-0 table #5: 4591 bytes OK +2026/08/18-23:21:42.523288 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.523332 16edc3000 Delete type=3 #2 diff --git a/packs/armour/LOG.old b/packs/armour/LOG.old index 6aab54a..f1db83c 100644 --- a/packs/armour/LOG.old +++ b/packs/armour/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.356757 172877000 Delete type=3 #1 +2026/08/18-23:21:17.000759 16ec7f000 Delete type=3 #1 diff --git a/packs/expedition/LOG b/packs/expedition/LOG index 6f43f8f..331cc1f 100644 --- a/packs/expedition/LOG +++ b/packs/expedition/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.827220 176b97000 Recovering log #3 -2026/08/18-22:37:32.827303 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.827422 176b97000 Level-0 table #5: 4129 bytes OK -2026/08/18-22:37:32.827654 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.827715 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.542861 1705e7000 Recovering log #3 +2026/08/18-23:21:42.542881 1705e7000 Level-0 table #5: started +2026/08/18-23:21:42.543000 1705e7000 Level-0 table #5: 4129 bytes OK +2026/08/18-23:21:42.543233 1705e7000 Delete type=0 #3 +2026/08/18-23:21:42.543262 1705e7000 Delete type=3 #2 diff --git a/packs/expedition/LOG.old b/packs/expedition/LOG.old index e962a5f..c6c19fb 100644 --- a/packs/expedition/LOG.old +++ b/packs/expedition/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.433657 17388f000 Delete type=3 #1 +2026/08/18-23:21:17.085840 1704a3000 Delete type=3 #1 diff --git a/packs/gear/LOG b/packs/gear/LOG index 52c8408..3289fc0 100644 --- a/packs/gear/LOG +++ b/packs/gear/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.801021 17638b000 Recovering log #3 -2026/08/18-22:37:32.801454 17638b000 Level-0 table #5: started -2026/08/18-22:37:32.801772 17638b000 Level-0 table #5: 31614 bytes OK -2026/08/18-22:37:32.802069 17638b000 Delete type=0 #3 -2026/08/18-22:37:32.802111 17638b000 Delete type=3 #2 +2026/08/18-23:21:42.523809 16edc3000 Recovering log #3 +2026/08/18-23:21:42.523911 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.524193 16edc3000 Level-0 table #5: 31614 bytes OK +2026/08/18-23:21:42.524392 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.524442 16edc3000 Delete type=3 #2 diff --git a/packs/gear/LOG.old b/packs/gear/LOG.old index 7584902..4e62250 100644 --- a/packs/gear/LOG.old +++ b/packs/gear/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.358422 17409b000 Delete type=3 #1 +2026/08/18-23:21:17.002659 1704a3000 Delete type=3 #1 diff --git a/packs/lastadmission/LOG b/packs/lastadmission/LOG index 7bdf5d6..92ad3e4 100644 --- a/packs/lastadmission/LOG +++ b/packs/lastadmission/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.818774 176b97000 Recovering log #3 -2026/08/18-22:37:32.819665 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.820075 176b97000 Level-0 table #5: 46857 bytes OK -2026/08/18-22:37:32.820335 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.820381 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.537220 1705e7000 Recovering log #3 +2026/08/18-23:21:42.537462 1705e7000 Level-0 table #5: started +2026/08/18-23:21:42.537820 1705e7000 Level-0 table #5: 46857 bytes OK +2026/08/18-23:21:42.538102 1705e7000 Delete type=0 #3 +2026/08/18-23:21:42.538144 1705e7000 Delete type=3 #2 diff --git a/packs/lastadmission/LOG.old b/packs/lastadmission/LOG.old index c5694a1..5549886 100644 --- a/packs/lastadmission/LOG.old +++ b/packs/lastadmission/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.417310 172877000 Delete type=3 #1 +2026/08/18-23:21:17.066464 16ec7f000 Delete type=3 #1 diff --git a/packs/npcs/LOG b/packs/npcs/LOG index e85dc95..f37bb6d 100644 --- a/packs/npcs/LOG +++ b/packs/npcs/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.784037 17638b000 Recovering log #3 -2026/08/18-22:37:32.786085 17638b000 Level-0 table #5: started -2026/08/18-22:37:32.787900 17638b000 Level-0 table #5: 234672 bytes OK -2026/08/18-22:37:32.788307 17638b000 Delete type=0 #3 -2026/08/18-22:37:32.788379 17638b000 Delete type=3 #2 +2026/08/18-23:21:42.511991 16edc3000 Recovering log #3 +2026/08/18-23:21:42.512828 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.514313 16edc3000 Level-0 table #5: 234672 bytes OK +2026/08/18-23:21:42.514681 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.514785 16edc3000 Delete type=3 #2 diff --git a/packs/npcs/LOG.old b/packs/npcs/LOG.old index e9a811c..65c1374 100644 --- a/packs/npcs/LOG.old +++ b/packs/npcs/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.394128 172877000 Delete type=3 #1 +2026/08/18-23:21:17.040817 1704a3000 Delete type=3 #1 diff --git a/packs/powers/LOG b/packs/powers/LOG index 601fd23..3a1ae77 100644 --- a/packs/powers/LOG +++ b/packs/powers/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.795533 176b97000 Recovering log #3 -2026/08/18-22:37:32.795934 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.796065 176b97000 Level-0 table #5: 3295 bytes OK -2026/08/18-22:37:32.796355 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.796392 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.520180 16edc3000 Recovering log #3 +2026/08/18-23:21:42.520206 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.520334 16edc3000 Level-0 table #5: 3295 bytes OK +2026/08/18-23:21:42.520647 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.520681 16edc3000 Delete type=3 #2 diff --git a/packs/powers/LOG.old b/packs/powers/LOG.old index a2f69d8..23535aa 100644 --- a/packs/powers/LOG.old +++ b/packs/powers/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.428065 173083000 Delete type=3 #1 +2026/08/18-23:21:17.079222 16f48b000 Delete type=3 #1 diff --git a/packs/pregens/LOG b/packs/pregens/LOG index 6d74ddc..e268a49 100644 --- a/packs/pregens/LOG +++ b/packs/pregens/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.768723 176b97000 Recovering log #3 -2026/08/18-22:37:32.772081 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.777725 176b97000 Level-0 table #5: 560581 bytes OK -2026/08/18-22:37:32.778401 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.778494 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.501500 16edc3000 Recovering log #3 +2026/08/18-23:21:42.503444 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.506554 16edc3000 Level-0 table #5: 560581 bytes OK +2026/08/18-23:21:42.506965 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.507130 16edc3000 Delete type=3 #2 diff --git a/packs/pregens/LOG.old b/packs/pregens/LOG.old index 5de7279..8b53a27 100644 --- a/packs/pregens/LOG.old +++ b/packs/pregens/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.379031 173083000 Delete type=3 #1 +2026/08/18-23:21:17.025927 16fc97000 Delete type=3 #1 diff --git a/packs/primer/000005.ldb b/packs/primer/000005.ldb index 6abe3a0..75e5695 100644 Binary files a/packs/primer/000005.ldb and b/packs/primer/000005.ldb differ diff --git a/packs/primer/000006.log b/packs/primer/000006.log index 554a3a3..bb91941 100644 Binary files a/packs/primer/000006.log and b/packs/primer/000006.log differ diff --git a/packs/primer/LOG b/packs/primer/LOG index b5f686c..f6a0730 100644 --- a/packs/primer/LOG +++ b/packs/primer/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.804709 175b7f000 Recovering log #3 -2026/08/18-22:37:32.804956 175b7f000 Level-0 table #5: started -2026/08/18-22:37:32.805208 175b7f000 Level-0 table #5: 23087 bytes OK -2026/08/18-22:37:32.805486 175b7f000 Delete type=0 #3 -2026/08/18-22:37:32.805525 175b7f000 Delete type=3 #2 +2026/08/18-23:21:42.526413 16edc3000 Recovering log #3 +2026/08/18-23:21:42.526467 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.526727 16edc3000 Level-0 table #5: 24730 bytes OK +2026/08/18-23:21:42.526971 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.526999 16edc3000 Delete type=3 #2 diff --git a/packs/primer/LOG.old b/packs/primer/LOG.old index eed8a7c..5835654 100644 --- a/packs/primer/LOG.old +++ b/packs/primer/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.424767 17409b000 Delete type=3 #1 +2026/08/18-23:21:17.075510 16fc97000 Delete type=3 #1 diff --git a/packs/primer/MANIFEST-000004 b/packs/primer/MANIFEST-000004 index 757bfe9..7f1100d 100644 Binary files a/packs/primer/MANIFEST-000004 and b/packs/primer/MANIFEST-000004 differ diff --git a/packs/prologue/LOG b/packs/prologue/LOG index 741965e..412ad02 100644 --- a/packs/prologue/LOG +++ b/packs/prologue/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.821353 176b97000 Recovering log #3 -2026/08/18-22:37:32.821969 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.822328 176b97000 Level-0 table #5: 44887 bytes OK -2026/08/18-22:37:32.822647 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.822686 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.538839 16f5cf000 Recovering log #3 +2026/08/18-23:21:42.539012 16f5cf000 Level-0 table #5: started +2026/08/18-23:21:42.539381 16f5cf000 Level-0 table #5: 44887 bytes OK +2026/08/18-23:21:42.539604 16f5cf000 Delete type=0 #3 +2026/08/18-23:21:42.539638 16f5cf000 Delete type=3 #2 diff --git a/packs/prologue/LOG.old b/packs/prologue/LOG.old index 148d211..36808ba 100644 --- a/packs/prologue/LOG.old +++ b/packs/prologue/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.421322 17409b000 Delete type=3 #1 +2026/08/18-23:21:17.071063 16ec7f000 Delete type=3 #1 diff --git a/packs/rules/000005.ldb b/packs/rules/000005.ldb index 1e29a85..b4e6a9a 100644 Binary files a/packs/rules/000005.ldb and b/packs/rules/000005.ldb differ diff --git a/packs/rules/000006.log b/packs/rules/000006.log index 6821460..e5ce67c 100644 Binary files a/packs/rules/000006.log and b/packs/rules/000006.log differ diff --git a/packs/rules/LOG b/packs/rules/LOG index 6436c28..e9bdaeb 100644 --- a/packs/rules/LOG +++ b/packs/rules/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.806477 1773a3000 Recovering log #3 -2026/08/18-22:37:32.807016 1773a3000 Level-0 table #5: started -2026/08/18-22:37:32.807385 1773a3000 Level-0 table #5: 36532 bytes OK -2026/08/18-22:37:32.807654 1773a3000 Delete type=0 #3 -2026/08/18-22:37:32.807690 1773a3000 Delete type=3 #2 +2026/08/18-23:21:42.527792 16f5cf000 Recovering log #3 +2026/08/18-23:21:42.527874 16f5cf000 Level-0 table #5: started +2026/08/18-23:21:42.528257 16f5cf000 Level-0 table #5: 37376 bytes OK +2026/08/18-23:21:42.528527 16f5cf000 Delete type=0 #3 +2026/08/18-23:21:42.528572 16f5cf000 Delete type=3 #2 diff --git a/packs/rules/LOG.old b/packs/rules/LOG.old index 0899e5e..fcea83e 100644 --- a/packs/rules/LOG.old +++ b/packs/rules/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.432064 172877000 Delete type=3 #1 +2026/08/18-23:21:17.083980 16fc97000 Delete type=3 #1 diff --git a/packs/rules/MANIFEST-000004 b/packs/rules/MANIFEST-000004 index 3c4122b..bbb24de 100644 Binary files a/packs/rules/MANIFEST-000004 and b/packs/rules/MANIFEST-000004 differ diff --git a/packs/skills/LOG b/packs/skills/LOG index a8b3d1c..5ecdd3f 100644 --- a/packs/skills/LOG +++ b/packs/skills/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.791576 1773a3000 Recovering log #3 -2026/08/18-22:37:32.791785 1773a3000 Level-0 table #5: started -2026/08/18-22:37:32.792018 1773a3000 Level-0 table #5: 7806 bytes OK -2026/08/18-22:37:32.792443 1773a3000 Delete type=0 #3 -2026/08/18-22:37:32.792496 1773a3000 Delete type=3 #2 +2026/08/18-23:21:42.517325 16edc3000 Recovering log #3 +2026/08/18-23:21:42.517371 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.517580 16edc3000 Level-0 table #5: 7806 bytes OK +2026/08/18-23:21:42.518061 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.518138 16edc3000 Delete type=3 #2 diff --git a/packs/skills/LOG.old b/packs/skills/LOG.old index 98526f0..3b01202 100644 --- a/packs/skills/LOG.old +++ b/packs/skills/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.351270 17409b000 Delete type=3 #1 +2026/08/18-23:21:16.994201 1704a3000 Delete type=3 #1 diff --git a/packs/slice/LOG b/packs/slice/LOG index 3a1b405..ecfa214 100644 --- a/packs/slice/LOG +++ b/packs/slice/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.823569 176b97000 Recovering log #3 -2026/08/18-22:37:32.823738 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.824004 176b97000 Level-0 table #5: 16902 bytes OK -2026/08/18-22:37:32.824268 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.824310 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.540416 16fddb000 Recovering log #3 +2026/08/18-23:21:42.540459 16fddb000 Level-0 table #5: started +2026/08/18-23:21:42.540720 16fddb000 Level-0 table #5: 16902 bytes OK +2026/08/18-23:21:42.540972 16fddb000 Delete type=0 #3 +2026/08/18-23:21:42.541026 16fddb000 Delete type=3 #2 diff --git a/packs/slice/LOG.old b/packs/slice/LOG.old index 38a1310..c64b8c7 100644 --- a/packs/slice/LOG.old +++ b/packs/slice/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.426452 173083000 Delete type=3 #1 +2026/08/18-23:21:17.077585 16fc97000 Delete type=3 #1 diff --git a/packs/starter/LOG b/packs/starter/LOG index 053b782..4ea1137 100644 --- a/packs/starter/LOG +++ b/packs/starter/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.815371 176b97000 Recovering log #3 -2026/08/18-22:37:32.816691 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.817466 176b97000 Level-0 table #5: 84039 bytes OK -2026/08/18-22:37:32.817750 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.817802 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.534997 16edc3000 Recovering log #3 +2026/08/18-23:21:42.535341 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.535924 16edc3000 Level-0 table #5: 84039 bytes OK +2026/08/18-23:21:42.536290 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.536355 16edc3000 Delete type=3 #2 diff --git a/packs/starter/LOG.old b/packs/starter/LOG.old index 148f8f8..f520bc2 100644 --- a/packs/starter/LOG.old +++ b/packs/starter/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.404367 173083000 Delete type=3 #1 +2026/08/18-23:21:17.052004 1704a3000 Delete type=3 #1 diff --git a/packs/tables/LOG b/packs/tables/LOG index 8874da8..4158d77 100644 --- a/packs/tables/LOG +++ b/packs/tables/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.825592 175b7f000 Recovering log #3 -2026/08/18-22:37:32.825723 175b7f000 Level-0 table #5: started -2026/08/18-22:37:32.825837 175b7f000 Level-0 table #5: 3739 bytes OK -2026/08/18-22:37:32.826141 175b7f000 Delete type=0 #3 -2026/08/18-22:37:32.826181 175b7f000 Delete type=3 #2 +2026/08/18-23:21:42.541794 1705e7000 Recovering log #3 +2026/08/18-23:21:42.541832 1705e7000 Level-0 table #5: started +2026/08/18-23:21:42.541980 1705e7000 Level-0 table #5: 3739 bytes OK +2026/08/18-23:21:42.542243 1705e7000 Delete type=0 #3 +2026/08/18-23:21:42.542288 1705e7000 Delete type=3 #2 diff --git a/packs/tables/LOG.old b/packs/tables/LOG.old index e6c428a..786f74a 100644 --- a/packs/tables/LOG.old +++ b/packs/tables/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.398341 17409b000 Delete type=3 #1 +2026/08/18-23:21:17.045501 1704a3000 Delete type=3 #1 diff --git a/packs/talents/LOG b/packs/talents/LOG index b71da07..2ab73b9 100644 --- a/packs/talents/LOG +++ b/packs/talents/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.793721 17638b000 Recovering log #3 -2026/08/18-22:37:32.793870 17638b000 Level-0 table #5: started -2026/08/18-22:37:32.794081 17638b000 Level-0 table #5: 5770 bytes OK -2026/08/18-22:37:32.794365 17638b000 Delete type=0 #3 -2026/08/18-22:37:32.794405 17638b000 Delete type=3 #2 +2026/08/18-23:21:42.519065 16edc3000 Recovering log #3 +2026/08/18-23:21:42.519098 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.519276 16edc3000 Level-0 table #5: 5770 bytes OK +2026/08/18-23:21:42.519549 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.519584 16edc3000 Delete type=3 #2 diff --git a/packs/talents/LOG.old b/packs/talents/LOG.old index eee745f..ff775fa 100644 --- a/packs/talents/LOG.old +++ b/packs/talents/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.353747 17388f000 Delete type=3 #1 +2026/08/18-23:21:16.997485 1704a3000 Delete type=3 #1 diff --git a/packs/throughtrain/LOG b/packs/throughtrain/LOG index dbcbab7..9db2725 100644 --- a/packs/throughtrain/LOG +++ b/packs/throughtrain/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.810562 176b97000 Recovering log #3 -2026/08/18-22:37:32.811570 176b97000 Level-0 table #5: started -2026/08/18-22:37:32.812165 176b97000 Level-0 table #5: 68636 bytes OK -2026/08/18-22:37:32.812502 176b97000 Delete type=0 #3 -2026/08/18-22:37:32.812546 176b97000 Delete type=3 #2 +2026/08/18-23:21:42.531147 1705e7000 Recovering log #3 +2026/08/18-23:21:42.531387 1705e7000 Level-0 table #5: started +2026/08/18-23:21:42.531924 1705e7000 Level-0 table #5: 68636 bytes OK +2026/08/18-23:21:42.532247 1705e7000 Delete type=0 #3 +2026/08/18-23:21:42.532303 1705e7000 Delete type=3 #2 diff --git a/packs/throughtrain/LOG.old b/packs/throughtrain/LOG.old index 5a2454d..de05636 100644 --- a/packs/throughtrain/LOG.old +++ b/packs/throughtrain/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.412095 17409b000 Delete type=3 #1 +2026/08/18-23:21:17.060194 16f48b000 Delete type=3 #1 diff --git a/packs/vehicles/LOG b/packs/vehicles/LOG index a5b12a5..309684b 100644 --- a/packs/vehicles/LOG +++ b/packs/vehicles/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.803216 1773a3000 Recovering log #3 -2026/08/18-22:37:32.803361 1773a3000 Level-0 table #5: started -2026/08/18-22:37:32.803524 1773a3000 Level-0 table #5: 6939 bytes OK -2026/08/18-22:37:32.803783 1773a3000 Delete type=0 #3 -2026/08/18-22:37:32.803833 1773a3000 Delete type=3 #2 +2026/08/18-23:21:42.525170 16edc3000 Recovering log #3 +2026/08/18-23:21:42.525197 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.525391 16edc3000 Level-0 table #5: 6939 bytes OK +2026/08/18-23:21:42.525699 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.525727 16edc3000 Delete type=3 #2 diff --git a/packs/vehicles/LOG.old b/packs/vehicles/LOG.old index 3ec41e4..7358978 100644 --- a/packs/vehicles/LOG.old +++ b/packs/vehicles/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.360390 17409b000 Delete type=3 #1 +2026/08/18-23:21:17.004916 1704a3000 Delete type=3 #1 diff --git a/packs/weapons/LOG b/packs/weapons/LOG index c13f9ea..8157caa 100644 --- a/packs/weapons/LOG +++ b/packs/weapons/LOG @@ -1,5 +1,5 @@ -2026/08/18-22:37:32.797239 17638b000 Recovering log #3 -2026/08/18-22:37:32.797509 17638b000 Level-0 table #5: started -2026/08/18-22:37:32.797806 17638b000 Level-0 table #5: 14767 bytes OK -2026/08/18-22:37:32.798073 17638b000 Delete type=0 #3 -2026/08/18-22:37:32.798122 17638b000 Delete type=3 #2 +2026/08/18-23:21:42.521392 16edc3000 Recovering log #3 +2026/08/18-23:21:42.521476 16edc3000 Level-0 table #5: started +2026/08/18-23:21:42.521798 16edc3000 Level-0 table #5: 14767 bytes OK +2026/08/18-23:21:42.522090 16edc3000 Delete type=0 #3 +2026/08/18-23:21:42.522134 16edc3000 Delete type=3 #2 diff --git a/packs/weapons/LOG.old b/packs/weapons/LOG.old index 7360601..1d7a683 100644 --- a/packs/weapons/LOG.old +++ b/packs/weapons/LOG.old @@ -1 +1 @@ -2026/08/18-22:37:15.355377 172877000 Delete type=3 #1 +2026/08/18-23:21:16.999207 1704a3000 Delete type=3 #1 diff --git a/ringbrp.mjs b/ringbrp.mjs index 50b83ff..00894f3 100644 --- a/ringbrp.mjs +++ b/ringbrp.mjs @@ -52,7 +52,8 @@ import { CIRCUMSTANCE, RANGE_LADDER, rangeBandFrom, MAJOR_WOUND_TABLE, COHERENCE_BANDS, COHERENCE_COSTS, COHERENCE_RECOVERY, coherenceBandFrom, PANIC_MAX, PANIC_BANDS, PANIC_TRIGGERS, PANIC_RELIEF, PANIC_BREAK, PANIC_AFTER_BREAK, - panicBandFrom, panicPenaltyFor, panicBreaksAt, panicAfter, panicReliefFor + panicBandFrom, panicPenaltyFor, panicBreaksAt, panicAfter, panicReliefFor, + DEVELOPMENT, developmentMarksOn, developmentImproves, developmentGainFor } from "./rules.mjs"; export { hitPointsFor, majorWoundFor, damageModifierFor, locationMaxHp, reflexRating, awarenessRating }; @@ -1096,10 +1097,10 @@ export async function skillRoll({ tokenId: actor?.token?.id ?? null } } }, { rollMode: game.settings.get("core", "rollMode") }); - // BRP experience: a skill used SUCCESSFULLY under pressure earns a tick. This - // was stored on every skill and set by nothing, so ticks never appeared and the - // end-of-case improvement roll had nothing to work on. - if (skillItem && ["critical", "special", "success"].includes(level) + // Development is REVERSED from Call of Cthulhu: a skill is marked when it FAILS you, + // not when it works. Nobody learns anything from the lock that opened first time. + // The condition lives in rules.mjs so the rulebook, the primer and this cannot drift. + if (skillItem && developmentMarksOn(level) && skillItem.system?.experienceEligible !== false && !skillItem.system?.experienceTicked) { await skillItem.update({ "system.experienceTicked": true }); } @@ -3309,7 +3310,7 @@ export async function usePower(actor, power, { pushed = false, difficulty = "ave // An Art earns its tick the way a skill does. Without this it arrived at roughly // POW x2 and stayed there for the rest of the character's life while charging // Coherence per attempt — a trap rather than a temptation. - if (["critical", "special", "success"].includes(res.level) && !power.system?.experienceTicked) { + if (developmentMarksOn(res.level) && !power.system?.experienceTicked) { await power.update({ "system.experienceTicked": true }); } @@ -3665,6 +3666,31 @@ export async function restFor(actor, { days = 0, hours = 0, device = "" } = {}) return total > 0 ? heal(actor, total, { source: device || "natural" }) : actor.system.hp.value; } +/** Is the development phase open? Safe before init, so the sheet can ask freely. */ +export function developmentOpen() { + try { return game.settings.get("ringbrp", "developmentPhase") === true; } + catch { return false; } +} + +/** + * The GM opens the phase at the end of a case. Everybody's sheet grows a button; each + * player presses their own. Closing it again is the GM saying the case is finished. + */ +export async function setDevelopmentPhase(open = true) { + if (!game.user.isGM) { ui.notifications?.warn(game.i18n.localize("RINGBRP.Dev.GMOnly")); return null; } + await game.settings.set("ringbrp", "developmentPhase", !!open); + const marked = game.actors.filter(a => a.type === "character") + .map(a => ({ name: a.name, + n: a.items.filter(i => ["skill","power"].includes(i.type) && i.system.experienceTicked).length })) + .filter(r => r.n > 0); + await ChatMessage.create({ + speaker: { alias: game.i18n.localize("RINGBRP.Dev.Title") }, + content: await rbRenderTemplate("systems/ringbrp/templates/chat-development.hbs", + { open: !!open, rows: marked }) + }); + return !!open; +} + /** * End of case. Every ticked skill rolls to improve: succeed against d100 > current * rating and it goes up 1D6, and the tick clears either way. This is the step that @@ -3690,18 +3716,23 @@ export async function endOfCase(actor) { const rating = Number(sk.system.value) || 0; const roll = await new Roll("1d100").evaluate(); // Roll OVER the current rating: the better you already are, the harder to gain. - const improved = roll.total > rating; - const gain = improved ? (await new Roll("1d6").evaluate()).total : 0; + const improved = developmentImproves(roll.total, rating); + const rolled = improved ? (await new Roll(DEVELOPMENT.gain).evaluate()).total : 0; + // A d100 gain can overshoot the top, so clip it to the skill's ceiling. + const gain = developmentGainFor(rating, rolled, sk.system.trainingCap); + const capped = improved && gain < rolled; await sk.update({ "system.value": rating + gain, "system.experienceTicked": false }); - results.push({ name: sk.name, rating, roll: roll.total, gain, improved }); + results.push({ name: sk.name, rating, roll: roll.total, gain, improved, capped }); } await ChatMessage.create({ speaker: ChatMessage.getSpeaker({ actor }), content: `

${ game.i18n.localize("RINGBRP.Exp.Title")}

` + !r.improved ? game.i18n.localize("RINGBRP.Exp.NoGain") : + r.gain ? `+${r.gain} → ${r.rating + r.gain}%${ + r.capped ? ` (${game.i18n.localize("RINGBRP.Exp.Capped")})` : ""}` : + game.i18n.localize("RINGBRP.Exp.AtCeiling")}`).join("")}` }); return results; } @@ -4889,6 +4920,11 @@ class RingBRPCharacterSheet extends ActorSheet { ctx.reaction = this._reactionRail(); ctx.readiness = this._readiness ?? { rating: 0 }; ctx.coherence = { band: coherenceBand(sys.coherence?.value) }; + ctx.development = { + open: developmentOpen(), + marked: this.actor.items.filter(i => ["skill", "power"].includes(i.type) + && i.system?.experienceTicked).length + }; ctx.panic = { band: panicBandFrom(sys.panic?.value), penalty: panicPenaltyFor(sys.panic?.value), pips: Array.from({ length: PANIC_MAX }, (_, i) => i < Number(sys.panic?.value ?? 0)) }; @@ -4953,6 +4989,13 @@ class RingBRPCharacterSheet extends ActorSheet { el.addEventListener("click", ev => this._onRollAttribute(ev))); root.querySelectorAll("[data-action='roll-characteristic']").forEach(el => el.addEventListener("click", ev => this._onRollCharacteristic(ev))); + root.querySelectorAll("[data-action='development']").forEach(el => + el.addEventListener("click", async ev => { + ev.preventDefault(); ev.stopPropagation(); + if (!developmentOpen()) { ui.notifications?.warn(game.i18n.localize("RINGBRP.Dev.NotOpen")); return; } + await endOfCase(this.actor); + this.render(false); + })); root.querySelectorAll("[data-action='full-skills']").forEach(el => el.addEventListener("click", async () => { const n = await grantFullSkillList(this.actor); @@ -6176,6 +6219,25 @@ async function resolveLegDialog(expedition) { Hooks.once("init", () => { console.log("RingBRP | initialising"); + // The development phase. World-scoped and GM-only, because it is a thing the table + // does together at the end of a case rather than a preference. It is the first game + // setting this system registers, and it earns that: the players' button has to know + // whether the GM has opened the phase, and that answer must survive a reload. + game.settings.register("ringbrp", "developmentPhase", { + name: "RINGBRP.Dev.SettingName", + hint: "RINGBRP.Dev.SettingHint", + scope: "world", config: false, type: Boolean, default: false, + onChange: open => { + for (const a of game.actors) a.sheet?.rendered && a.sheet.render(false); + // getSceneControlButtons fires once at load, so the toggle would keep showing + // the state it had then if the phase were opened by anything else - the API, or + // a second GM. Foundry preserves `active` on toggle tools across a render, so + // setting it and re-rendering is what keeps the button honest. + const tool = ui.controls?.controls?.tokens?.tools?.ringbrpDevelopment; + if (tool) { tool.active = !!open; ui.controls.render(); } + } + }); + // The figure is drawn in three places — combat, anatomy, and the header thumbnail. // One partial, so a location cannot be present in one of them and missing from // another, which is the same rule rules.mjs enforces for the formulas. @@ -6237,6 +6299,7 @@ Hooks.once("init", () => { coherenceBand, coherencePenaltyFor, woundPenaltyFor, loseCoherence, regainCoherence, displace, panicBand, panicPenaltyFor, panicBreaksAt, panicReliefFor, panicAfter, gainPanic, easePanic, breakPanic, clearPanic, partyState, + developmentOpen, setDevelopmentPhase, developmentMarksOn, developmentImproves, developmentGainFor, isAcross, setAcross, useTransposed, debrief, activeCaseFile, returnCrossing, caseMembers, earnedDiscoveries, deviceFunctionStatus, earnDiscovery, useDeviceFunction, pickContactActor, resolveContact, newEncounter, claimRewards, activeRewardEffects, @@ -6434,6 +6497,24 @@ for (const hook of [foundry?.applications?.sidebar?.tabs?.ChatLog }); } +// The GM needs a way to open the development phase that is not the console. It rides +// in the token controls beside the other table-facing tools, GM-only, and reflects the +// world setting so a reload or a second GM screen shows the true state. +Hooks.on("getSceneControlButtons", controls => { + if (!game.user?.isGM) return; + const group = controls?.tokens; + if (!group?.tools) return; + group.tools.ringbrpDevelopment = { + name: "ringbrpDevelopment", + order: Object.keys(group.tools).length + 1, + title: "RINGBRP.Dev.Toggle", + icon: "fa-solid fa-arrow-trend-up", + toggle: true, + active: developmentOpen(), + onChange: (_event, active) => setDevelopmentPhase(active) + }; +}); + Hooks.on("renderCombatTracker", (app, html) => { const root = html instanceof HTMLElement ? html : html?.[0]; if (!root || !game.combat) return; diff --git a/rules.mjs b/rules.mjs index 66c9466..49300da 100644 --- a/rules.mjs +++ b/rules.mjs @@ -283,6 +283,65 @@ export const COHERENCE_RECOVERY = { debrief: 1, downtimeWeek: 1, anchorObject: 1 arithmetic. ============================================================ */ +/* ============================================================ + DEVELOPMENT — you learn from what beat you + ============================================================ + Call of Cthulhu marks a skill when you SUCCEED with it and improves it later. + This department works the other way round: a skill is marked when it FAILS you. + Nobody learns anything from the lock that opened first time. + + The improvement roll itself is the familiar one and is deliberately unchanged: roll + d100 against the current rating and you improve by OVERSHOOTING it. The better you + already are, the less often a case teaches you anything, which is what stops a + long campaign turning everyone into ninety-percenters. + + The two halves therefore pull in opposite directions on purpose. A low skill fails + constantly in play (so it gets marked) and then fails its development roll (so it + climbs fast). A high skill rarely fails at all, and when it does it usually passes + the development roll and gains nothing. Skills converge rather than run away. + + There is still a hard ceiling, because a d100 gain can overshoot the top: a skill is + held at its own trainingCap if it has one, and at DEVELOPMENT.ceiling otherwise. + ============================================================ */ + +/** A skill is marked by the outcomes that went badly. Nothing else marks it. */ +export const DEVELOPMENT = { + marksOn: ["failure", "fumble"], + gain: "1d6", + improveOn: "over", + ceiling: 100 +}; + +/** Did this outcome mark the skill for development? */ +export function developmentMarksOn(level) { + return DEVELOPMENT.marksOn.includes(level); +} + +/** + * Does this development roll improve the skill? Roll OVER the current rating. + * A rating of 0 always improves; a rating of 100 or better never does. + */ +export function developmentImproves(roll, rating) { + return (Number(roll) || 0) > (Number(rating) || 0); +} + +/** + * The highest a skill can be taken to. A skill carrying its own trainingCap is held + * there — that is the only thing trainingCap has ever meant — and everything else + * stops at the point where a d100 can no longer overshoot it. + */ +export function developmentCeiling(trainingCap) { + const cap = Number(trainingCap); + return Number.isFinite(cap) && cap > 0 ? Math.min(cap, DEVELOPMENT.ceiling) : DEVELOPMENT.ceiling; +} + +/** What the skill actually becomes: the gain, clipped to the ceiling. */ +export function developmentGainFor(rating, rolled, trainingCap) { + const from = Number(rating) || 0; + const to = Math.min(from + (Number(rolled) || 0), developmentCeiling(trainingCap)); + return Math.max(0, to - from); +} + export const PANIC_MAX = 5; export const PANIC_BANDS = [ diff --git a/styles/ringbrp.css b/styles/ringbrp.css index 3871b1d..bb43ad6 100644 --- a/styles/ringbrp.css +++ b/styles/ringbrp.css @@ -1858,3 +1858,14 @@ .rb-scene-list li { display: flex; justify-content: space-between; padding: 2px 0; font-size: 13px; } .rb-scene-name { color: var(--rb-white); } .rb-scene-drop { color: var(--rb-yellow); font-variant-numeric: tabular-nums; } + +/* Development — the button only exists while the GM has the phase open. */ +.rb-cbtn-dev { + color: var(--rb-void); background: var(--rb-yellow); border-color: var(--rb-yellow); +} +.rb-cbtn-dev:hover { background: var(--rb-white); border-color: var(--rb-white); } +.rb-dev-count { + font-family: var(--rb-body); font-size: 11px; font-weight: 700; letter-spacing: .06em; + text-transform: uppercase; color: var(--rb-danger); +} +.rb-dev-open { color: var(--rb-yellow); font-size: 13px; margin: 4px 0; } diff --git a/system.json b/system.json index 4fcbd58..4c6c988 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.4.2", + "version": "1.5.0", "compatibility": { "minimum": 13, "verified": "14.364" diff --git a/templates/character-sheet.hbs b/templates/character-sheet.hbs index 58b4ebf..dc06fa6 100644 --- a/templates/character-sheet.hbs +++ b/templates/character-sheet.hbs @@ -181,6 +181,13 @@ {{/if}}
{{skillCount}} {{localize "RINGBRP.Sheet.SkillsShown"}} + {{#if development.marked}} + {{development.marked}} {{localize "RINGBRP.Dev.Marked"}} + {{/if}} + {{#if development.open}} + {{localize "RINGBRP.Dev.Button"}} + {{/if}} {{localize "RINGBRP.Sheet.FullList"}}
{{#each RINGBRP.categories as |cat key|}} diff --git a/templates/chat-development.hbs b/templates/chat-development.hbs new file mode 100644 index 0000000..693b25e --- /dev/null +++ b/templates/chat-development.hbs @@ -0,0 +1,19 @@ +
+
{{localize "RINGBRP.Dev.Title"}}
+ {{#if open}} +

{{localize "RINGBRP.Dev.Opened"}}

+ {{#if rows.length}} + + + + {{#each rows as |r|}}{{/each}} + +
{{localize "RINGBRP.Party.Who"}}{{localize "RINGBRP.Dev.Marked"}}
{{r.name}}{{r.n}}
+

{{localize "RINGBRP.Dev.PressIt"}}

+ {{else}} +

{{localize "RINGBRP.Exp.NoTicks"}}

+ {{/if}} + {{else}} +

{{localize "RINGBRP.Dev.Closed"}}

+ {{/if}} +
diff --git a/tools/check-behaviour.mjs b/tools/check-behaviour.mjs index 3a9ba53..5dd3e89 100644 --- a/tools/check-behaviour.mjs +++ b/tools/check-behaviour.mjs @@ -564,6 +564,59 @@ test("no roster agent has Anomaly Lore 0", async () => { "induction's lowest band is 0, so a probationer still cannot roll it"); }); +/* ---------------------------------------------------------------- * + * Development — reversed from Call of Cthulhu + * ---------------------------------------------------------------- */ + +// The whole point of the house rule: a skill is marked by FAILING, not by working. +// If this ever flips back, characters stop improving at the things they are bad at, +// which is the entire behaviour the rule exists to produce. +test("only failure and fumble mark a skill for development", () => { + for (const good of ["critical", "special", "success"]) + assert.equal(RULES.developmentMarksOn(good), false, `${good} marked a skill`); + for (const bad of ["failure", "fumble"]) + assert.equal(RULES.developmentMarksOn(bad), true, `${bad} did not mark a skill`); +}); +// Roll OVER to improve, so the better you are the less a case teaches you. Together +// with marking-on-failure this is what makes ratings converge instead of running away. +test("development improves on ANOTHER failure, and ties do not count", () => { + assert.equal(RULES.developmentImproves(72, 45), true); + assert.equal(RULES.developmentImproves(30, 45), false); + assert.equal(RULES.developmentImproves(45, 45), false, "a tie improved the skill"); + assert.equal(RULES.developmentImproves(1, 0), true, "a rating of 0 could not improve"); + assert.equal(RULES.developmentImproves(100, 100), false, "a rating of 100 still improved"); +}); +test("a d100 gain cannot push a skill past the ceiling", () => { + // 98% + the best possible 1d6 would be 104% without the clip + assert.equal(98 + RULES.developmentGainFor(98, 6, null), RULES.DEVELOPMENT.ceiling); +}); + +test("a skill with a trainingCap stops there, not at the global ceiling", () => { + assert.equal(60 + RULES.developmentGainFor(60, 6, 62), 62); + assert.equal(RULES.developmentGainFor(62, 6, 62), 0, "already at its cap, gains nothing"); +}); + +test("trainingCap is honoured but cannot raise the ceiling above it", () => { + assert.equal(RULES.developmentCeiling(250), RULES.DEVELOPMENT.ceiling); + assert.equal(RULES.developmentCeiling(null), RULES.DEVELOPMENT.ceiling); +}); + +test("a skill at the ceiling can still be marked but never gains", () => { + // 96-00 always fails, so even a maxed skill gets marked - it just never improves + assert.ok(RULES.developmentMarksOn("failure")); + assert.equal(RULES.developmentGainFor(RULES.DEVELOPMENT.ceiling, 6, null), 0); +}); + +test("the gain is a die, not a fixed number", () => + assert.match(RULES.DEVELOPMENT.gain, /^\d*d\d+$/)); +// The two halves must disagree: what marks a skill in play must be an outcome the +// development roll treats as improvement, or the rule reads as two unrelated coins. +test("the marking outcome and the improving outcome are the same idea", () => { + assert.ok(RULES.DEVELOPMENT.marksOn.includes("failure")); + assert.equal(RULES.DEVELOPMENT.improveOn, "over", + "improveOn changed; the rulebook and primer describe rolling over"); +}); + /* ---------------------------------------------------------------- */ await runAll(); diff --git a/tools/check-rules.mjs b/tools/check-rules.mjs index af9762a..92fe21e 100644 --- a/tools/check-rules.mjs +++ b/tools/check-rules.mjs @@ -197,6 +197,23 @@ const checks = [ ["panicReliefFor('special')", RULES.panicReliefFor("special"), 2], ["panicReliefFor('failure')", RULES.panicReliefFor("failure"), 0], ["panicReliefFor('fumble')", RULES.panicReliefFor("fumble"), -1], + + // Development is REVERSED from the usual: failure marks the skill, not success. + ["developmentMarksOn('failure')", RULES.developmentMarksOn("failure"), true], + ["developmentMarksOn('fumble')", RULES.developmentMarksOn("fumble"), true], + ["developmentMarksOn('success')", RULES.developmentMarksOn("success"), false], + ["developmentMarksOn('special')", RULES.developmentMarksOn("special"), false], + ["developmentMarksOn('critical')",RULES.developmentMarksOn("critical"), false], + ["developmentImproves(72,45)", RULES.developmentImproves(72, 45), true], + ["developmentImproves(30,45)", RULES.developmentImproves(30, 45), false], + ["developmentImproves(45,45)", RULES.developmentImproves(45, 45), false], + ["developmentCeiling(null)", RULES.developmentCeiling(null), 100], + ["developmentCeiling(75)", RULES.developmentCeiling(75), 75], + ["developmentCeiling(140)", RULES.developmentCeiling(140), 100], + ["developmentGainFor(45,6,null)", RULES.developmentGainFor(45, 6, null), 6], + ["developmentGainFor(97,6,null)", RULES.developmentGainFor(97, 6, null), 3], + ["developmentGainFor(73,6,75)", RULES.developmentGainFor(73, 6, 75), 2], + ["developmentGainFor(75,6,75)", RULES.developmentGainFor(75, 6, 75), 0], // a lost location must cost something, for every kind the species tables use ["locationEffectsFor([]) is inert", RULES.locationEffectsFor([]).all, 0], ["leg disabled → physical", RULES.locationEffectsFor([{kind:"leg",disabled:true}]).physical, -30], diff --git a/tools/primer-text.mjs b/tools/primer-text.mjs index cc983d8..cb15b08 100644 --- a/tools/primer-text.mjs +++ b/tools/primer-text.mjs @@ -16,7 +16,8 @@ import { TRADE_BANDS, INDUCTION } from "../postings.mjs"; import { hitPointsFor, majorWoundFor, damageModifierFor, defencePenaltyFor, COHERENCE_BANDS, RANGE_LADDER, CIRCUMSTANCE, - PANIC_BANDS, PANIC_TRIGGERS, PANIC_RELIEF, PANIC_MAX, PANIC_AFTER_BREAK, PANIC_BREAK + PANIC_BANDS, PANIC_TRIGGERS, PANIC_RELIEF, PANIC_MAX, PANIC_AFTER_BREAK, PANIC_BREAK, + DEVELOPMENT } from "../rules.mjs"; import { readFile } from "node:fs/promises"; @@ -412,6 +413,30 @@ export const PRIMER_PAGES = [ + P("Panic clears when the scene does. Somewhere safe, door shut, " + "somebody has made tea: you are back to nought. Coherence does not clear.") }, +{ name: "8c · Getting better at things", content: + P("You improve at what BEATS you." + + "Not at what you are already good at. If you have played this kind of game before, " + + "this is the opposite way round, and it is deliberate.") + + H(2, "During the case") + + P("Every time a skill fails you, it gets marked. You do not have to " + + "do anything — the sheet marks it. A success marks nothing. Nobody learns " + + "anything from the lock that opened first time.") + + H(2, "At the end") + + P("The GM opens the development phase and a button appears on your " + + "sheet. Press it once. For every marked skill you roll 1D100, and:") + + TABLE(["The roll", "What happens"], [ + ["Over your current rating — another failure", + "the skill goes up ${DEVELOPMENT.gain}"], + ["Equal to or under your rating — you made it", + "nothing. You knew that already."] + ]) + + P("Marks clear either way, and the next case starts clean.") + + P("Why it works like that. A skill you are bad at fails all the " + + "time, so it gets marked, and then it fails the development roll too, so it climbs " + + "fast. A skill you are already good at hardly ever fails, and when it does it " + + "usually passes the development roll and gains nothing. Everybody's weak spots " + + "close and nobody runs away with a 95%.") }, + { name: "9 · The whole game, in one page", content: P("You play ordinary adults in the modern world, pulled into an old and quiet " + "government department because you stay yourselves when reality goes wrong.") diff --git a/tools/rules-text.mjs b/tools/rules-text.mjs index 2f46397..7793c21 100644 --- a/tools/rules-text.mjs +++ b/tools/rules-text.mjs @@ -29,7 +29,8 @@ import { crossingArtPoolFor, crossingGrantsArt, crossingCostFor, REACTION, BLEED, CIRCUMSTANCE, RANGE_LADDER, rangeBandFrom, MAJOR_WOUND_TABLE, COHERENCE_BANDS, COHERENCE_COSTS, COHERENCE_RECOVERY, - PANIC_BANDS, PANIC_TRIGGERS, PANIC_RELIEF, PANIC_MAX, PANIC_AFTER_BREAK, PANIC_BREAK + PANIC_BANDS, PANIC_TRIGGERS, PANIC_RELIEF, PANIC_MAX, PANIC_AFTER_BREAK, PANIC_BREAK, + DEVELOPMENT } from "../rules.mjs"; // Postings, trades and induction are plain data and are imported as such. import { ROLES, TRADES, INDUCTION, TIERS, TRADE_BANDS } from "../postings.mjs"; @@ -686,10 +687,28 @@ find the brief was, as always, incomplete. They sort it out or they do not. They back and are debriefed, and the debrief is where coherence is recovered and where the GM finds out what the players think they did.

-

Afterwards

-

Every skill you used successfully under pressure is ticked. At the end of the case -each ticked skill rolls to improve: beat your current rating on 1D100 and it goes up -1D6. Ticks clear either way.

+

Afterwards — development

+

This is the opposite way round from most games in this family, and it is meant +to be.

+

A skill is marked when it FAILS you — ${DEVELOPMENT.marksOn.join(' or ')} — +and not when it works. Nobody learns anything from the lock that opened first time.

+

At the end of a case the GM opens the development phase, and each +agent rolls once for every marked skill. Roll 1D100 OVER your current rating and +it goes up ${DEVELOPMENT.gain}. If you make the roll you have learned nothing this +time. Marks clear either way.

+

So the two halves pull against each other on purpose. A skill you are bad at fails +constantly, gets marked, and then fails its development roll and climbs quickly. A skill +you are good at rarely fails at all, and when it does it usually passes the development +roll and gains nothing. Ratings converge instead of running away.

+

Running the phase at the table

+

The GM opens it with the Development phase toggle in the token +controls on the left of the screen, which only GMs see. Opening it posts a card naming +every agent and how many skills each has marked, and puts a Development +button on every agent's sheet. Each player presses their own button once; the rolls and +the results go to chat. Marks clear as they are rolled, so a second press does nothing. +Close the toggle when everyone has rolled.

+

A scene has to be open on the canvas for the toggle to respond — that is true of +every control on that bar, not just this one.

Cases do not need to connect. The running thread of a campaign is not a plot — it is the four coherence tracks going down.

` },