R-284: report a wrong figure as a wrong figure, and read the right entry
R-283 left the intact-sentence-wrong-number case falling through to omission, so the guard said BESTIARY "does not state" a rating the page was stating. reads() now has a shape tier between strict and loose: the strict pattern with its value slot loosened, reporting which of the two numbers moved. Scoped the per-creature rules while adding it. They read the whole page, and each is the only rule of its kind today, so a page-wide match found the right line by luck; a second attackFactor creature would have let the courier's rule match that creature's sentence and report the courier correct. They now read the creature's own "### Name" entry -- proved by deleting the courier's line and planting an identical one under the redcap: still omission, where before it would have passed. Five discriminations plus the decoy, proved in a worktree with the message read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
327d060eb1
commit
1a2ca44c52
@@ -6637,3 +6637,28 @@ digits; reword the statblock too and this degrades to OMISSION, the safe directi
|
||||
Four discriminations proved in a worktree, message read in each: reword kept → REWORD;
|
||||
line deleted → OMISSION; wrong number, right wording → mismatch naming both figures; redcap
|
||||
back on the ladder → the R-282 contradiction, unchanged.
|
||||
|
||||
## R-284 — the third tier: a figure that moved is not a sentence that went
|
||||
|
||||
R-283 left a case falling through to the omission branch: the sentence intact, the number
|
||||
inside it wrong. The message said BESTIARY "does not state the rating the simulator actually
|
||||
uses" about a page that was stating one. It named the right figures, so it pointed at the
|
||||
fix, but it described a document that does not exist — the same class of confidently-wrong
|
||||
report R-283 was written to remove, one case further in.
|
||||
|
||||
`reads` now runs four tiers: **strict** (the sentence as it stands) passes; **shape** — the
|
||||
strict pattern with its value slot loosened — reports a MISMATCH and says which of the two
|
||||
numbers moved; **loose** reports a REWORD; nothing reports the OMISSION. The mismatch text
|
||||
is the sharpest of the four on purpose: *a figure the page states and the harness
|
||||
contradicts is worse than one it omits, because a GM can act on it.*
|
||||
|
||||
**Scoped while adding it.** The rules read `page`, and every one of them is the only rule of
|
||||
its kind today, so a page-wide match found the right line by luck. Wire a second
|
||||
`attackFactor` creature and the courier's rule would have matched that creature's sentence
|
||||
and reported the courier correct — a guard passing on someone else's evidence. Per-creature
|
||||
rules now read the creature's own `### Name` entry. Proved by deleting the courier's line and
|
||||
planting an identical one in the redcap's entry: still OMISSION, where before scoping it
|
||||
would have passed.
|
||||
|
||||
Five cases proved in a worktree, message read in each: effective rating wrong, base rating
|
||||
wrong, both wrong, reworded with the numbers kept, deleted. Plus the decoy above.
|
||||
|
||||
+41
-7
@@ -137,11 +137,25 @@ if (!page) {
|
||||
changed the prose — restore the wording, or move the pattern — and reporting it as the
|
||||
last would send them to fix a page that is already right. */
|
||||
const rx = t => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const SENTENCES = page.split(/\n+|(?<=\.)[ \t]+/);
|
||||
const sentencesWith = (...needles) => SENTENCES.filter(s => needles.every(n => s.includes(n)));
|
||||
const sentencesIn = (text, needles) =>
|
||||
text.split(/\n+|(?<=\.)[ \t]+/).filter(s => needles.every(n => s.includes(n)));
|
||||
const quote = s => s.trim().replace(/\s+/g, " ").slice(0, 120);
|
||||
|
||||
/** strict -> [] ; loose only -> a reword problem naming the line ; neither -> `omission`.
|
||||
/* A per-creature rule must read that creature's entry, not the page. Every rule here
|
||||
happens to be the only one of its kind today, so a page-wide match finds the right line
|
||||
by luck; wire a second attackFactor creature and the courier's rule would match ITS
|
||||
sentence and report the courier correct. Entries are `### Name` and run to the next
|
||||
heading. */
|
||||
const entryOf = name => {
|
||||
const at = page.indexOf(`### ${name}\n`);
|
||||
if (at < 0) return null;
|
||||
const rest = page.slice(at);
|
||||
const end = rest.slice(1).search(/\n#{2,3} /);
|
||||
return end < 0 ? rest : rest.slice(0, end + 1);
|
||||
};
|
||||
|
||||
/** strict -> [] ; right shape, wrong value -> `shape.mismatch` ; loose only -> a reword
|
||||
problem naming the line ; neither -> `omission`.
|
||||
`loose` is {has, not}: the needles the claim must carry AND the ones that mark a line
|
||||
as something else. The exclusion is not fussiness. The courier's factored-attack
|
||||
sentence prints 40% and 80%, and so does its own statblock line — `**Attacks with**
|
||||
@@ -151,11 +165,19 @@ if (!page) {
|
||||
level down. Excluding the statblock's own generated marker makes the loose test about
|
||||
the claim rather than about the digits; reword that marker too and this degrades to
|
||||
OMISSION, which is the safe direction to fail. */
|
||||
const reads = ({ who, strict, loose, still, omission }) => {
|
||||
if (strict.test(page)) return [];
|
||||
const reads = ({ who, where, strict, shape, loose, still, omission }) => {
|
||||
const text = where ?? page;
|
||||
if (strict.test(text)) return [];
|
||||
/* THE SENTENCE IS INTACT AND THE NUMBER INSIDE IT IS WRONG. Without this tier that case
|
||||
fell through to `omission`, which said the page "does not state" a rating the page was
|
||||
stating — the right figures were named, so it pointed at the fix, but it described a
|
||||
document that does not exist. `shape` is the strict pattern with the value slot
|
||||
loosened, so the guard can tell a figure that moved from a sentence that went. */
|
||||
const m = shape && text.match(shape.re);
|
||||
if (m) return [shape.mismatch(m)];
|
||||
const has = Array.isArray(loose) ? loose : loose.has;
|
||||
const not = Array.isArray(loose) ? [] : (loose.not ?? []);
|
||||
const near = sentencesWith(...has).filter(s => !not.some(n => s.includes(n)));
|
||||
const near = sentencesIn(text, has).filter(s => !not.some(n => s.includes(n)));
|
||||
if (!near.length) return [omission];
|
||||
return [` ${who}: BESTIARY still ${still}, but no longer in the form this guard reads, so `
|
||||
+ `nothing checks it any more. The line it found: "${quote(near[0])}". The page may well `
|
||||
@@ -187,7 +209,7 @@ if (!page) {
|
||||
const exemptDodgers = dodgers.filter(n => combatPowerFor(n.key)?.defenceStacking === "ignores").length;
|
||||
const m = page.match(/Across \*{0,2}(\d+)\*{0,2} creatures that spend defences/);
|
||||
if (!m) {
|
||||
const near = sentencesWith("spend defences");
|
||||
const near = sentencesIn(page, ["spend defences"]);
|
||||
out.push(near.length
|
||||
? ` BESTIARY still talks about creatures that spend defences — "${quote(near[0])}" — but `
|
||||
+ `not in the counted form "Across N creatures that spend defences", so the number is `
|
||||
@@ -208,9 +230,21 @@ if (!page) {
|
||||
const arm = c.arms?.[0];
|
||||
if (!arm) return [];
|
||||
const effective = Math.floor(arm.rating * effect.attackFactor);
|
||||
const entry = entryOf(spec.name);
|
||||
if (!entry) return [` ${spec.key}: BESTIARY has no "### ${spec.name}" entry to check`];
|
||||
return reads({
|
||||
who: spec.key,
|
||||
where: entry,
|
||||
strict: new RegExp(`\\*\\*${effective}%\\*\\*, not ${arm.rating}%`),
|
||||
shape: {
|
||||
re: /\*\*(\d+)%\*\*, not (\d+)%/,
|
||||
mismatch: ([line, printed, base]) =>
|
||||
` ${spec.key}: BESTIARY prints "${line}" and the simulator fights it at `
|
||||
+ `${effective}% of ${arm.rating}% — ${Number(printed) !== effective ? `the effective `
|
||||
+ `rating is wrong (${printed}%, should be ${effective}%)` : `the rating it is taken `
|
||||
+ `from is wrong (${base}%, should be ${arm.rating}%)`}. A figure the page states `
|
||||
+ `and the harness contradicts is worse than one it omits: a GM can act on it.`
|
||||
},
|
||||
loose: { has: [`${effective}%`, `${arm.rating}%`], not: ["**Attacks with**"] },
|
||||
still: `prints ${effective}% and ${arm.rating}% together`,
|
||||
omission: ` ${spec.key}: BESTIARY does not state the rating the simulator actually uses `
|
||||
|
||||
Reference in New Issue
Block a user