R-290: the same spelling gap in tagsIn and the POWER marker
Swept every reader that parses a human-written marker. Two more had R-289's fail-open. tagsIn matched a literal [CUS:, so [cus: Spot], [Cus: Spot], [CUS : Spot] and [ CUS: Spot] all read as nothing -- and it is the corpus reader behind check-scenarios' skill validation and check-rollable's reachability, so a beat spelled any of those four ways was checked by neither while both printed OK. The corpus contains no such tag today; the 88-to-89 roll count during this work was c0 writing v0.18, verified against HEAD's reader on the same tree. The POWER: marker had it with nothing covering it. bestiary.mjs and check-powers' own scan both used the literal, so a creature added with "Power:" generates no entry line and is never reported unclassified -- check-powers passes green on it, measured on a clone with its dodge skills stripped so the dodger-count assertion could not fire instead. Reader now takes POWER\s*: and stays uppercase, because power: occurs in ordinary prose; an asymmetric counter names the rest, and was measured against content.mjs first (41 strict, zero loose outside them). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9c2ce9d905
commit
ae8179bd9b
@@ -6804,3 +6804,39 @@ pass where three silently did not and the green that followed proved nothing:
|
||||
| code-span blanking removed | four, including both R-286 cases |
|
||||
| marker list truncated to the first hit | two declarations both seen |
|
||||
| odd-marker counter disabled | cast-shaped marker named |
|
||||
|
||||
## R-290 — the same spelling gap in the other two readers
|
||||
|
||||
Swept every reader that parses a human-written marker, after R-289 found `<!-- cast : -->`
|
||||
invisible. Two more had it. The probe is the same each time and it costs a minute: put the
|
||||
plausible misspellings to the reader as strings and print what it returns.
|
||||
|
||||
**`tagsIn` in check-scenarios.** `/\[CUS:\s*([^\]]+)\]/g` — no `i`, no space tolerance.
|
||||
`[cus: Spot]`, `[Cus: Spot]`, `[CUS : Spot]` and `[ CUS: Spot]` all read as nothing. This is
|
||||
the corpus reader behind check-scenarios' skill validation and check-rollable's reachability,
|
||||
so a beat spelled any of those four ways was checked by neither guard while both printed OK.
|
||||
Now `\[\s*CUS\s*:\s*...\]`, case-insensitive. **The corpus contains no such tag today** — the
|
||||
88-to-89 roll count during this work was c0 writing v0.18, not the widening finding one, and
|
||||
HEAD's reader gives 89 on the same tree. Checked rather than claimed, because a preventive fix
|
||||
reported as a discovery is a lie about what the tree contained.
|
||||
|
||||
Worth recording that c0's `outcome-coverage.mjs` loose pattern already catches all four as
|
||||
unparsed. Its guard was the only thing between those spellings and silence.
|
||||
|
||||
**The `POWER:` marker, which nothing covered.** `bestiary.mjs` and check-powers' own
|
||||
classification scan both used the literal, so `Power:` and `POWER :` generated no entry line
|
||||
and were never reported unclassified. A creature added tomorrow with `Power:` in its tactics
|
||||
**passes check-powers green** — 41 powers classified, the forty-second invisible. Measured on a
|
||||
clone with its dodge skills stripped, so the dodger-count assertion could not fire and take the
|
||||
credit: HEAD OK, this change names the marker and the creature.
|
||||
|
||||
The reader now takes `POWER\s*:` and stays uppercase, because `power:` occurs in ordinary
|
||||
tactics prose and matching it would invent powers. The other half is the counter: anything
|
||||
power-marker-shaped, case and spacing free, that the strict reader did not consume. Measured
|
||||
against content.mjs before wiring — 41 strict markers, zero loose ones outside them — so it
|
||||
starts from a clean corpus rather than from an exception list.
|
||||
|
||||
**All three fixes share one shape, which is check-cited's from R-283 and is now the house
|
||||
pattern**: widen the reader for the spellings you thought of, and add a looser counter that
|
||||
names the one you did not. A marker nothing reads is worse than no marker, because whoever
|
||||
wrote it believes the thing is declared.
|
||||
|
||||
+4
-1
@@ -76,7 +76,10 @@ const essenceOf = spec => String(spec.role ?? "").replace(/^[A-Z]{3,}[^.]*\.\s*/
|
||||
|
||||
/** The POWER line is the mechanic a GM needs in front of them; the rest is staging. */
|
||||
function powerOf(spec) {
|
||||
const m = String(spec.tactics ?? "").match(/POWER:\s*([A-Z][A-Z '’-]*)\.\s*([^]*?)(?=\s*(?:POWER:|$))/);
|
||||
/* `POWER\s*:` — the marker stays uppercase, because `power:` occurs in ordinary tactics
|
||||
prose and matching it would invent powers, but a space before the colon must not hide one.
|
||||
check-powers carries the counter that names the spellings this deliberately refuses. */
|
||||
const m = String(spec.tactics ?? "").match(/POWER\s*:\s*([A-Z][A-Z '’-]*)\.\s*([^]*?)(?=\s*(?:POWER\s*:|$))/);
|
||||
if (!m) return null;
|
||||
let body = m[2].trim().replace(/\s+/g, " ");
|
||||
// Trim to the first two sentences: the rule, and the qualification on it.
|
||||
|
||||
+29
-1
@@ -40,7 +40,35 @@ const problems = [];
|
||||
|
||||
/* 1. Classification. The prose is the authority for what a power SAYS; powers.mjs is the
|
||||
authority for what the harness does about it, and every one must be in both. */
|
||||
const withPower = NPCS.filter(n => /POWER:/.test(n.tactics ?? ""));
|
||||
const withPower = NPCS.filter(n => /POWER\s*:/.test(n.tactics ?? ""));
|
||||
|
||||
/* A POWER THIS READER CANNOT SEE IS A POWER NOBODY REPORTS UNCLASSIFIED. The marker was the
|
||||
literal `POWER:` in both this guard and bestiary.mjs, so `Power:` or `POWER :` generated no
|
||||
entry line AND passed the classification check — R-275's defect with the guard written to
|
||||
prevent it looking straight past it, and R-289's fail-open one file over.
|
||||
|
||||
Widening the reader fixes the spellings we thought of. This is the other half: anything
|
||||
power-marker-shaped, case and spacing free, that the strict reader did not consume. It is
|
||||
deliberately looser, so a creature whose tactics prose happens to say "power:" trips it and
|
||||
has to be reworded — which costs a sentence, where the opposite error costs a power the
|
||||
whole book measures without. Measured against content.mjs before wiring: 41 strict markers,
|
||||
zero loose ones outside them, so this starts from a clean corpus rather than from an
|
||||
exception list. */
|
||||
{
|
||||
const STRICT = /POWER\s*:/g, LOOSE = /\bpowers?\s*:/gi;
|
||||
for (const n of NPCS) {
|
||||
const t = String(n.tactics ?? "");
|
||||
const spans = [...t.matchAll(STRICT)].map(m => [m.index, m.index + m[0].length]);
|
||||
for (const m of t.matchAll(LOOSE)) {
|
||||
if (spans.some(([a, b]) => m.index >= a && m.index < b)) continue;
|
||||
problems.push(` ${n.key}: tactics carry "${m[0]}", which looks like a POWER marker and is `
|
||||
+ `not one either reader can see — "…${t.slice(Math.max(0, m.index - 40), m.index + 30)
|
||||
.replace(/\s+/g, " ")}…". Spelled this way it generates no bestiary line and is never `
|
||||
+ `reported unclassified. The marker is \`POWER:\` in capitals; if the word is ordinary `
|
||||
+ `prose here, reword it.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const spec of withPower) {
|
||||
const p = POWERS[spec.key];
|
||||
if (!p) {
|
||||
|
||||
@@ -54,10 +54,17 @@ export function skillsIn(body) {
|
||||
.filter(x => x && !NOISE.has(x.toLowerCase()) && !/^\d/.test(x));
|
||||
}
|
||||
|
||||
/* [CUS: ...] as written in the markdown, and the HTML-escaped form in the generators.
|
||||
Case and spacing are tolerated. The literal `\[CUS:` was measured against the obvious
|
||||
near-misses and `[cus: Spot]`, `[Cus: Spot]`, `[CUS : Spot]` and `[ CUS: Spot]` all read as
|
||||
nothing — and reading as nothing is the bad direction here, because this function is what
|
||||
check-scenarios validates skills through and what check-rollable checks reachability
|
||||
through. A beat spelled any of those four ways was checked by neither guard while both
|
||||
printed OK. Same fail-open as R-289's `<!-- cast : -->` and check-cited's three spellings of
|
||||
`cite:`; found the same way, by putting the spellings to the reader instead of to the tree. */
|
||||
export function tagsIn(text) {
|
||||
// [CUS: ...] as written in the markdown, and the HTML-escaped form in the generators
|
||||
const out = [];
|
||||
for (const m of text.matchAll(/\[CUS:\s*([^\]]+)\]/g)) out.push(m[1]);
|
||||
for (const m of text.matchAll(/\[\s*CUS\s*:\s*([^\]]+)\]/gi)) out.push(m[1]);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user