Skip to content

Fix pass: walkthrough, deck-coupled wall items, live-preview elevation, room snap modes, import validation#537

Merged
wass08 merged 10 commits into
mainfrom
fix/walkthrough-wall-slab-placement-pass
Jul 23, 2026
Merged

Fix pass: walkthrough, deck-coupled wall items, live-preview elevation, room snap modes, import validation#537
wass08 merged 10 commits into
mainfrom
fix/walkthrough-wall-slab-placement-pass

Conversation

@wass08

@wass08 wass08 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

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

  • Stuck on upper levels (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 (derived baseY === 0), matching the baked-GLB viewer policy.
  • Start button insta-cancelling (4546bcb6): entering with a persisted orthographic camera swaps to perspective, which recreated the interaction callbacks and re-ran the pointer-lock effect — its cleanup called exitPointerLock(), 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)

  • Wall items rode deck height (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 pass curveOffset/thickness/supportSlabId so their cursor agrees with the rendered frame.
  • Jumpy multi-selection / preset drags (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 honor useLiveNodeOverrides (group moves) and fold useLiveTransforms deltas (slab move tool, room-preset stamp), and FloorElevationSystem reapplies 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

  • Shift snap modes (78cd7a45): the host app's room stamp runs with tool='room', which has no registry entry, so snapContextOf resolved 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

  • Plugin nodes blocked import (8bdacc70, bef36cf3): trees:tree ids inside level.children hard-failed the static children union — while the same JSON loads fine from the DB (setScene never 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 under stats.pluginTypes, no unknown-types warning. Unregistered types still warn.

Tests

  • core 1,498 / nodes 875 / editor 449 — all green; editor package tsgo --noEmit clean.
  • New regression coverage: two-level fallback floors, deck-adjacent wall election (incl. pointer cap under a capped-away deck), live-override + live-transform support queries, room tool snap context, build-JSON plugin-children import, registered-plugin-kind validation.

🤖 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 async requestPointerLock rejections 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, and supportSlabId into wall elevation queries. spatialGridManager merges useLiveNodeOverrides and useLiveTransforms into effective slab geometry during live preview (skipping the rendered-polygon cache), and FloorElevationSystem reapplies floor lift every frame for nodes with live overrides/transforms to avoid Y flicker on drags.

Room stamptool='room' maps to polygon snap context so Shift cycling and the HUD chip work during room-preset placement.

ImportvalidateBuildJson strips 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.

wass08 and others added 10 commits July 23, 2026 11:18
…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>
@wass08
wass08 merged commit f6d28f6 into main Jul 23, 2026
3 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit de39a87. Configure here.

@wass08
wass08 deleted the fix/walkthrough-wall-slab-placement-pass branch July 23, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant