From cef09dbd81577fe8857307cc2b52756cb856c3d6 Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Thu, 24 Sep 2026 09:34:28 +0000 Subject: [PATCH] fix(ground): synchronize act-scene renderStarted with simulation clock domain and guard bounce height --- src/game/ground-items.ts | 5 ++--- src/scene/act-scene.ts | 3 ++- tests/ground-items-scatter-bounce.test.ts | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/game/ground-items.ts b/src/game/ground-items.ts index a4a2f9a..f4c0702 100644 --- a/src/game/ground-items.ts +++ b/src/game/ground-items.ts @@ -148,12 +148,11 @@ export function resolveGroundItemMetadata(item: any, specifiedAmount?: number): /** Calculate parabolic bounce height h(t) at timestamp now. */ export function calculateBounceHeight(bounceState: GroundItemBounceState, now: number): number { const elapsed = now - bounceState.startTime - if (elapsed <= 0) return 0 - if (elapsed >= bounceState.durationMs) return 0 + if (elapsed <= 0 || elapsed >= bounceState.durationMs) return 0 const p = elapsed / bounceState.durationMs // Parabolic equation: h(p) = 4 * H * p * (1 - p) - return 4 * bounceState.peakHeightPx * p * (1 - p) + return Math.max(0, 4 * bounceState.peakHeightPx * p * (1 - p)) } /** diff --git a/src/scene/act-scene.ts b/src/scene/act-scene.ts index 1b93ebf..f16e049 100644 --- a/src/scene/act-scene.ts +++ b/src/scene/act-scene.ts @@ -6604,9 +6604,10 @@ if (typeof window !== 'undefined') { ) } + const groundSimTime = (engine.world.tick + ((engine.world as any).alpha ?? 0)) * 40 for (const gItem of engine.groundItems.all) { pushEntity(gItem.x, gItem.y, () => { - drawGroundItem(renderer, gItem, renderStarted, itemsAtlasHandle) + drawGroundItem(renderer, gItem, groundSimTime, itemsAtlasHandle) }) } diff --git a/tests/ground-items-scatter-bounce.test.ts b/tests/ground-items-scatter-bounce.test.ts index 0a6a0dd..d7ea6f5 100644 --- a/tests/ground-items-scatter-bounce.test.ts +++ b/tests/ground-items-scatter-bounce.test.ts @@ -438,6 +438,24 @@ describe('Milestone M11.3 (Issue #406) — Ground Item & Gold Spawning, Isometri expect(entity.bounceState).toBeNull() expect(manager.getRenderOffset(entity, 600)).toEqual({ x: 0, y: 0 }) }) + + it('defensively guards calculateBounceHeight against non-positive or expired elapsed times', () => { + const bounce = { + startTime: 1000, + durationMs: 400, + peakHeightPx: 24, + } + + // Negative elapsed (sub-frame jitter / out of order): returns 0, never negative + expect(calculateBounceHeight(bounce, 900)).toBe(0) + expect(calculateBounceHeight(bounce, 999.9)).toBe(0) + // Exactly at start (t = 1000): returns 0 + expect(calculateBounceHeight(bounce, 1000)).toBe(0) + // At duration boundary (t = 1400): returns 0 + expect(calculateBounceHeight(bounce, 1400)).toBe(0) + // Far in future: returns 0 + expect(calculateBounceHeight(bounce, 2000)).toBe(0) + }) }) describe('4. 2:1 Isometric Diamond Grid Scatter Algorithm Parity', () => {