fix(ground): synchronize act-scene renderStarted with simulation clock domain and guard bounce height

This commit is contained in:
troytt 2026-09-24 09:34:28 +00:00
parent 1568fcbd10
commit cef09dbd81
3 changed files with 22 additions and 4 deletions

View File

@ -148,12 +148,11 @@ export function resolveGroundItemMetadata(item: any, specifiedAmount?: number):
/** Calculate parabolic bounce height h(t) at timestamp now. */ /** Calculate parabolic bounce height h(t) at timestamp now. */
export function calculateBounceHeight(bounceState: GroundItemBounceState, now: number): number { export function calculateBounceHeight(bounceState: GroundItemBounceState, now: number): number {
const elapsed = now - bounceState.startTime const elapsed = now - bounceState.startTime
if (elapsed <= 0) return 0 if (elapsed <= 0 || elapsed >= bounceState.durationMs) return 0
if (elapsed >= bounceState.durationMs) return 0
const p = elapsed / bounceState.durationMs const p = elapsed / bounceState.durationMs
// Parabolic equation: h(p) = 4 * H * p * (1 - p) // 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))
} }
/** /**

View File

@ -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) { for (const gItem of engine.groundItems.all) {
pushEntity(gItem.x, gItem.y, () => { pushEntity(gItem.x, gItem.y, () => {
drawGroundItem(renderer, gItem, renderStarted, itemsAtlasHandle) drawGroundItem(renderer, gItem, groundSimTime, itemsAtlasHandle)
}) })
} }

View File

@ -438,6 +438,24 @@ describe('Milestone M11.3 (Issue #406) — Ground Item & Gold Spawning, Isometri
expect(entity.bounceState).toBeNull() expect(entity.bounceState).toBeNull()
expect(manager.getRenderOffset(entity, 600)).toEqual({ x: 0, y: 0 }) 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', () => { describe('4. 2:1 Isometric Diamond Grid Scatter Algorithm Parity', () => {