Skip to content

One building mode, light room labels and a unified viewer - #1019

Merged
wass08 merged 8 commits into
mainfrom
feat/merge-structure-furnish
Oct 8, 2026
Merged

wass08 merged 8 commits into
mainfrom
feat/merge-structure-furnish

Conversation

@wass08

@wass08 wass08 commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Simplifies how the editor and viewers handle levels, rooms and selection.

One building mode. The editor phase was Site / Structure / Furnish, but clicks switched it to the clicked node's home, so the tab followed the last click, 2D and 3D disagreed, and some handles only existed on Structure. Phase is now site | building:

  • No click changes it, except entering the building from Site. In 2D or 3D, a click on the house from Site selects the building.
  • Everything on the active level is clickable in 2D and 3D: rooms first, then drill down.
  • Outline editors and 2D opening placement depend only on select mode.
  • Keys: 1 = Site, 2 = building, F opens the catalog; B/P/M/Z just arm their tools.
  • A stored structure/furnish preference reads back as building.
  • resolveNodeSelectionTarget, emitCanvasNodeSelection and the selection:canvas-node-click event are removed.

One rooms list, light room labels. The Elements | Rooms layer split (structureLayer) is removed.

  • The Scene panel lists Units, then Rooms, then Elements.
  • Rooms on the active level show a small pill (colour dot, name, area in the active unit) in 2D and 3D when nothing but the site, building or level is selected.
  • Pills float 1.25 m above the floor, declutter when they overlap, and fade in and out. Hover and click go through the canvas's zone rule.
  • Tinted zone volumes remain only for unit-membership painting. Drawn zones look like rooms.
  • The zone toolbar button is gone; Z still works.

Room colours. A colour is picked once at creation from the room's id (zoneColorForSeed, newZone) and stored; existing rooms are never recoloured.

Viewers. The public viewer (baked and parametric), embeds and the editor's Preview share one rules module (viewer-selection) with two hit sources and one header:

  • Floor or wall click → room; second click → element. An empty click clears but keeps the floor.
  • Breadcrumb Building › Floor › Room › Item, with framing on crumb, sidebar and pill clicks (camera-controls:frame).
  • Sidebar Units / Rooms / Zones, and a hover-name pill.
  • The editor's pills replace tinted volumes.
  • Preview saves and restores the editor's selection, focused unit, tool and camera.

Editor app. Streetscape is pinned to a one-line fix (drop setPhase('structure') / setStructureLayer); upstream sudhir9297/streetscape-pascal-plugin#1.

How to test

  1. Click a sofa, a wall, then stairs in 3D and 2D → the mode stays Building.
  2. From Site, click the house in 2D or 3D → the building is selected and you're inside.
  3. With nothing selected → room pills with distinct colours. Select a wall → pills fade out. Hover a pill → the room highlights; click it → the room is selected.
  4. Draw a zone → it gets a pill like a room, with no tint.
  5. Preview → click a floor (selects the room), an item (breadcrumb shows the room), a sidebar room (camera frames it) → exit → the editor is exactly as before.

Automated: editor, nodes, core, viewer and mcp suites pass locally.

Screenshots / screen recording

Not added yet.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code

https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X


Note

Medium Risk
Broad changes to editor phase, 2D/3D/preview selection, camera framing, and zone creation colors affect core interaction paths; regressions are likely in edge cases rather than data/security.

Overview
Unifies editor navigation around site and building, dropping the old Structure/Furnish split and the Elements vs Rooms structureLayer. Build tools and palette activations now set phase: 'building' only; selection no longer auto-switches phase. From site, clicks on the building (2D or 3D via enterBuildingFromCanvas / enterBuildingFromPlanHit) enter the building instead of routing through structure/furnish targets.

Room UX adds decluttered name/area pills in 2D and 3D (RoomLabels2D/3D) when the level is at rest (select idle, only site/building/level context selected). Zone fills are tinted volumes only during unit-membership painting; otherwise zones behave like rooms (outline highlight, shared clickZoneArea / hoverZoneArea). The old 3D zone label editor system is removed.

Core introduces newZone and zoneColorForSeed so new rooms get a deterministic palette color from their id unless explicitly set; structure reconcile remaps colors when zone ids are minted. camera-controls:frame and CameraControlFrameEvent support viewer-style framing.

Preview aligns with the shared viewer selection path (ViewerSelectionManager, ParametricViewerRooms): left click no longer pans the camera; opening preview frames level/building once; camera restore on exit. Streetscape dependency is repinned to a fork commit without the removed phase APIs.

Reviewed by Cursor Bugbot for commit 0eb4058. Bugbot is set up for automated code reviews on this repo. Configure here.

wass08 and others added 7 commits October 7, 2026 16:56
The editor phase was Site / Structure / Furnish, but clicks switched it to the
clicked node's home, so the tab followed the last click, 2D and 3D disagreed,
and some handles only existed on Structure. Phase is now site | building: no
click changes it except entering the building from Site; everything on the
active level is clickable in 2D and 3D (rooms first, then drill down); slab,
ceiling and zone outline editors and 2D opening placement depend only on
select mode; the Scene panel reads Elements | Rooms; 1 = Site, 2 = building,
F opens the catalog with the item tool, B/P/M/Z just arm their tools. A stored
structure/furnish preference reads back as building. resolveNodeSelectionTarget,
emitCanvasNodeSelection and the unused selection:canvas-node-click event are
removed. Docs and wiki updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X
Removes the Elements | Rooms layer split (structureLayer): the Scene panel
lists Units, then Rooms, then Elements for the active level. Rooms on the
active level show a small pill (colour dot, name, area in the active unit)
when nothing but the site, building or level is selected and no tool is
active; tinted zone volumes remain only for unit-membership painting and a
selected free-drawn zone. In 2D, clicking any part of the building from Site
selects the building and enters it, like 3D.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X
… mode

