From 8f29b88cc94741ea1dc7b4c6582e863135a57f95 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Thu, 24 Sep 2026 13:22:43 +0200 Subject: [PATCH] Make the actor smoke reach every sub pixel edge position again Since #225 the game view opens at 100 % instead of fit. The actor walks 150 px/s, so its top edge only lands on .0 or .5 of a pixel. The check for the frame top edge needs 0.6 to 0.9 and gave up in 6 of 7 CI runs. The picture itself was fine: the pixels above and at the edge were the same in every red run. - While nudging the actor into place the smoke slows time to 0.08 with __gm.setTimeScale. The actor then moves 0.2 px per frame and can reach any position inside a pixel. - Time scale goes back to 1 before the edge is measured. Tests: - Old smoke on the same seeded project fails locally like CI (fraction 0.08). - New smoke passed three times in a row (fraction 0.80, 21 checks). - The actor project file was saved first and restored byte exact. Co-Authored-By: Claude Opus 5.5 --- tools/smoke-actor.mjs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/smoke-actor.mjs b/tools/smoke-actor.mjs index 91081de2..79841a18 100644 --- a/tools/smoke-actor.mjs +++ b/tools/smoke-actor.mjs @@ -38,7 +38,21 @@ const walkOf = (e) => const rowOfFrame = (texture) => Number(String(texture).split('-')[1]); await runChecks({ projectId, baseUrl, port: 9341, shotPrefix: 'gm-actor' }, async (t) => { - const { check, sleep, json, shot, entity, play, stop, navigate, exceptions, holdKey, keyDown, keyUp } = t; + const { + check, + sleep, + json, + evaluate, + shot, + entity, + play, + stop, + navigate, + exceptions, + holdKey, + keyDown, + keyUp, + } = t; await navigate(); @@ -257,10 +271,13 @@ await runChecks({ projectId, baseUrl, port: 9341, shotPrefix: 'gm-actor' }, asyn // 12. A frame's top edge shows nothing of the cell above it, wherever the edge lands inside a // pixel. Drawn from the sheet, a partly covered edge pixel read the boots of the cell above – // the hairline over the head. Nudged upwards until the edge sits in the lower half of a pixel. + // At the game view's 100% a frame at full speed moves 2.5 px, so the edge only ever lands on + // .0 or .5. Slowed to 0.2 px a frame, it walks through every part of a pixel. const topEdge = async () => { const r = await json(`__gm.gameRect('oplayer')`); return { cx: Math.round(r.x), y0: r.y - r.h / 2 }; }; + await evaluate('__gm.setTimeScale(0.08)'); let edge; let nudges = 0; do { @@ -269,6 +286,7 @@ await runChecks({ projectId, baseUrl, port: 9341, shotPrefix: 'gm-actor' }, asyn edge = await topEdge(); nudges += 1; } while (nudges < 40 && !(edge.y0 % 1 > 0.6 && edge.y0 % 1 < 0.9)); + await evaluate('__gm.setTimeScale(1)'); const facingUp = await entity('oplayer'); const rowAt = async (y) => json(`[-8,-6,-4,-2,0,2,4,6,8].map((dx) => __gm.pixelAt(${edge.cx} + dx, ${y}))`).then((p) => p.join(' '));