R-287: assert the scope anchors in check-behaviour, and end them structurally
indexOf returns -1 for a name that is gone and slice(0, -1) is everything but one character, so a scope anchored on a moved name does not shrink or fail -- it becomes the file. Renaming the end anchor took one test's body from 3,184 characters to 30,825 with the assertion still passing. The other site windowed burstAttack at 4,000 characters over a function that runs 4,305. bodyOf asserts the anchor and ends at the next top-level function. A missing anchor now says which anchor and what would have happened, where the old code reported "burstAttack still calls rollWeaponDamage" -- a claim about a call when the truth was a claim about a name. Also records the rest of the sweep, including the guards deliberately left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0453255ecc
commit
f3caaab04d
@@ -6690,3 +6690,65 @@ nothing to check.
|
|||||||
Five cases in a worktree: two decoys (fail here, pass at R-284, the pass confirmed by running
|
Five cases in a worktree: two decoys (fail here, pass at R-284, the pass confirmed by running
|
||||||
HEAD's copy against the same tree), a reword inside the section, the renamed heading, and the
|
HEAD's copy against the same tree), a reword inside the section, the renamed heading, and the
|
||||||
clean control.
|
clean control.
|
||||||
|
|
||||||
|
## R-286 — a declaration and a mention of one look identical to a regex
|
||||||
|
|
||||||
|
Swept the other seventeen guards for the page-wide match R-285 removed from check-powers.
|
||||||
|
Most are already scoped or have nothing to scope. Two are not, and the first is live.
|
||||||
|
|
||||||
|
`CLEAN_GROUND.md` contains **two** things matching `<!-- cast: ... -->`: the declaration
|
||||||
|
under `## Casting`, and — ten lines below it — the warning that explains it, which quotes
|
||||||
|
`` `<!-- cast: -->` `` inside backticks and matches the same pattern with an empty capture.
|
||||||
|
`check-rollable` and `declared-cast.mjs` both used `.match()`, first hit wins. **The whole
|
||||||
|
arrangement has been correct because the declaration happens to come first.**
|
||||||
|
|
||||||
|
Move that warning above the list and `check-rollable` reads a cast of nobody, falls back to
|
||||||
|
`ROSTER_BEST`, and goes green having held every skill in the document to the full duty roster
|
||||||
|
instead of the declared six — the identical OK line, the scope silently widened. Instrumented
|
||||||
|
and read off, not reasoned about:
|
||||||
|
|
||||||
|
[scope] docs/scenarios/CLEAN_GROUND.md -> the duty roster (warning moved above)
|
||||||
|
[scope] docs/scenarios/CLEAN_GROUND.md -> its declared cast of 6 (as the file stands)
|
||||||
|
|
||||||
|
`check-firstblood` fails on the same page, but says *"CLEAN GROUND now casts 0"* — describing
|
||||||
|
a scenario that casts six, which sends a reader to the cast list rather than to the two
|
||||||
|
markers. R-268 had this bug from the other side, when a tools file's own example was read as
|
||||||
|
a real declaration; this is the same defect facing inward, and the marker that triggers it was
|
||||||
|
added to the document by the warning telling everyone how load-bearing the marker is.
|
||||||
|
|
||||||
|
`castMarkersIn` now blanks code spans and fenced blocks before scanning — padded, so line
|
||||||
|
numbers still point at the source — and more than one surviving marker is fatal with every
|
||||||
|
line named. `check-rollable` imports it rather than carrying a second regex, so the guard that
|
||||||
|
checks the cast and the guards that measure the fight cannot disagree about who is in it.
|
||||||
|
|
||||||
|
**Disclosed to c0 the same minute.** The comparison that found this was meant to run in a
|
||||||
|
throwaway worktree; `git worktree add` failed on a stale registration, the heredoc ran on in
|
||||||
|
the shared tree, and it moved one line of c0's uncommitted `CLEAN_GROUND.md`. Reverted by
|
||||||
|
hand, `## Casting` block diffed byte-identical against HEAD, c0 told what to check and why
|
||||||
|
before anything else was done. A `test -d` on the worktree now guards the pattern.
|
||||||
|
|
||||||
|
## R-287 — a scope anchored on a name that has moved is the whole file
|
||||||
|
|
||||||
|
`check-behaviour` scopes three source assertions with `indexOf`. For a name that is gone
|
||||||
|
`indexOf` returns -1 and `slice(0, -1)` is everything-but-one-character, so the scope does not
|
||||||
|
shrink or fail — it becomes the file. Renaming the end anchor took one test's body from
|
||||||
|
**3,184 characters to 30,825** with the assertion still passing: a test that reads like a
|
||||||
|
statement about `rollWeaponDamage` making a statement about all of `ringbrp.mjs`.
|
||||||
|
|
||||||
|
The other site windowed `burstAttack` at 4,000 characters. The function runs 4,305, so the
|
||||||
|
window was already short of its own subject, and one edit the other way would have had it
|
||||||
|
testing whatever came next for a `locate: false` that belongs to a different call.
|
||||||
|
|
||||||
|
`bodyOf` now asserts the anchor and ends structurally, at the next top-level function. A
|
||||||
|
missing anchor says so — *check-behaviour scopes a test to "export async function burstAttack"
|
||||||
|
and ringbrp.mjs no longer contains it. Fix the anchor: unfixed, the test would read the whole
|
||||||
|
file and pass* — where the old code reported *"burstAttack still calls rollWeaponDamage"*,
|
||||||
|
which is a claim about a call when the truth is a claim about a name.
|
||||||
|
|
||||||
|
**The rest of the sweep, so the negative result is on the record.** check-cited resolves each
|
||||||
|
citation positionally and already refuses markers it cannot read. check-handouts matches each
|
||||||
|
almanac row's note against the whole scenario (`md.includes(note)`); the notes are distinctive
|
||||||
|
sentences and there is no smaller slice that owns them, so it is left alone deliberately
|
||||||
|
rather than overlooked. check-rules builds its covered-set from labels it generates, not from
|
||||||
|
searching a document. check-lang, check-creatures, check-templates, check-kits and the
|
||||||
|
baseline guards do no document matching at all.
|
||||||
|
|||||||
@@ -626,10 +626,29 @@ test("the marking outcome and the improving outcome are the same idea", () => {
|
|||||||
did not. These tests are about the JOIN, because every individual part was already
|
did not. These tests are about the JOIN, because every individual part was already
|
||||||
correct and the table still never saw a located hit. */
|
correct and the table still never saw a located hit. */
|
||||||
|
|
||||||
|
/* A SCOPE ANCHORED ON A NAME THAT HAS MOVED IS NOT A SMALLER SCOPE, IT IS THE WHOLE FILE.
|
||||||
|
`indexOf` returns -1 for a name that is gone, `slice(0, -1)` is everything-but-one-character,
|
||||||
|
and the assertion below then passes against 30,825 characters of unrelated code while
|
||||||
|
reading like a test about one function — measured, not supposed: renaming the end anchor
|
||||||
|
took this body from 3,184 characters to 30,825 and the test stayed green. The other site
|
||||||
|
used a 4,000-character window on a function that runs 4,305, so it was both short of its
|
||||||
|
own subject and one edit away from covering the next one.
|
||||||
|
|
||||||
|
Same defect R-285 removed from check-powers, in source rather than prose. So the anchor is
|
||||||
|
asserted and the end is structural: the next top-level function, whatever it is called. */
|
||||||
|
const NEXT_FN = /\n(?:export\s+)?(?:async\s+)?function\s/;
|
||||||
|
function bodyOf(src, decl) {
|
||||||
|
const at = src.indexOf(decl);
|
||||||
|
assert.notEqual(at, -1, `check-behaviour scopes a test to "${decl}" and ringbrp.mjs no longer `
|
||||||
|
+ `contains it. Fix the anchor: unfixed, the test would read the whole file and pass.`);
|
||||||
|
const rest = src.slice(at);
|
||||||
|
const end = rest.slice(1).search(NEXT_FN);
|
||||||
|
return end < 0 ? rest : rest.slice(0, end + 1);
|
||||||
|
}
|
||||||
|
|
||||||
test("an ordinary weapon damage roll asks for a location", () => {
|
test("an ordinary weapon damage roll asks for a location", () => {
|
||||||
const src = readFileSync(new URL("../ringbrp.mjs", import.meta.url), "utf8");
|
const src = readFileSync(new URL("../ringbrp.mjs", import.meta.url), "utf8");
|
||||||
const fn = src.slice(src.indexOf("export async function rollWeaponDamage"));
|
const body = bodyOf(src, "export async function rollWeaponDamage");
|
||||||
const body = fn.slice(0, fn.indexOf("\nasync function promptDamageBand"));
|
|
||||||
assert.ok(/hitLocationRoll\(/.test(body),
|
assert.ok(/hitLocationRoll\(/.test(body),
|
||||||
"rollWeaponDamage must reach hitLocationRoll — this is the join that was missing");
|
"rollWeaponDamage must reach hitLocationRoll — this is the join that was missing");
|
||||||
});
|
});
|
||||||
@@ -649,8 +668,7 @@ test("a weapon's class picks the column of the table it reads", () => {
|
|||||||
|
|
||||||
test("the one caller that locates for itself opts out, so nothing applies twice", () => {
|
test("the one caller that locates for itself opts out, so nothing applies twice", () => {
|
||||||
const src = readFileSync(new URL("../ringbrp.mjs", import.meta.url), "utf8");
|
const src = readFileSync(new URL("../ringbrp.mjs", import.meta.url), "utf8");
|
||||||
const burst = src.slice(src.indexOf("export async function burstAttack"));
|
const body = bodyOf(src, "export async function burstAttack");
|
||||||
const body = burst.slice(0, 4000);
|
|
||||||
const call = body.match(/rollWeaponDamage\(\{[^}]*\}/);
|
const call = body.match(/rollWeaponDamage\(\{[^}]*\}/);
|
||||||
assert.ok(call, "burstAttack still calls rollWeaponDamage");
|
assert.ok(call, "burstAttack still calls rollWeaponDamage");
|
||||||
assert.ok(/locate:\s*false/.test(call[0]),
|
assert.ok(/locate:\s*false/.test(call[0]),
|
||||||
|
|||||||
Reference in New Issue
Block a user