The pinned plugin called setPhase('structure') and setStructureLayer, both gone.
Pins the one-line fix on top of the previous commit (wass08 fork, upstream PR
sudhir9297/streetscape-pascal-plugin#1) until the plugin moves to its monorepo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X
…es as rooms

Rooms show distinct colours; drawn zones render like rooms (pill + outline, tint
only while painting unit membership); the zone toolbar button is gone (Z stays);
pills float 1.25 m above the floor and declutter on camera rest; the 2D plan
uses the same pills instead of multiline tags on the editor canvas (exports and
sheets keep tags). Pill hover/click go through the canvas's zone rule
(hoverZoneArea/clickZoneArea), including modifiers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X
…reation

Viewers: the public viewer (baked and parametric), embeds and the editor's
Preview share one rules module (viewer-selection) fed by two hit sources
(node events, baked GLB picks) and one header: floor/wall clicks select the
room, a second click drills to the element, items select, empty clicks clear
but keep the floor; breadcrumb Building › Floor › Room › Item with framing on
crumb/sidebar clicks (camera-controls:frame); sidebar Units / Rooms / Zones;
the editor's room pills replace tinted zone volumes. Preview saves and restores
the editor's selection, focused unit, tool and camera.

Rooms: a colour is chosen once at creation from the room's id (zoneColorForSeed)
and stored; captured rooms seed from the capture id; nothing is recoloured on
load. Pills recompute overlaps on near-rest and while moving, and fade in/out
then unmount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X
Viewer pills select and frame their room; a bottom hover pill names what a click
would select (baked, parametric, embed, Preview; hidden in walkthrough, 2D and
on touch); viewer pills fade out like the editor's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X

@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.

Stale Bugbot comment from a previous run.

useViewer.getState().setSelection({ buildingId, selectedIds: [] })
useEditor.getState().setPhase('building')
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Site entry clears the active floor

High Severity

Clicking the house from Site calls setSelection with only buildingId, and the viewer hierarchy guard then nulls levelId. The user enters Building with no active floor, so room pills, level filtering, and the floorplan lose the storey they just clicked.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a18985b. Configure here.

The WebXR plugin listens to it; canvas clicks that select a node (2D and 3D)
emit it again from emitCanvasNodeSelection (Delete mode deletes instead).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzGbHVg2Tpv4zEw9yLaT5X

@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.

There are 2 total unresolved issues (including 1 from previous review).

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 0eb4058. Configure here.

useViewer.getState().setShowZones(active)
return () => useViewer.getState().setShowZones(true)
}, [structureLayerState, isCaptureModeState])
}, [active])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Preview shows tinted zone volumes

Medium Severity

showZones now means “unit-painting volumes only”, but ZoneSystem still restores showZones to true on unmount. Opening Preview unmounts that system, so the shared viewer flag turns zone meshes back on and tinted volumes appear next to the new pills.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0eb4058. Configure here.

