test(harness): close three smoke-select and fixture blind spots (#754)

Three independent blind spots in the test harness.

1. smoke-select read symbols only from changed lines of a --unified=0
   diff. An edit to the arguments of a multi-line call names nothing:
   #741 (d5bdfde9) changed only the arguments of
   runtime.resolveIsoOverlayFitEnvelope({ on the line above, and the
   selection answered "unproven" plus the visual minimum, although the
   callee is registered in smoke-links for smoke_iso_flat_parity and
   smoke_isometric_contract - the two smokes the #741 author ran by hand.
   The selection diff now carries CALL_CONTEXT_LINES = 3 lines of
   context; for each changed line parseDiff looks for the nearest
   unclosed "(" above it within the hunk, walking through a literal
   argument ({ or [ after "(", "," or "["), stopping at ";" on depth zero
   or any other unclosed brace. A callee from the symbol table joins
   symbols and the new callees field and is marked "(вызов)" in the
   report. Context lines never give direct symbols. task-packet takes a
   separate context diff for selectSmokes; change-risk keeps --unified=0.
   Over the last 80 src commits of dev: 16 commits gain a callee, 2 move
   from unproven to a proven link (#741, #724 5f8e8ca7), +15 smokes in
   total, at most 4 per commit, none lost.

2. The #732 dead-field check judged only scene-builder calls. The four
   resolveIsoOverlayFitEnvelope({...}) literals in iso-scene-render tests
   went straight into the test-build function, so stageSize: null (the
   field #741 removed) stayed green. They now go through overlayFit typed
   with OverlayFitFixture (keys of IsoOverlayFitEnvelopeInput); the check
   judges overlayFit/resolveIsoOverlayFitEnvelope calls like the scene
   builders, and its probe asserts that OverlayFitFixture rejects
   stageSize, so the type resolved to the real input and not to any.

3. smoke_backdrop's mode() called the private _setMode and slept 220 ms.
   It now enters a mode through __hpTest.setMode and waits for the end of
   the transition by the same markers as section 6b (#715): one page
   helper used by both. Oracles and the 59 check names are unchanged.

Witnesses: d5bdfde9 selects both iso smokes with no "unproven"; the same
fixture without context lines is unproven again; attribution disabled
reds both AC1 units. stageSize: null in an overlayFit call reds the first
#732 test; a direct resolveIsoOverlayFitEnvelope({...}) reds the third.
smoke_backdrop is green normally and with animation frames slowed to 60
and 150 ms; a stage animation that never ends fails with a named error.

Issue: #754
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Claude
2026-10-01 12:24:59 +00:00
committed by claude[bot]
parent da8edb1f0e
commit d8e09cd1a0
8 changed files with 387 additions and 63 deletions
+10 -5
View File
@@ -50,14 +50,19 @@ const wallRect = (x0, y0, x1, y1) => [[[[x0, y0], [x1, y0], [x1, y1], [x0, y1]]]
* typechecks this file: a field nothing reads — a zoom view, a stage size,
* decoration layers, a selection, a ground radius — fails there instead of
* pretending to be an input. Every scene fixture reaches the builder through
* `overlayScene` or a declaration of this type.
* `overlayScene` or a declaration of this type. #754: the overlay fit input
* goes the same way — every `resolveIsoOverlayFitEnvelope` fixture through
* `overlayFit` (`OverlayFitFixture`); #741 removed its `stageSize`.
*
* @typedef {{ [K in keyof import('../src/iso-scene-render.js').IsoOverlaySceneInput]?: unknown }} OverlaySceneFixture
* @typedef {{ [K in keyof import('../src/iso-scene-render.js').IsoOverlayRenderEntry]?: unknown }} OverlayEntryFixture
* @typedef {{ [K in keyof import('../src/iso-scene-render.js').IsoOverlayFitEnvelopeInput]?: unknown }} OverlayFitFixture
*/
/** @param {OverlaySceneFixture} input */
const overlayScene = (input) => buildIsoOverlayRenderScene(input);
/** @param {OverlayFitFixture} input */
const overlayFit = (input) => resolveIsoOverlayFitEnvelope(input);
const room = (id, x0, y0, x1, y1) => ({
id,
@@ -177,18 +182,18 @@ test('#713 K8: overlay fit is the structure plus visible tiles, no #651 nudge re
const w = bounds.h * aspect;
return { x: bounds.x - (w - bounds.w) / 2, y: bounds.y, w, h: bounds.h };
};
const fitted = resolveIsoOverlayFitEnvelope({
const fitted = overlayFit({
baseBounds: { x: 0, y: 0, w: 100, h: 100 }, entries: [entry], targetView,
});
assert.ok(fitted);
assert.deepEqual(fitted.bounds, { x: 0, y: 0, w: 105, h: 100 },
'the tile edge at x=105 is the only growth: no 48 CSS px reserve around it');
assert.deepEqual(fitted.view, targetView(fitted.bounds));
const repeated = resolveIsoOverlayFitEnvelope({
const repeated = overlayFit({
baseBounds: { x: 0, y: 0, w: 100, h: 100 }, entries: [entry], targetView,
});
assert.deepEqual(repeated, fitted, 'the canonical envelope is deterministic');
const otherRoom = resolveIsoOverlayFitEnvelope({
const otherRoom = overlayFit({
baseBounds: { x: 0, y: 0, w: 100, h: 100 }, entries: [entry],
targetView, ownerId: 'other-room',
});
@@ -216,7 +221,7 @@ test('#725 AC3/#741: the overlay fit bounds depend only on scene.frame and the t
const frame = { x: 0, y: 0, w: 100, h: 100 };
// The card's targetView: a contain-fit of the frame at the stage aspect.
const fitAt = (aspect) => (bounds) => fitView([bounds.x, bounds.y, bounds.w, bounds.h], aspect);
const fit = (baseBounds, aspect) => resolveIsoOverlayFitEnvelope({ baseBounds, entries, targetView: fitAt(aspect) });
const fit = (baseBounds, aspect) => overlayFit({ baseBounds, entries, targetView: fitAt(aspect) });
// Portrait, square, a 1000×500 and a 320×180 stage, the frame's own aspect (`_isoScene`).
const aspects = [0.5, 1, 1000 / 500, 320 / 180, frame.w / frame.h];
const results = aspects.map((aspect) => {