Summary
LevelSystem animates level elevation with an unclamped lerp factor:
// packages/viewer/src/systems/level/level-system.ts
obj.position.y = lerp(obj.position.y, targetY, delta * 12)
delta * 12 is a per-frame interpolation factor, so it must stay in [0, 1].
It exceeds 1 as soon as a frame takes longer than ~83 ms (12 fps) and exceeds 2
past ~167 ms (6 fps). Above 2 the iteration diverges: each frame overshoots the
target further than the last, and the level's position.y runs away
exponentially within a dozen frames.
Impact
The level (and everything parented to it — walls, doors, windows, roofs) is
translated to a coordinate like -3.6e+22 and effectively disappears from the
scene. The scene graph is intact; only the rendered transform is broken, so it
looks like "the upper floor never loads".
This does not reproduce on a desktop GPU at 60 fps. It reproduces reliably on:
- headless rendering (SwiftShader / software WebGL), which is what CI-style
screenshot checks use;
- low-end phones and thermally throttled devices;
- any tab that was backgrounded and resumes with a large
delta.
Measured
Two-storey building, viewer at ~1–2 fps (software renderer). Reading the scene
registry after the scene settled:
1 этаж (level 0): y = 0.00 visible=true, 45 children registered
2 этаж (level 1): y = -3.5854908156330287e+22
Крыша (level 2): y = -7.170981631266057e+22
getLevelElevations() returns the correct baseY (0 / 2.7 / 5.4) — the
divergence is purely in the lerp.
Suggested fix
Clamp the factor:
obj.position.y = lerp(obj.position.y, targetY, Math.min(1, delta * 12))
The same unclamped pattern appears in
packages/viewer/src/components/viewer/glb-scene.tsx:
node.position.y = walkthroughMode ? targetY : lerp(node.position.y, targetY, delta * 12)
Happy to open a PR if that's useful.
Version
@pascal-app/viewer@1.0.0-beta.4
Summary
LevelSystemanimates level elevation with an unclamped lerp factor:delta * 12is a per-frame interpolation factor, so it must stay in[0, 1].It exceeds 1 as soon as a frame takes longer than ~83 ms (12 fps) and exceeds 2
past ~167 ms (6 fps). Above 2 the iteration diverges: each frame overshoots the
target further than the last, and the level's
position.yruns awayexponentially within a dozen frames.
Impact
The level (and everything parented to it — walls, doors, windows, roofs) is
translated to a coordinate like
-3.6e+22and effectively disappears from thescene. The scene graph is intact; only the rendered transform is broken, so it
looks like "the upper floor never loads".
This does not reproduce on a desktop GPU at 60 fps. It reproduces reliably on:
screenshot checks use;
delta.Measured
Two-storey building, viewer at ~1–2 fps (software renderer). Reading the scene
registry after the scene settled:
getLevelElevations()returns the correctbaseY(0 / 2.7 / 5.4) — thedivergence is purely in the lerp.
Suggested fix
Clamp the factor:
The same unclamped pattern appears in
packages/viewer/src/components/viewer/glb-scene.tsx:Happy to open a PR if that's useful.
Version
@pascal-app/viewer@1.0.0-beta.4