diff --git a/tools/e2e/41-physics.mjs b/tools/e2e/41-physics.mjs index 42eafbc8..a83b0442 100644 --- a/tools/e2e/41-physics.mjs +++ b/tools/e2e/41-physics.mjs @@ -290,33 +290,60 @@ export async function physics(t) { await mouse('mouseMoved', from.x, from.y + ((to.y - from.y) * i) / 10, 'left', { buttons: 1 }); await sleep(16); } + const target = REST - LIFT; const crateY = "__gm.world()?.entities.find((e) => e.objectId === 'phys-crate')?.worldY"; - const sampleCrate = async (ms) => { - const ys = []; - const start = Date.now(); - while (Date.now() - start < ms) { - ys.push(await evaluate(crateY)); - await sleep(50); - } - return ys; - }; - const held = await sampleCrate(1000); + // The last move reaches the body a few frames later: preview, physics step, drawn one step + // behind. Reading right away raced it and caught the ninth move's height (#282). + const arrived = await waitUntil(`Math.abs(${crateY} - ${target}) < 1.5`, { + timeout: 2000, + label: 'the crate reached the pointer', + }).then( + () => true, + () => false, + ); + // Every frame recorded in the page, so the count follows the frames and not the round trips. + await evaluate(`(() => { + const out = (window.__dragSamples = []); + const sample = () => { + out.push({ t: performance.now(), y: ${crateY} }); + if (!window.__dragSamplesDone) requestAnimationFrame(sample); + }; + window.__dragSamplesDone = false; + requestAnimationFrame(sample); + })()`); + await sleep(1000); await shot('41-drag-while-playing'); const dragInfo = { grab, selected: (await state()).selection?.objectId }; + const holdEnd = await evaluate('performance.now()'); await mouse('mouseReleased', to.x, to.y, 'left'); - const released = await sampleCrate(150); + const letGo = await evaluate('performance.now()'); + await sleep(300); + await evaluate('window.__dragSamplesDone = true'); + const dragSamples = await json('window.__dragSamples'); const log = (await json('__gm.world()'))?.log ?? []; await stop(); - const target = REST - LIFT; + const held = dragSamples.filter((s) => s.t < holdEnd).map((s) => s.y); + const released = dragSamples.filter((s) => s.t >= letGo); + // 200 ms of free fall from rest is under 20 below the hold, a body let go at speed is not. + const fell = released.filter((s) => s.t - letGo <= 200).at(-1)?.y; check( 'a body dragged while playing hangs where the pointer holds it and is let go at rest', - held.length > 10 && + arrived && + held.length >= 30 && held.every((y) => Math.abs(y - target) < 1.5) && - Math.abs(released[0] - target) < 3 && - released.at(-1) > target + 1 && - released.at(-1) < target + 40 && + Math.abs(released[0]?.y - target) < 3 && + fell > target + 1 && + fell < target + 40 && !log.some((line) => line.includes('moved directly')), - JSON.stringify({ target, held: [held[0], held.at(-1)], released, log, dragInfo }), + JSON.stringify({ + target, + arrived, + held: { frames: held.length, first: held[0], last: held.at(-1) }, + released: released.slice(0, 3).map((s) => s.y), + fell, + log, + dragInfo, + }), ); const bodies = [crateBody?.id, ballBody?.id].filter(Boolean);