Fix LevelUpMarquee digit lock on non-zero scene clocks

- Stamp `c.lockAt` as elapsed beat time instead of an absolute scene-clock value, so `el >= c.lockAt` holds and the slots actually lock onto the final level during a live session (previously they kept cycling random digits)
- Add regression test 6 covering a marquee on a scene clock at 60s
- Refresh marquee dev screenshots
- Add trestle-q4-04/05 speech assets
This commit is contained in:
Brian Fertig 2026-09-19 19:16:45 -06:00
parent 0bd93369a5
commit d9ba476d57
11 changed files with 30 additions and 1 deletions

Binary file not shown.

Binary file not shown.

View File

@ -17,6 +17,9 @@
* ones.
* - BUFFER — a level-up landing MID-BEAT is buffered and plays right
* after the live one (the queue never drops a level).
* - LOCK — the digit slots lock onto the FINAL level on any scene
* clock (the lock stamps are ELAPSED beat time, not scene
* time — a live session's clock must lock like a zero one).
* - TEARDOWN — destroy() ends the beat and every created object dies
* with the scene (a scene mid-shutdown can't wedge it).
*
@ -263,4 +266,25 @@ const settled = (m) =>
console.log('ok 5 — the cap level plays the MAXED line');
}
// ---------------------------------------------------------------------------
// 6 — LOCK ON A LIVE CLOCK (the digit locks compare ELAPSED beat time, so
// a scene whose clock is minutes in must lock the same as one at zero —
// the regression where c.lockAt was stamped as an ABSOLUTE scene time
// and `el >= c.lockAt` never held, leaving the slots cycling random
// values off the level the player actually reached)
// ---------------------------------------------------------------------------
{
const scene = makeScene();
scene.time.now = 60000; // a real session's clock, not the zero origin
const m = new LevelUpMarquee(scene);
m.play({ from: 7, to: 9 });
// Step just past the last lock (the rightmost locks at T.lockBase, the
// leftmost at T.lastLock) and check the slots read the FINAL digits.
step(m, scene, () => scene.time.now - m.t0 >= m.T.lastLock + 200, 5000);
assert.equal(m._digitCols[0].gt.main.text, '0', 'the left slot locked onto the final digit');
assert.equal(m._digitCols[1].gt.main.text, '9', 'the right slot locked onto the final digit');
step(m, scene, () => settled(m), 20000);
console.log('ok 6 — the digit locks land on a non-zero scene clock');
}
console.log('\nmarquee: all checks passed');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -402,7 +402,12 @@ export class LevelUpMarquee {
for (const p of newBoard.parts) p.gt.setVisible(false);
this._digitCols.forEach((c, i) => {
c.final = c.ch;
c.lockAt = this.t0 + this.T.lockBase + (D - 1 - i) * 140; // i = from the left
// ELAPSED ms since the beat start (update() compares it against
// el = time - t0) — NOT a scene-clock stamp: on a live session
// s.time.now is minutes in, and an absolute stamp would make
// `el >= c.lockAt` false for the whole beat, so the digit never
// locks and the slots keep cycling random values off the level.
c.lockAt = this.T.lockBase + (D - 1 - i) * 140; // i = from the left
c.bucket = -1;
c.phase = i * 1.7;
});