R-221: the phase-lead fallback could never fire

bestIn("tradecraft") ?? bestIn("navigate") never reached Navigate, because every
character carries every skill and skillRating() returns the printed base rather
than null — so the first lookup always found somebody. A team of postings that
train Navigate and not Tradecraft was led by whoever had the highest UNTRAINED
Tradecraft, which is 5%, putting phase three at 1%.

TRAINED now means above the printed base. Measured on a Pathology / Overwatch /
Warden / Materials team: lead 5% -> 34%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-08-10 20:44:03 +01:00
co-authored by Claude Opus 5
parent cc23fdb7bd
commit 1a0bdbd2df
28 changed files with 139 additions and 81 deletions
+41
View File
@@ -3676,3 +3676,44 @@ The rulebook is 9 pages, every page generated from the code it documents, and no
unresolved localisation key leaks into it. All packs rebuilt: 53 skills, 18 talents,
30 weapons, 13 armour, 119 gear, 14 vehicles, 6 Arts, 4 pregens, 6 NPCs, 2 tables,
the rulebook and the starter case. 231 icons and the four dice textures regenerated.
## IRON SIGNAL, played with Anchor / Pathology / Overwatch / Night Warden
A family removed from the world and not from the paperwork, with something using the
gap; the adversaries were themselves misfiled here generations ago and this is their
last stable ground. Good case. The team lost it.
Approach succeeded, then everything after failed. Contact with the staff opened on a
special and then failed twice, so the log never came across. Resources reached zero at
phase four. The case ends unclosed — the record was not put back — which the generator
already has an answer for: it followed somebody home, and that is next season's case.
**R-221 · the phase-lead fallback could never fire.** `bestIn("tradecraft") ??
bestIn("navigate")` — and every character carries every skill, because
`grantFullSkillList` puts them there, so `skillRating()` returns the printed base
rather than null. `bestIn("tradecraft")` therefore always found somebody and Navigate
was unreachable. A team of postings that train Navigate and not Tradecraft — and ten
of the twenty-five train neither, while eight train only Navigate — was led by
whoever had the highest UNTRAINED Tradecraft, which is the printed 5%. Measured on a
Pathology / Overwatch / Warden / Materials team: **the lead was 5%, and phase three
resolved at 1%.** With TRAINED meaning "above the printed base", the same team leads
at 34%. Fixed.
**And a correction to my own diagnosis.** I first read IRON SIGNAL's collapse as that
bug. It was not. The Anchor Officer trains Tradecraft at 40%, which beats the team's
best Navigate of 34, so the fix changes nothing for this team and the case would have
gone the same way. The real cause is tuning, and it is worth stating plainly:
phase chance across the five-phase ramp (difficult, 0 to −20)
lead 40% 20% → 15% → 10% → 5% → 1%
lead 55% 27% → 22% → 17% → 12% → 7%
lead 70% 35% → 30% → 25% → 20% → 15%
lead 85% 42% → 37% → 32% → 27% → 22%
Tradecraft is a SUPPORT skill on most postings that have it at all, so a realistic
lead is 40–55%, and the back half of every case sits in single figures. Two cases in
a row have now been decided by Resources running out rather than by anything the
players did. This is a design decision rather than a defect — a case is meant to be
survived rather than won — but if phases are supposed to be passable, the ramp
(−5 per phase, on top of `difficult`) is the dial, not `DEFENCE_STEP` and not the
skill ratings.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.065707 1755c3000 Recovering log #3
2026/08/10-20:30:07.065728 1755c3000 Level-0 table #5: started
2026/08/10-20:30:07.065850 1755c3000 Level-0 table #5: 1902 bytes OK
2026/08/10-20:30:07.066113 1755c3000 Delete type=0 #3
2026/08/10-20:30:07.066156 1755c3000 Delete type=3 #2
2026/08/10-20:41:41.141023 174647000 Recovering log #3
2026/08/10-20:41:41.141046 174647000 Level-0 table #5: started
2026/08/10-20:41:41.141166 174647000 Level-0 table #5: 1902 bytes OK
2026/08/10-20:41:41.141467 174647000 Delete type=0 #3
2026/08/10-20:41:41.141512 174647000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.729566 1735fb000 Delete type=3 #1
2026/08/10-20:41:32.871312 16fbcb000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.073841 174db7000 Recovering log #3
2026/08/10-20:30:07.073861 174db7000 Level-0 table #5: started
2026/08/10-20:30:07.073980 174db7000 Level-0 table #5: 4129 bytes OK
2026/08/10-20:30:07.074278 174db7000 Delete type=0 #3
2026/08/10-20:30:07.074323 174db7000 Delete type=3 #2
2026/08/10-20:41:41.147740 175e6b000 Recovering log #3
2026/08/10-20:41:41.147761 175e6b000 Level-0 table #5: started
2026/08/10-20:41:41.147869 175e6b000 Level-0 table #5: 4129 bytes OK
2026/08/10-20:41:41.148109 175e6b000 Delete type=0 #3
2026/08/10-20:41:41.148134 175e6b000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.752510 174613000 Delete type=3 #1
2026/08/10-20:41:32.894989 16ebb3000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.066829 175dcf000 Recovering log #3
2026/08/10-20:30:07.066918 175dcf000 Level-0 table #5: started
2026/08/10-20:30:07.067237 175dcf000 Level-0 table #5: 27954 bytes OK
2026/08/10-20:30:07.067567 175dcf000 Delete type=0 #3
2026/08/10-20:30:07.067649 175dcf000 Delete type=3 #2
2026/08/10-20:41:41.142024 175e6b000 Recovering log #3
2026/08/10-20:41:41.142110 175e6b000 Level-0 table #5: started
2026/08/10-20:41:41.142362 175e6b000 Level-0 table #5: 27954 bytes OK
2026/08/10-20:41:41.142565 175e6b000 Delete type=0 #3
2026/08/10-20:41:41.142606 175e6b000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.731275 172def000 Delete type=3 #1
2026/08/10-20:41:32.873111 1703d7000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.059142 1755c3000 Recovering log #3
2026/08/10-20:30:07.059383 1755c3000 Level-0 table #5: started
2026/08/10-20:30:07.059924 1755c3000 Level-0 table #5: 60660 bytes OK
2026/08/10-20:30:07.060223 1755c3000 Delete type=0 #3
2026/08/10-20:30:07.060258 1755c3000 Delete type=3 #2
2026/08/10-20:41:41.134409 174647000 Recovering log #3
2026/08/10-20:41:41.134636 174647000 Level-0 table #5: started
2026/08/10-20:41:41.135086 174647000 Level-0 table #5: 60660 bytes OK
2026/08/10-20:41:41.135295 174647000 Delete type=0 #3
2026/08/10-20:41:41.135327 174647000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.741242 172def000 Delete type=3 #1
2026/08/10-20:41:32.883213 16f3bf000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.063712 1755c3000 Recovering log #3
2026/08/10-20:30:07.063735 1755c3000 Level-0 table #5: started
2026/08/10-20:30:07.063831 1755c3000 Level-0 table #5: 3295 bytes OK
2026/08/10-20:30:07.064030 1755c3000 Delete type=0 #3
2026/08/10-20:30:07.064057 1755c3000 Delete type=3 #2
2026/08/10-20:41:41.138725 174647000 Recovering log #3
2026/08/10-20:41:41.138746 174647000 Level-0 table #5: started
2026/08/10-20:41:41.138865 174647000 Level-0 table #5: 3295 bytes OK
2026/08/10-20:41:41.139133 174647000 Delete type=0 #3
2026/08/10-20:41:41.139175 174647000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.746762 172def000 Delete type=3 #1
2026/08/10-20:41:32.889425 16f3bf000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.056785 1755c3000 Recovering log #3
2026/08/10-20:30:07.056996 1755c3000 Level-0 table #5: started
2026/08/10-20:30:07.057503 1755c3000 Level-0 table #5: 54921 bytes OK
2026/08/10-20:30:07.057829 1755c3000 Delete type=0 #3
2026/08/10-20:30:07.057871 1755c3000 Delete type=3 #2
2026/08/10-20:41:41.132366 174647000 Recovering log #3
2026/08/10-20:41:41.132578 174647000 Level-0 table #5: started
2026/08/10-20:41:41.133060 174647000 Level-0 table #5: 54921 bytes OK
2026/08/10-20:41:41.133331 174647000 Delete type=0 #3
2026/08/10-20:41:41.133365 174647000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.736946 172def000 Delete type=3 #1
2026/08/10-20:41:32.878370 16fbcb000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.070149 174db7000 Recovering log #3
2026/08/10-20:30:07.070202 174db7000 Level-0 table #5: started
2026/08/10-20:30:07.070462 174db7000 Level-0 table #5: 23316 bytes OK
2026/08/10-20:30:07.070725 174db7000 Delete type=0 #3
2026/08/10-20:30:07.070754 174db7000 Delete type=3 #2
2026/08/10-20:41:41.144398 175e6b000 Recovering log #3
2026/08/10-20:41:41.144451 175e6b000 Level-0 table #5: started
2026/08/10-20:41:41.144690 175e6b000 Level-0 table #5: 23316 bytes OK
2026/08/10-20:41:41.144892 175e6b000 Delete type=0 #3
2026/08/10-20:41:41.144919 175e6b000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.751001 1735fb000 Delete type=3 #1
2026/08/10-20:41:32.893484 16ebb3000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.061396 1755c3000 Recovering log #3
2026/08/10-20:30:07.061441 1755c3000 Level-0 table #5: started
2026/08/10-20:30:07.061612 1755c3000 Level-0 table #5: 6679 bytes OK
2026/08/10-20:30:07.061918 1755c3000 Delete type=0 #3
2026/08/10-20:30:07.061984 1755c3000 Delete type=3 #2
2026/08/10-20:41:41.136311 174647000 Recovering log #3
2026/08/10-20:41:41.136348 174647000 Level-0 table #5: started
2026/08/10-20:41:41.136491 174647000 Level-0 table #5: 6679 bytes OK
2026/08/10-20:41:41.136799 174647000 Delete type=0 #3
2026/08/10-20:41:41.136837 174647000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.723371 172def000 Delete type=3 #1
2026/08/10-20:41:32.865001 16fbcb000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.071532 1745ab000 Recovering log #3
2026/08/10-20:30:07.071606 1745ab000 Level-0 table #5: started
2026/08/10-20:30:07.071826 1745ab000 Level-0 table #5: 16892 bytes OK
2026/08/10-20:30:07.072104 1745ab000 Delete type=0 #3
2026/08/10-20:30:07.072155 1745ab000 Delete type=3 #2
2026/08/10-20:41:41.145560 174e53000 Recovering log #3
2026/08/10-20:41:41.145600 174e53000 Level-0 table #5: started
2026/08/10-20:41:41.145824 174e53000 Level-0 table #5: 16892 bytes OK
2026/08/10-20:41:41.146032 174e53000 Delete type=0 #3
2026/08/10-20:41:41.146059 174e53000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.745389 173e07000 Delete type=3 #1
2026/08/10-20:41:32.887732 16ebb3000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.072788 1755c3000 Recovering log #3
2026/08/10-20:30:07.072808 1755c3000 Level-0 table #5: started
2026/08/10-20:30:07.072910 1755c3000 Level-0 table #5: 3739 bytes OK
2026/08/10-20:30:07.073132 1755c3000 Delete type=0 #3
2026/08/10-20:30:07.073159 1755c3000 Delete type=3 #2
2026/08/10-20:41:41.146606 174647000 Recovering log #3
2026/08/10-20:41:41.146628 174647000 Level-0 table #5: started
2026/08/10-20:41:41.146719 174647000 Level-0 table #5: 3739 bytes OK
2026/08/10-20:41:41.146990 174647000 Delete type=0 #3
2026/08/10-20:41:41.147036 174647000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.743450 174613000 Delete type=3 #1
2026/08/10-20:41:32.885357 1703d7000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.062652 175dcf000 Recovering log #3
2026/08/10-20:30:07.062680 175dcf000 Level-0 table #5: started
2026/08/10-20:30:07.062844 175dcf000 Level-0 table #5: 5105 bytes OK
2026/08/10-20:30:07.063075 175dcf000 Delete type=0 #3
2026/08/10-20:30:07.063106 175dcf000 Delete type=3 #2
2026/08/10-20:41:41.137507 17565f000 Recovering log #3
2026/08/10-20:41:41.137545 17565f000 Level-0 table #5: started
2026/08/10-20:41:41.137683 17565f000 Level-0 table #5: 5105 bytes OK
2026/08/10-20:41:41.137984 17565f000 Delete type=0 #3
2026/08/10-20:41:41.138042 17565f000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.726270 174613000 Delete type=3 #1
2026/08/10-20:41:32.867714 16fbcb000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.068780 1755c3000 Recovering log #3
2026/08/10-20:30:07.068812 1755c3000 Level-0 table #5: started
2026/08/10-20:30:07.068996 1755c3000 Level-0 table #5: 5012 bytes OK
2026/08/10-20:30:07.069362 1755c3000 Delete type=0 #3
2026/08/10-20:30:07.069417 1755c3000 Delete type=3 #2
2026/08/10-20:41:41.143254 174647000 Recovering log #3
2026/08/10-20:41:41.143279 174647000 Level-0 table #5: started
2026/08/10-20:41:41.143426 174647000 Level-0 table #5: 5012 bytes OK
2026/08/10-20:41:41.143706 174647000 Delete type=0 #3
2026/08/10-20:41:41.143760 174647000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.733002 1735fb000 Delete type=3 #1
2026/08/10-20:41:32.874846 1703d7000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:30:07.064707 175dcf000 Recovering log #3
2026/08/10-20:30:07.064741 175dcf000 Level-0 table #5: started
2026/08/10-20:30:07.064869 175dcf000 Level-0 table #5: 7240 bytes OK
2026/08/10-20:30:07.065071 175dcf000 Delete type=0 #3
2026/08/10-20:30:07.065101 175dcf000 Delete type=3 #2
2026/08/10-20:41:41.139842 175e6b000 Recovering log #3
2026/08/10-20:41:41.139878 175e6b000 Level-0 table #5: started
2026/08/10-20:41:41.140047 175e6b000 Level-0 table #5: 7240 bytes OK
2026/08/10-20:41:41.140366 175e6b000 Delete type=0 #3
2026/08/10-20:41:41.140415 175e6b000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:29:51.728080 1735fb000 Delete type=3 #1
2026/08/10-20:41:32.869495 16ebb3000 Delete type=3 #1
+20 -3
View File
@@ -4975,11 +4975,28 @@ async function resolveLegDialog(expedition) {
// as a fallback for phases that really are about ground.
// Tradecraft FIRST, not "best of either": a case phase is led by whoever reads
// the situation best, and Navigate is only consulted when nobody has Tradecraft.
const bestIn = fam => members.reduce((acc, a) => {
const r = a.skillRating(fam);
//
// R-221. That fallback could never fire. Every character carries every skill —
// grantFullSkillList sees to it — so skillRating() returns the printed base rather
// than null, `bestIn("tradecraft")` always found somebody, and Navigate was
// unreachable. A team of four postings that train Navigate and not Tradecraft was
// led by whoever had the highest UNTRAINED Tradecraft, which is the printed 5%,
// and every phase after the first became arithmetically hopeless. TRAINED means
// above the printed base, not merely present.
const trained = (a, fam) => {
const it = a.items.find(i => i.type === "skill" && i.system.familyId === fam
&& !i.system.specialisationId);
if (!it) return null;
const base = Number(it.system.base?.value) || 0;
const val = Number(it.system.value) || 0;
return val > base ? val : null;
};
const bestIn = (fam, requireTrained = true) => members.reduce((acc, a) => {
const r = requireTrained ? trained(a, fam) : a.skillRating(fam);
return (r !== null && r > (acc?.rating ?? -1)) ? { actor: a, rating: r, skill: fam } : acc;
}, null);
const best = bestIn("tradecraft") ?? bestIn("navigate");
const best = bestIn("tradecraft") ?? bestIn("navigate")
?? bestIn("tradecraft", false) ?? bestIn("navigate", false);
if (!best) return ui.notifications.warn(game.i18n.localize("RINGBRP.Notify.NoNavigator"));
// A leg may state its own difficulty; the generator sets one deliberately and