Fix pass: walkthrough, deck-coupled wall items, live-preview elevation, room snap modes, import validation#537
Merged
Conversation
…e coverage An elevated deck drawn against a house wall covers the wall's outer face line end-to-end (boundary contact counts), so the max-across-polylines election handed the wall origin to the deck: every wall-hosted window/door rode along whenever the deck height changed, and placement local-Y was clamped above the deck top. Elect from the carrying profile instead (per arc segment: highest support per face, min across supported faces), with the pointer cap applied inside the profile so a capped-away deck still falls back to the floor that carries the wall. Also pass curveOffset/ thickness/supportSlabId at the window/door tool query sites so their cursor agrees with the rendered wall frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Group drags publish translated slab polygons and wall endpoints to useLiveNodeOverrides only; the committed spatial index made floor items and walls re-elect support against the pre-drag slab footprint, so multi-selection moves and room-preset placement jumped vertically until the validating click committed the batch. Support queries now read live-effective slab/wall records and bypass the rendered-polygon cache while a slab or wall on the level has an override; the committed cached path stays the fast path otherwise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The host app's room stamp drives placement with tool='room', which has no registry entry, so snapContextOf resolved null: Shift never cycled the snapping mode and the HUD chip stayed hidden during room preset placement. A tool-level context map hands the stamp the no-angle polygon set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…evels Every slab-less visible level got a >=30x30m opening-free fallback floor box at its elevation, so a walkthrough spawned on an upper level (the no-spawn-node fallback ray picks the highest surface) stood on a phantom plane it could never descend from. Match the baked-GLB viewer policy: only the lowest level of each building (derived baseY === 0) gets the fallback; upper levels rely on their real slabs, whose stair openings are cut into the geometry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The slab move tool and the room-preset stamp publish a translation DELTA to useLiveTransforms (no polygon override), so the spatial index still elected support against the slab's committed footprint: furniture riding a room-preset preview (or sitting on a dragged deck) dropped to ground under the visually-moved deck until the validating click. Effective slab records now apply the live delta to polygon/holes/elevation, mapped exactly once at each query's loop entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FloorElevationSystem only wrote mesh Y for dirty nodes, but the React commit that rebinds a dragged node's base-Y group position can land between frames, after priority-2 systems consumed the dirty mark — the lift then vanished until the next pointer tick re-dirtied the node, blinking the Y of items dragged over elevated slabs. Nodes holding a live override or transform now get the lift reapplied every frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ough Entering walkthrough with a persisted orthographic camera swaps it to perspective, which recreates the interaction callbacks and re-ran the pointer-lock effect: its cleanup called exitPointerLock, and the unlock handler read that as "user left walkthrough" — instantly cancelling the fresh entry and arming the browser's ~1.25s re-lock cooldown (hence needing multiple button presses). The effect is now mount-stable (the changing callback rides a ref, deps down to [gl]), and the entry lock request swallows async cooldown rejections like the P-resume path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exports from projects with plugins carry nodes like trees:tree whose ids sit in level.children; the import validator parsed parents against the static children id union, so one tree id hard-failed the level schema and blocked the whole import — while the same data loads fine from the DB (setScene never runs this gate). Parents are now validated against a copy with non-static-schema child ids filtered out; those nodes keep surfacing through the unknown-types warning and the imported payload is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lidation Nodes of runtime-registered plugin kinds (trees:tree, trees:grass) were lumped into the unknown-types warning even when the plugin is loaded. The validator now consults the node registry: registered kinds validate against their own registered schema (corrupt plugin nodes still block), count under stats.pluginTypes, and raise no warning — only genuinely unregistered types do, which stays correct for hosts without the plugin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit de39a87. Configure here.
| if (!dirtyNodes.has(id as AnyNodeId)) applyLift(id as AnyNodeId) | ||
| }) | ||
| transforms.forEach((_transform, id) => { | ||
| if (!dirtyNodes.has(id as AnyNodeId) && !overrides.has(id)) applyLift(id as AnyNodeId) |
There was a problem hiding this comment.
Slab move skips item lift
Medium Severity
During a slab move preview, only the slab id is published to useLiveTransforms. The per-frame applyLift loop walks override/transform keys, not floor-placed nodes on that level, so items riding the deck can keep a stale mesh Y while support queries already use the translated footprint.
Reviewed by Cursor Bugbot for commit de39a87. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Bug-fix pass driven by community testing on :3005. Every fix ships with a regression test verified to fail on the pre-fix code.
Walkthrough
340622c1): every slab-less visible level synthesized a ≥30×30 m opening-free fallback collider floor at its elevation; a walkthrough spawned on an upper level (the no-spawn fallback ray picks the highest surface) stood on a phantom plane it could never descend from. Fallback floors now only generate for the lowest level of each building (derivedbaseY === 0), matching the baked-GLB viewer policy.4546bcb6): entering with a persisted orthographic camera swaps to perspective, which recreated the interaction callbacks and re-ran the pointer-lock effect — its cleanup calledexitPointerLock(), read by the unlock handler as "user left walkthrough". The involuntary unlock also armed Chrome's ~1.25 s re-lock cooldown, so subsequent presses silently failed. The effect is now mount-stable (callback via ref, deps[gl]) and the entry request swallows async cooldown rejections.Elevated slabs (decks)
9a4bdb31): a deck drawn against a wall covers the wall's outer face line end-to-end, so the max-across-polylines election handed the wall's frame origin to the deck — every hosted window/door moved when the deck height changed, and placement local-Y was clamped above the deck. The election now uses the carrying profile (per arc segment: highest support per face, min across supported faces), with the pointer cap applied inside the profile. Window/door tools also passcurveOffset/thickness/supportSlabIdso their cursor agrees with the rendered frame.10a395ec,2cbcdfbd,d5d45a26): support queries elected against the committed spatial index while drags publish only live previews — items and walls re-derived Y against pre-drag slab footprints until the validating click. Queries now honoruseLiveNodeOverrides(group moves) and folduseLiveTransformsdeltas (slab move tool, room-preset stamp), andFloorElevationSystemreapplies the lift every frame for nodes with live previews (a React commit could land after the frame's elevation pass and strip the lift until the next pointer tick).Room presets
78cd7a45): the host app's room stamp runs withtool='room', which has no registry entry, sosnapContextOfresolved null — Shift never cycled and the HUD chip stayed hidden. A tool-level context map gives the stamp the no-angle polygon set. (Host-side gating of the placement math lands in the private repo.)Import validation
8bdacc70,bef36cf3):trees:treeids insidelevel.childrenhard-failed the static children union — while the same JSON loads fine from the DB (setScenenever runs this gate). Parents now validate against a copy with non-static child ids filtered (imported payload untouched), and runtime-registered plugin kinds are first-class: validated with their own registered schema, counted understats.pluginTypes, no unknown-types warning. Unregistered types still warn.Tests
tsgo --noEmitclean.🤖 Generated with Claude Code
Note
Medium Risk
Touches spatial support election, live-preview paths used every frame during drags, and build import validation—behavioral changes in core geometry and auth-adjacent walkthrough input, but well-covered by new regression tests.
Overview
Community-testing bug-fix pass with regression tests across core, editor, nodes, and viewer.
Walkthrough — Fallback collider floors are generated only for the lowest level per building (
baseY === 0), so upper slab-less levels no longer trap the player on phantom planes. Pointer-lock setup is mount-stable ([gl]deps, interact callback via ref) and asyncrequestPointerLockrejections are swallowed so ortho→perspective entry does not instantly cancel walkthrough or hit the browser re-lock cooldown.Slab / wall support — Wall slab election now uses a carrying profile (per segment: max per face, then min across faces) so an elevated deck that only brushes the outer face no longer lifts the wall origin or hosted doors/windows; door/window tools pass
curveOffset,thickness, andsupportSlabIdinto wall elevation queries.spatialGridManagermergesuseLiveNodeOverridesanduseLiveTransformsinto effective slab geometry during live preview (skipping the rendered-polygon cache), andFloorElevationSystemreapplies floor lift every frame for nodes with live overrides/transforms to avoid Y flicker on drags.Room stamp —
tool='room'maps to polygon snap context so Shift cycling and the HUD chip work during room-preset placement.Import —
validateBuildJsonstrips non-static child ids from parent schema checks (plugin children no longer block import), validates runtime-registered plugin kinds with their schemas (stats.pluginTypes), and still warns on unknown unregistered types.Reviewed by Cursor Bugbot for commit de39a87. Bugbot is set up for automated code reviews on this repo. Configure here.