@wass08
wass08 merged commit 0c186e2 into main Oct 8, 2026
5 checks passed
@wass08
wass08 deleted the feat/merge-structure-furnish branch October 8, 2026 19:36
AxiomeCG added a commit to AxiomeCG/editor that referenced this pull request Oct 12, 2026
Main removed the 'zone' action-menu control and narrowed Phase to
'site' | 'building' (pascalorg#1019). The Point button was anchored on the zone
control, so it would never have rendered; it now sits after Select, before
the measurement control, where it stood. The point pick's phase type is the
same two values; a building or site is still pickable only in the site phase.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
wass08 pushed a commit that referenced this pull request Oct 13, 2026
* feat: deliver selected Bundle 2 editor improvements

* fix(core): port planPreviewSvg, which the MCP's import_plan_reference already calls

reference-construction.ts imports planPreviewSvg from @pascal-app/core/building
and draws an imported SVG plan with it (the picture the chat's import also
returns), but the helper and its export never came across, so the MCP build
failed. The helper redraws the plan from the shapes and texts the import read,
never from the agent's SVG, so an agent's markup is not handed to the host's
renderer. Its two dependencies (markedPlanSvg, referenceContours) were already
in the branch.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(editor): Point and ask follows main's one building mode

Main removed the 'zone' action-menu control and narrowed Phase to
'site' | 'building' (#1019). The Point button was anchored on the zone
control, so it would never have rendered; it now sits after Select, before
the measurement control, where it stood. The point pick's phase type is the
same two values; a building or site is still pickable only in the site phase.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(mcp): create_roof is registered once, by the shared tools

The shared tools already register create_roof from the one core contract and
operation, and construction-tools.ts still registered main's own copy, so every
MCP server threw "Tool create_roof is already registered" at start-up. Remove
the second registration and its schemas, and move its tests to the shared
contract (the answer lists segmentIds; with no arguments the roof covers the
rooms a story shell derived).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(roofs): a roof segment's fascia takes a height, a depth and its own finish

create_roof already advertised fasciaHeight and fasciaMaterialPreset (the shared
contract came across with the operation), but the roof-segment schema had no
such fields and parsing dropped them: a flat roof asked for a 0.3 m black band
got the plain 1x8. Port the schema half of the lab's change so the contract
means what it says.

- RoofSegmentNode: fasciaHeight, fasciaDepth, fasciaMaterial, fasciaMaterialPreset;
  getSegmentFasciaMaterial reads the finish (the trim slot, else the wall role's).
- The viewer sizes the boards from them; with a height set, a flat roof's boards
  rise to its top, a coping band over the deck and membrane edges.
- The segment panel gets a Fascia toggle and a height.

Not included: the lab's second half of this change (place_design by preset name).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(nodes): register the facade and panel kinds the schema already carries

The branch brings the facade and panel node schemas and the core facade
operations that write them, but not their node definitions, so builtinPlugin
registered 2 fewer kinds than AnyNode held (main's registry invariant) and the
facade/panel fixtures and frozen corpus did not cover them. This is the data
model only, not Facade Studio:

- nodes/src/facade: a data-only, hidden, non-duplicable definition.
- nodes/src/panel: the flat cladding node (geometry, floorplan, paint, slots).
  Its box comes from viewer's buildPanelGeometry and core's panelDepthOffset.
- fixtures: facade's required `set`, and the frozen pre-host-children corpus
  gains a facade and a panel row.
- reference inventory: the facade/panel reference fields and the metadata keys
  the branch's sources read (facadeOwner, facadeReleasedFrom, balcony*, plan
  reference keys, guessed.unitId...). Rows for floor-plan-import metadata the
  branch does not ship are left out, so no row is stale.

If facade tools do not ship in bundle 2, this commit and the facade/panel
schema go together.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: bring the checks that name this branch's tools and behaviour up to date

- mcp read-tool-annotations + plugin-evals/tool-annotation-justifications.json:
  the 32 tools the shared tools now serve are classified (the packet was main's).
- mcp server.test: instructions are the host's line at the end of the built-in
  guidance, not the whole text; the plan/photo and upload wording is checked.
- mcp measure-world: the node union has a facade.
- core operations.test: the circulation check names a stair with no landing in
  the bare scene; the design targets must stay informational, which is what the
  test is for.
- editor history probe: stub useThree, which Door/WindowSystem now call to
  register their materials for scene capture (this probe fails the same way in
  the lab tree).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(nodes, viewer): export subscribeWallRebuilds, import createMaterialFromPresetRef

The panel host system subscribes to wall rebuilds, which the viewer notifies
but did not export; the segment renderer's fascia finish needs the preset
material helper it was not importing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: facades are not part of bundle 2; remove the facade family

Owner decision: facade sets, their tools and nodes stay in R&D. Remove what the
branch carried of them, so nothing references code that is not shipped.

- core: agent-tools/facade.ts (preview_facade_unit, apply_facade, apply_facade_set,
  describe_facade), the facade operations (result, set-operation, targets) and
  describe_facade's operation and batch entry; building/facade*.ts and
  systems/facade/*; the Facade and Panel node schemas and their places in
  AnyNode, building.children and wall.children; the facade exports of the
  schema, building and core indexes.
- mcp: tools/facade.ts (the editor-tab facade tools) and describe_facade's
  shared-tool registration; the four tools leave the annotation test and
  plugin-evals/tool-annotation-justifications.json.
- nodes, viewer: the facade/panel node definitions registered earlier, the
  panel geometry and the wall-rebuild subscription they used.
- prompts: the plans guide no longer names a facade step (5) or apply_facade;
  create_reference_elements, correct_plan_reading, circulation and site-works
  text stop naming facade tools and pieces.
- verify_scene's `photo` input is dropped: its check (photo_facade_mismatch) is
  the facade checker and was never in the branch, so the input did nothing.
- reference inventory: facade/panel rows and the metadata keys only they read
  leave; rows for the plan-reference, balcony, coherence and fascia keys the
  branch's sources do read are classified.

Kept on purpose, and named in the PR description: straighten_facade_photo (a
photo-measuring tool paired with locate_photo, not a facade-set tool) and
create_reference_elements' outlineSteps value 'facade', the default, which leaves
a balcony step unbuilt and names the wall behind it (balconyWalls).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: the OpenAI annotation policy lists the tools the shared tools serve

Brings scripts/openai-tool-annotation-policy.ts in step with
plugin-evals/tool-annotation-justifications.json (28 tools the branch registers
that main's list did not know), without the facade tools.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(core): export unitClientBrief from agent-operations

The hosted chat's unit-layout judge reads it from @pascal-app/core/agent-operations
(the lab exports everything from unit-layouts); the branch exported only
proposeUnitLayoutsInScene.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(core): validate plugin nodes and respect vertical collision bounds

* refactor(core): make scene-tool evidence and batching optional

* feat(core): a scene preview shows another state of the document without writing it

beginScenePreview(snapshot) puts a past state on screen (the scene as it was before an agent's
turn, from the history Undo would restore) until the handle ends. History is held and the scene
is read only for its whole life, the swap is a remote change that announces no commit, and only
the nodes that differ are marked for a rebuild. Anything about to write the scene (an agent's
write, a collaborator's patch, a snapshot, a load, an undo or redo) ends it first through a
write guard, because a write onto a preview would be lost or land on the wrong state.
isScenePreviewing and subscribeScenePreview tell whoever persists the scene to stand still.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 1533e9a73adf7c9b4fdf66abde7f8bebb30f470d)
(cherry picked from commit b11f136ac346be38e21f850404d44c53c5241f0b)

* fix(editor): autosave stands still under a held scene preview

A preview is on screen, not in the document: the subscription ignores it, a save that comes due
under it waits and is written from the live scene when it ends, and the exit flush and the save
shortcut end it first so what they write is the live scene.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 8ccb1d665897b780be5e86a0eafb638d1f1d6e94)
(cherry picked from commit 75255635fbbdc3bea96db799a2b11fa622d4b2d3)

* feat(editor): Point and ask's done card asks for more and shows the before in 3D

"Change more" replaces "Hold to compare": it reopens the bubble on the same elements (a fresh
view and picture, the field focused, chips that move on from the words just sent) and sends as
its own ask; the earlier pin gives way and goes when the new ask is taken. The card's pictures
shrink to one small after thumbnail, and holding "Hold to see before" on the card, or the pin,
puts the scene as it was before that turn on screen in 3D (the history Undo would restore) with
a small "Before" tag, and lets go on release. It ends on the pointer leaving, a key, the window
losing focus, the page going away or another turn starting, and writes nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 007dbc2950743d838c604f3d4357ae3b9ec214ae)
(cherry picked from commit 3b054bc1da59fc6f3e2b4fea528fedf453ee0304)

* fix(editor): the Point and ask card closes when the pointer is on neither it nor the pin

A card that closed under the pointer (a click on Redo or Keep) never heard its leave, so its
"pointer is on me" flag stayed up and the next card never closed on its timer; the flag is
reset when the card changes and the browser's :hover is asked as well. A card's close timer also
shut the card a pin hover had just opened when the pointer went from the card to the pin, and a
card carried from under a still pointer stayed for good: it now waits 6 s for the pointer. The
leave of a pointer that walked out arrives before its move, so a carried card is told by
position (the leave reports where the pointer is, against where it was last seen), not by time.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 211b55e06ede6d30a248eda0f0206dce8b541642)
(cherry picked from commit a943bfb0b812b3260624b66de43b2968f13b56f5)

* fix(editor): the Before tag clears the editor's own controls and Change more starts empty

The tag sat under the level selector; it now sits below it. "Change more" starts from an empty
field, and the words of an ask that was sent are no longer kept as a draft for the next time the
same element is pointed at (only words that were dismissed unsent are).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit f4b64a95fb9002d14cbeae49c781a0b7dc4c6323)
(cherry picked from commit c260874be36f76bb6bc42f514edb5387589d27d2)

* fix(point-ask): scope room commits and retain Undo for writes

(cherry picked from commit 6f9424f7d5f82a31b8934e7992acf0249eaed21c)
(cherry picked from commit 0a8c657dfe3828aa42410dbca3637a3aba8d872f)

* fix(point-ask): exclude foreign writes and resolve hosted frames

(cherry picked from commit e4b220feb128dc5824b094241fc20e66f58d4eb0)
(cherry picked from commit cd91545d5a362e7d59164a5824f50e96168eb1d1)

* fix(point-ask): derive room suggestions from scoped contents

(cherry picked from commit c1e2dc5f64ca7bbc4efee4488910ad0b501e8cec)
(cherry picked from commit 3a73b74cf0051f1f127a3931045e4bfd9945c537)

* fix: honor Point calm controls and initial hover dwell

(cherry picked from commit e11ee4094bc3b0d4df16bfa90bc0c84ade59875d)
(cherry picked from commit 4120cf640eb93ec2b9fd5952252ca94b6ab2e4d0)

* fix(point-ask): hold to see before says why it cannot start, and takes its before at the ask

The hold did nothing, silently, after an agent turn. Three causes, each with a test:

- A refused hold said nothing. `compare.start` now records why (a turn is writing, history cannot
  show the before, another change holds the scene) and the card says it for a few seconds.
- A pin that said "working" while the chat was idle, or one still "sent", shut every hold for good.
  Only a pin that is working while the chat is busy counts as a turn writing.
- The before was the scene at the chat's first "working", which comes from the stream and can
  follow the turn's first write. It is taken when the chat accepts the ask (an ask the chat queues
  is marked when its own turn starts).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(point-ask): a host can say Pascal is working and retry an ask with its own pin

- `PointAskSubmitResult` gets `code: 'busy'`. While the host says Pascal is working, the bubble's
  button says so and does not send, and `sendBubble` keeps the words with the reason.
- `retryPointAsk(askId)`, next to `setPointAskBusy`: the chat's Retry on an ask sends the same
  words, targets, camera and crop as a new ask with its own pin. A finished ask keeps its pin (its
  compare and Undo stay); an errored one is replaced, as the pin's "Try again" does. It answers
  false when the pin is gone, and the host sends the words as a plain message.
- The pure part of the retry is `planRetry`, tested without the viewer.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(audio): port control-aware construction landing cues

* fix: keep group and editor shortcuts out of editable controls

* fix: preserve editable keys during group rotation

* fix: isolate remaining move and draft keyboard actions from fields

* feat(core): port authored room and wall finish readback

* fix(core): keep wall-hosted furniture planar to its face

* feat(core): report wall-bound furniture away from room walls

* fix(core): honor floor underlays and authored footprint bounds

* fix(point-ask): a turn's changes go only to the pin of the ask that is running

Every pin that said "working" took every scene commit, so an older ask whose end the pin never
heard (a dropped turn, a reload of the chat) absorbed the running ask's changes as "also changed"
and finished "done" while the ask the person clicked said "no change".

Only the ask the chat last reported "working", until it ends, takes commits, and a pin left
"working" or "asking" for a turn the chat has moved past is settled when another ask starts
working. Regression tests: three pins, two of them stale, on the same wall.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(point-ask): a pin that never heard its end is settled when the chat goes idle

The next turn (a plain message, say) writes the scene while such a pin still says "working", and
was taken for its changes. The host's busy flag going false settles every pin left "working" or
"asking", and none is the running ask until the chat reports one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(editor): follow Pascal onto the target floor

* fix(editor): keep Pascal follow targets in sight

* test(editor): ensure manual camera moves pause Pascal follow

* fix(capture): an interior view waits for the scene to build and sets aside nodes no system settles

An interior view_scene was refused on the spot as "the scene is still building" whenever the
viewer's pending-build count was above zero, and that count never reaches zero in a scene with
a pool: the pool plugin's renderer marks its node dirty on mount, its system only syncs other
nodes, and nothing clears the mark (plugin-trees clears its own kinds' marks in a priority-2
pass; a kind that declares `def.system` is expected to). The ordinary capture already waits on
the count progress-based and lets one that stopped moving go (a node no system settles); the
interior check had no wait and no release, so in such a scene it could not succeed.

- snapshot-capture.ts: waitForSceneBuilt is that wait as a function of its inputs: a count that
  keeps moving is waited for (up to two minutes), one that stops for fifteen seconds ends the
  wait and names the nodes holding it. untilSceneBuilt, the sheets' capture, uses it as before.
- thumbnail-generator.tsx: an interior capture runs the same wait first, and its readiness
  check counts everything except the nodes the wait named. Anything dirtied after the wait,
  a roof merge still queued, an unmounted root, an item whose model has not settled, pending
  textures and a reveal in progress still refuse it; a background tab now says so.
- lib/pending-scene-build.ts: the viewer's pending-build predicate and count moved out of the
  Viewer component module so they can be tested without the canvas; pendingSceneBuildCount
  takes `except`, and pendingSceneBuildNodeIds is exported.

Tests: the wait on a scripted clock (settled, moving, stalled, still moving at the cap, hidden
tab), and the count with the viewer's real floor-elevation, geometry and registered systems
running beside a plugin kind that never clears its mark (600 frames, the count stays at 1;
the same kind with a clearing system reads 0).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(point-ask): an ask sent while Pascal works is handed to the chat, which queues it

The first test refused it ("Pascal is working"); the owner asked for it to be queued as a message
is. The bubble's button says Queue again and `sendBubble` no longer gates on the busy flag: the
host takes the ask, queues it as its own turn and reports `queued`, and the pin says so until its
own turn starts (its before is the scene then, and it takes none of the running turn's changes).
A host that does not queue still answers `busy` and the bubble keeps its words.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(geometry-script): compile consecutive imports on one line

* fix: bind authored storey-height walls to the covering slab datum

* feat: report wall contacts with covering slabs in scene verification

* docs: explain explicit storey-height walls and saved-scene correction

* fix: top-anchor ceiling-mounted scripted objects

* feat: observe ceiling object bounds above attachments and in slabs

* docs: explain ceiling origins and explicit saved-object rebuilds

* fix(geometry-script): a mesh at a non-finite position fails the compile, naming the mesh

Box3.isEmpty() is false for a box with a NaN corner. A script that put a material where a
position number goes (a helper called one argument short passes the next value in its place)
compiled "successfully": the origin shift, the GLB export and the artifact store all ran on NaN
bounds, and the item schema then threw its raw validation array at the model and the person.

Right after the empty check, a box with a corner that is not finite fails the compile, before
the origin shift, export and storage. The error names the mesh (its name, or its geometry kind
and place in the script), the component and what it holds, for example "an unnamed BoxGeometry
mesh (mesh 16 of 290) has position.z = a MeshStandardMaterial". Non-finite vertices keep their
own message; an empty object keeps "no visible meshes".

Tests: the cabinet script as an agent wrote it, as a fixture: builds at its defaults and with
its display branch off, refused with it on; a named group moved to NaN; NaN vertices; no mesh.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(core): add_object refuses an object it cannot measure, before building its asset

An object whose bounds are not finite numbers (a mesh at a NaN position leaves them NaN, and
null once they crossed a JSON hop) has no size to place. addObject went on to build the asset
and the item from it, and the item schema threw its raw validation array: fifteen issues for
one cause, shown to the model and in the person's list of open items.

addObject now checks the manifest's bounds first, on the new-object and the rebuild path, and
refuses with `unmeasurable_object`. The message names the object, the params it was built with
and what to look for in the script, and says nothing was added or changed.

The compile in `@pascal-app/geometry-script` now fails such a script itself, before anything is
stored; this is the operation's own check for a manifest that arrives from a compile that did
not (a tab running an older editor build, answering the server over JSON).

Tests: three shared add_object cases, run by the core operation, the MCP tool and the chat's
tool: NaN bounds, null bounds, and a rebuild of an existing object.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(editor): the room pills' timers are called as functions, not as methods of the placer

PillPlacer defaulted `schedule = setTimeout` and `cancel = clearTimeout` and called them as
`this.schedule(...)` and `this.cancel(...)`. A browser's timers throw "TypeError: Illegal
invocation" when called with any receiver but the window, so every frame that hid or restored
a pill threw from RoomPillsLayer3D. Bun's timers do not check, which is why the injected
timers of the existing tests never saw it.

The defaults are now arrows that call the global timer as a plain function.

Test: the default timers under timers that throw "Illegal invocation" for any receiver but
the global, as a browser's do.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(nodes): a door or window with clips draws its own leaves, not a static batch instance

A door or window built from a script animates its own subtree: its `open` clip slides the leaf
groups. Unlike an item, a column or a procedural item, the door and window batch rules excluded
only an opening mid-swing of its native animation record, so a scripted one with a clip was
collected like any other: its leaf meshes were packed into the level's static batch, the sources
hidden, and the clip then moved meshes that nothing drew. The closed pose stayed on screen while
the control read open, with the node unselected (a selected node is lit and draws itself).

Doors and windows now stay out of the batch while their script registered clips, as columns do:
`itemClipRegistry.has(node.id)`. The registry entry is made with the model's settle, in the same
commit, so the first collection already sees it.

Tests: the two rules; and the real batch system over four wall doors sharing a material, three
plain (batched, the control) and one with a clip registered (its leaf keeps drawing itself).
This is upstream main's defect (the batch rules and the scripted opening came with it).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(editor): Follow Pascal puts back the cutaway, the floor and the hidden floors when it ends

Follow Pascal falls back to cutaway when no eye sees its target, selects the
floor being built and hides the floors above it. Nothing put them back: after a
build the walls stayed cut away and the upper floors hidden (the owner's walls
"looked invisible" on 3114), and the viewer persists the wall mode.

FollowPresentation (lib/follow-pascal-presentation.ts) records what the person
had before Follow's first change to each part and what Follow left, and puts
back only what is still as Follow left it. A part the person changes while
Follow is on (their wall mode, their floor, an element picked on it) is theirs:
it is kept, and Follow does not write it again until it has ended.

Follow ends when it is switched off, when the person takes the camera, when the
editor is left, and when the build is over with no turn running (the host's busy
flag, setPointAskBusy, is false). While a turn runs a build being over is not an
end: a turn runs several programs and the view holds steady between them. A
reveal that plays on after the turn is still followed and ends it. A host that
never sets the flag (a standalone editor) ends it with the build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(editor): the walkthrough operates a door or window built from a script through its mechanism

In the walkthrough every mounted door and window was taken for the walkthrough's own door and
window target (and covered, so no other target looked at it): the prompt said "open <name>" and
E or a click started the native swing, `operationState`, which the door and window systems draw
on a native leaf or sash. A door or window built from a script has neither: its leaves are the
model's own and its `open` clip is what moves them, so the swing drew nothing and the clip never
ran. Selecting it and using the action menu's Open (or E) worked, since that runs `mechanism`.

One rule, `hasNativeLeaf`: a door or window with a script is not the walkthrough's own target and
is not covered, so it falls through to the generic mechanism target, which gives the prompt (open
or close from its control), E and the close key. A scripted door with no clip has no mechanism and
now has no prompt, instead of a prompt that did nothing.

Tests: the rule and the covered set. The two resolvers sit inside the walkthrough's hook and have
no seam; the one-line `hasNativeLeaf` guard in each is not covered by a test.
This is upstream main's defect (3fd2614 scripted openings, 45ad24a).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: a scripted sliding door keeps its open clip through the compile and plays it through the door mechanism

Two tests that pin what already worked, found while tracing why a scripted sliding door did not
animate (the batch and walkthrough fixes before this one are the causes):

- compile: a sliding door module with two leaf groups and one `open` clip, as an agent wrote it
  (fixture). The manifest lists the clip, the artifact's clip has a `leaf_i.position` track per
  leaf that resolves to a node of the loaded scene, and played to its end the leaves slide apart.
- mount: the door's own renderer with its model loaded; the door mechanism's Open runs the clip
  to its open pose and Close returns the leaves, with the interactive state initialised by the
  model.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Observe wall object depth conflicts and orient new back mounts

* fix(editor): a scripted door that is open can be walked through in the walkthrough

The walkthrough stops the player at a door with a stand-in leaf box at the door's frame, built
from the node's own fields, and drops it when the door is open: `operationState` for a sliding
door, `swingAngle` for a hinged one, set by the native tween, whose end rebuilds the collider
world. A door built from a script is opened by its model's `open` clip through its mechanism,
which moves neither field and ends in no event. So its leaf box stayed in the doorway whatever
the leaves did, and nothing rebuilt the world when it opened.

This became visible with f9ca67e and is a regression of it in effect: before it, E on a
scripted door ran the native swing, which drew nothing but did move `operationState`, so the
collider dropped and the doorway was passable while the leaves stayed shut. Since it, E runs the
clip: the leaves open and the doorway stays blocked.

Two changes:
- the leaf box of a scripted door is not built while its mechanism is on (open). Closed, it is
  the same box a closed native door gets. The model's own meshes never collide (the door's
  subtree is not swept), the `cutout` box included.
- the walkthrough rebuilds the collider world when a mounted scripted door opens or closes
  (`watchScriptedDoors`, on the interactive store), as it does at the end of a native tween.

Timing differs from a native door's: the rebuild is at the toggle, not at the end of the clip,
so the doorway is passable as the leaves start to move (about 1.3 s early) and solid as they
start to close.

Tests: the collider world with a scripted door, closed (the player is stopped by the 0.06 m leaf,
not by the 1 m cutout box) and open (nothing is hit), red before; the watcher asks for a rebuild
on open and close and for nothing else, red with the subscription off.
Scripted windows are not covered: their model meshes go through the generic sweep.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: the refusals and the walk-through of a scripted door hold for a Blender build too

Both languages end in `finishBuild`, so the compile refuses a mesh at a position that is not a
number, and an empty object, the same way for a bpy build (checked through finishBuild with
language 'bpy', since a NaN transform cannot be made in a Blender export). The artifact carries
its language. A door whose source is bpy follows its mechanism in the walkthrough's collider:
solid closed, passable open.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: a roof outline's corner is bevelled past a mitre limit instead of reaching out without bound

The roof outline grows the walls' centreline polygon by half a wall, mitring each corner by
distance / (1 + dot) of the two edge normals. As the edges fold back on each other the mitre
runs away: a 30 m sliver with a 1° point came out with a 41.6 m roof segment, 0.2° with 87 m,
and an edge folding exactly onto itself divided by zero into NaN points. A corner whose mitre
would pass four times the distance (under about 29°, the limit an SVG stroke uses) now gets the
two edge-offset points instead; right angles and the ordinary 45° to 135° corners mitre as
before.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: photo location is not part of bundle 2; remove locate_photo

Owner decision: only what takes part in bundle 2 stays in the PRs. locate_photo (ranking a
floor's corners for a photo) leaves; the facade photo straightening tools stay.

- core: agent-tools/photo-location.ts and agent-operations/photo-location.ts, their entries in
  the tool and operation registries and the batch list.
- mcp: its shared-tool registration; the tool leaves the annotation test, the OpenAI
  annotation policy and plugin-evals/tool-annotation-justifications.json.
- straightening's two warnings no longer name create_reference_elements.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: the scene coherence checker is not part of bundle 2; remove it

Owner decision: only what takes part in bundle 2 stays in the PRs. The checker, its
dispute_coherence_item tool and everything that reported its items leave; the checkpoints and
the session's made list, which verify_scene's `since` reads, stay.

- core: coherence/* (the checks, the disputes, the checklist), the dispute operation and tool
  and their registrations; the wall's `roofJunction` declaration (only the checker read it;
  an old scene that has it still loads, the key is not part of the schema).
- apply-outcome no longer answers a write with a `coherence` delta; verify_scene returns no
  `coherence` checklist and `hasIssues` is its issues again; set_checkpoint no longer
  counts open issues; a checkpoint is taken with no issues and a review lists no coherence
  items. The parser still reads the `issues` older records carry, and a node's fingerprint
  still leaves out the `metadata.coherence` notes older versions kept.
- mcp: the tool's shared-tool registration and verify_scene's `coherence` output field; the
  tool leaves the annotation test, the OpenAI annotation policy and
  plugin-evals/tool-annotation-justifications.json.
- the reference inventory drops the row for the checker's metadata key.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: site works are not part of bundle 2; remove add_fence, add_site_surface and add_steps

Owner decision: only what takes part in bundle 2 stays in the PRs. The three site tools and
the fence ironwork nothing else called leave; the fence, stair and site schemas that main
already has stay.

- core: agent-tools/site-works.ts and agent-operations/site-works.ts with their registry,
  operation and batch entries; systems/fence/fence-ornament.ts (referenced by nothing).
- mcp: the three shared-tool registrations; the tools leave the annotation test, the OpenAI
  annotation policy and plugin-evals/tool-annotation-justifications.json.
- the reference inventory drops the `flatwork` row only add_site_surface wrote.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: apartments, units and circulation tools are not part of bundle 2; remove them

Owner decision: only what takes part in bundle 2 stays in the PRs. create_stairs_and_lifts,
add_entry_doors, propose_unit_layouts, apply_unit_layout and name_units leave, with the unit
layout engine and the reachability reader they stand on. The Unit and Guide schemas, the
create/list/set unit tools and the native stair tools that main already has stay.

- core: agent-operations/{circulation,entry-doors,name-units,reachability,unit-layouts},
  agent-tools/{circulation,entry-doors,unit-layouts,units} and building/unit-layout*.ts, with
  their registry, operation and batch entries and the exports only they fed (the layout
  judge type, the plan mask, the in-scene proposer). The plans guide's "copies",
  "interiors" and "apartments" steps leave with their files.
- mcp: the five shared-tool registrations; the tools leave the annotation test, the OpenAI
  annotation policy and plugin-evals/tool-annotation-justifications.json.
- the reference inventory drops the `guessed` rows only the unit guesser wrote.
- Kept for the plan commit, which still imports them: agent-operations/{level-balconies,
  unit-rooms} and building/room-habitable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: plan reading and reference construction are not part of bundle 2; remove them

Owner decision: only what takes part in bundle 2 stays in the PRs. Reading, calibrating,
matching, surveying and vectorising plans, building from them (create_reference_elements,
furnish_from_plan, correct_plan_reading) and the reference inventory (record_reference) leave,
with the reference/plan engines under them. The guide node and the native schemas main already
has stay; so do the facade photo straightening tools, create_roof and its gap bridging.

- core: agent-operations/{furnish-from-plan,plan-calibration,plan-reading,plan-survey,
  reference-elements,reference-items,vectorize-plan,level-balconies,unit-rooms,created-summary},
  agent-tools/{furnish-from-plan,inventory,plan-reference,vectorize-plan,guides},
  building/{balcony*,plan-*,reference-*,room-habitable,wall-support-in}.ts, and
  lib/preview-id, systems/wall/{wall-batch,wall-coverage}, which only those called; their
  registry, operation and batch entries and the context fields (planAnswers, planVector)
  only they filled.
- the plans guide and the connect-time reference-inputs guide leave with the tools they taught;
  the MCP server's instructions are main's again, plus the agent-guide pointer.
- scene-view: a view's note is main's text again (no inventory invitation); paint no longer
  registers build paths for the inventory.
- wall-gaps keeps what create_roof's outline needs (gapBridges); the door/outside
  classification and swing reading only plan reading used leave.
- mcp: tools/reference-construction.ts, the plan-reading hosts (judge, rasteriser,
  vectoriser, raster decoder and the options that lent them), the open-world annotation
  constant only they used; the tools leave the annotation test, the OpenAI annotation policy and
  plugin-evals/tool-annotation-justifications.json.
- the reference inventory drops the rows for the plan, outline, unit-plan and balcony keys only
  those sources wrote.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: request_upload is not part of bundle 2; remove its hints and the resolver type

Owner decision: only what takes part in bundle 2 stays in the PRs. request_upload (a hosted
tool: a file the agent uploads and passes as asset:<id>) leaves; the server's own storage
resolver and attached-file handling are the host's and stay there.

- mcp: tools/source-resolver.ts (the SourceResolver type and the upload hint) and its
  export from the package index.
- view_scene's photo input is main's text again, and the agent guide and the pascal-3d
  skill no longer tell an agent to upload a photo with request_upload; the guide test no
  longer expects the tool or lists hosted-only tools.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: trim the animation-feel and copy-pinning tests both audits agreed on

Owner decision: tests protect an observable result or an invariant, not a number from an
animation curve or a sentence of copy. Eleven files were named by both audits; where they
disagreed on a block, it stays.

- point-ask: choreography.test.ts (constants equal to themselves) is deleted; spring loses
  its pair, closed-form and snap/rest/new-parameter blocks; region loses the label,
  size-format and nearest-edge helpers; placement loses the side-order and tail-pixel
  blocks; suggestions loses the per-kind copy, placeholder and kind maps, and the room
  and wall chips assert that they change with state rather than their words.
- viewer: reveal-pose loses the drop-feel curves; reveal-schedule loses the scatter, natural
  pace and group-key blocks; construction-dust loses the base/foot placement block;
  construction-reveal loses the wall-puff, roof-hair-lift and warm-start dust blocks, and its
  post, plate, impact and finale tests keep settlement and event order without the
  millisecond and centimetre figures.
- core: snap-ease loses the eased-out curve block; the corner hint test asserts the click
  and Alt rows and distinct reasons, not their sentences.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: visual reference search is not part of bundle 2; remove its tool contracts

Owner decision: only what takes part in bundle 2 stays in the PRs. The contracts of
search_visual_references and inspect_visual_references (and the reference types they
return) were added by this branch and are not on main; nothing in the editor registers or
calls them.

- core: agent-tools/visual-references.ts and its export from the agent-tools index.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: remove the modules this branch added that nothing imports

None is on main and none has a caller or a test: building/material-swatches.ts,
systems/wall/wall-loop.ts, utils/repetition.ts, and lib/exterior-wall-loops.ts's
freeWallEnds (with its type), which only the removed plan engine would have called.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: a scripted item's plan footprint carries its asset's scale, rotation and offset

itemNodePlanCorners sized a scripted item from the manifest's raw bounds and the node's scale
only. The renderer, and scriptItemBoundsInLevel which measures the vertical bounds, put the
asset's own corrective scale, rotation and offset inside the node's scale, so an item whose
asset carries any of them was missed or invented in the plan footprints behind item
collisions, door clearance, mid-room checks and chair slots. The corners now take the
same pose; a catalog item, whose dimensions are a plan box, is as before.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: a patch that loses a field removes it from the simulated scene, as the store does

paint's roof erase deletes the role's preset from a copy and records it as `undefined` in the
update; the store's updateNode removes such a key, but applySceneChanges (the batch's working
scene, achieved counts, previews) and mergeSceneChanges (a node created and then changed in one
batch) spread the patch and left the key on the node as an own `undefined`. Both now remove a
field the patch sets to `undefined`, so the simulated node is the one the store holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: a scripted door, window or column that cannot be measured is refused like an object

add_object refuses a compile whose bounds are not finite numbers before anything is built, with
the code unmeasurable_object. A door, a window and a column take their width, height and
position from the same bounds and had no such check: NaN (null after a JSON hop) reached the
node as a non-finite size, or failed the schema with a raw validation array. The check moves
to lib/geometry-script-node as requireMeasured and add_door, add_window (new and rebuilt) and
add_column call it as add_object does, each naming its own tool and whether anything was
changed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: an unknown fascia finish is refused as an unknown roof finish is

create_roof checked materialPreset against the library so a finish it lacks could not be
stored and drawn as the default; fasciaMaterialPreset went to the segments unchecked, so an
unknown fascia finish was persisted and silently rendered as the default. It now takes the
same check, naming the field and the nearest library ids.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: a wall-side item with no side is observed on the face the renderer draws it on

The wall object observer reads a missing `side` as the back face. The item system, the
plan symbols and the editor's floor plan all place a side-less wall-side item on the back
face (`side === 'front' ? a : b`), so the observer reports where the item is drawn; new
objects and placements write the side. The rule is now stated where it is read and pinned
against the saved screen with its side removed (and with the side flipped, which differs).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: hold to see before is pinned for a turn that builds

The compare suites held turns that only changed a wall. "Furnish this
room" on an empty room creates nodes the before has never heard of: the
ask-flow suite now holds such a turn end to end (pin done, before has no
furniture, the live scene comes back), and the nodes suite mounts real
item renderers with the batch system and the construction reveal and
holds the before over agent-built furniture (nothing registered, no
batched instance drawn, and the pieces return on release).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: a storey added and not built yet is not the roof level: the roof goes above it

create_roof took the level directly above the covered storey as its host when it was empty. add_level
always leaves its new storey empty, so adding a floor and then roofing the house parented the roof to
the new storey: the floor was consumed (a level holding a roof reads as a roof level, and a later
create_room refused with roof_level). Only a roof level above is reused now; storeys above with
nothing on them are stepped over and the roof level is made above the highest, as above a storey
with rooms it is refused (storey_above).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: a batch on a host without a materials map refuses a native finish, applies nothing

run_batch lends a later call the materials an earlier one created only when the host lent a map. Paint
refuses a native material without one (materials_unavailable), so no call of such a batch creates a
material to be hidden; the pin keeps that in the batch's own words (batch_call_refused, scene untouched).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(point-ask): the bubble says Queue behind messages that already wait, though no turn runs

The host told the editor only that a turn was running, but the chat also queues an ask
behind messages that wait, or are held after an error and need Resume. The bubble said Send
and the ask then sat behind the hold with no cue in the scene. `setPointAskBehindQueue` is a
second flag, apart from `busy`: a held queue is not a turn, so it neither keeps Follow Pascal
going nor stops an idle chat from settling its pins or opening a hold to compare.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: a roof over a storey looks past a roof level for a built storey above it

The host walk stopped at the first roof or support level above the covered storey, so a storey
with rooms above that level was never seen: storey_above was not raised and the new roof was
parented under the upper storey. The walk now looks at every occupied storey above: one with
rooms refuses, empty ones are floors to be built and the roof goes above the highest of them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix: a roof stands only on a roof level of its building, by one rule and a table

The host was whatever level sat at the storey's level + 1: a support level became the roof's
parent, and a storey with no building got the roof as its own child, so a roof and rooms shared
a floor. roofHost is one pure function over the levels above the covered storey: a storey with
rooms above refuses, empty storeys and support levels are floors the roof goes above, a roof
level of the same building above those is reused, and a storey with no building refuses.
One test per row of its table.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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