Repository navigation
Conversation
…61010 Brings in pascalorg#1021 (dependency upgrades) and pascalorg#1026 (plugin agent tools: AgentToolContract, AgentOperations, PluginChatExtension, registerOperationTool). Conflicts, all in the agent tool registries pascalorg#1026 reshaped: - core/agent-operations/index.ts: keep the bundle's operation list and imports; AGENT_OPERATIONS (the export that carries run_batch) is `satisfies AgentOperations`. - core/agent-tools/index.ts: keep the bundle's contract list and exports; the list is `satisfies readonly AgentToolContract[]`; export ./contract too. - mcp/tools/shared-tools.ts: main's exported registerOperationTool and OperationTool are the shape; the bundle's per-call logic (plan judge and vectoriser hosts, agent session, native materials, run_batch place_in_room catalog, marked-plan image) folds into it, behind a trailing `hosts` parameter. agentRuntime also writes collection changes, and a call persists on main's `writesScene(changes) || afterReconcile` (collection-only outcomes now publish a snapshot); the bundle's checkpoint branch stays after it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… 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>
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>
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>
…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>
…arries 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>
… 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>
…alFromPresetRef 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>
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>
…erve 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>
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>
…ut 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)
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)
…efore 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)
…ther 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)
…ge 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)
(cherry picked from commit 6f9424f7d5f82a31b8934e7992acf0249eaed21c) (cherry picked from commit 0a8c657dfe3828aa42410dbca3637a3aba8d872f)
(cherry picked from commit e4b220feb128dc5824b094241fc20e66f58d4eb0) (cherry picked from commit cd91545d5a362e7d59164a5824f50e96168eb1d1)
(cherry picked from commit c1e2dc5f64ca7bbc4efee4488910ad0b501e8cec) (cherry picked from commit 3a73b74cf0051f1f127a3931045e4bfd9945c537)
(cherry picked from commit e11ee4094bc3b0d4df16bfa90bc0c84ade59875d) (cherry picked from commit 4120cf640eb93ec2b9fd5952252ca94b6ab2e4d0)
…s 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>
…th 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>
verify_scene reports item_wall_body_overlap, item_through_wall and item_faces_wall, and a new back-side wall mount with no rotation defaults to a half-turn yaw (5417caf). Merged with the add_object refusal of an unmeasurable object (8e76db8): both are kept. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e 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>
…scalorg#1033) into the bundle 2 editor branch Conflicts and what was kept: - packages/core/src/agent-tools/add-object.ts: main's `bpy` paragraph in the description is taken whole; the `Edit:` line stays ours (states that get_source provides the current code and params, where main says to read it first). get_source's description keeps ours (optional evidence, an edited module can be passed back) and takes main's "three.js or Blender bpy, named by language". - packages/core/src/agent-tools/wall-openings.ts: ours (a module is available, native fields and code express the requested shape), with main's mention that the module may be bpy by language. The two sentences "Fields first; code only beyond them" and "the fields cannot express" stay out. - skills/pascal-3d/SKILL.md: ours (the editor-tab settings and refusals, `editor_tab_busy`), with main's bpy sentence and "Blender modules run in its Blender service and need no tab". - skills/pascal-3d/references/tool-workflows.md: ours (the sections after the scripted-object paragraph that main does not have), with main's parenthesis that a bpy module builds without a tab. - wiki/architecture/authored-objects.md: both: our normalized-origin table, then main's "Blender modules" section. Both languages end in `finishBuild`, where the compile refuses a non-finite size naming the mesh and its component; add_object's `unmeasurable_object` reads the manifest, whichever language built it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… 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>
| detail: `${rect.long.toFixed(2)} × ${rect.short.toFixed(2)} m holds neither a ${MIN_FLIGHT} m switchback nor a straight flight of ${Math.round(getStoredLevelHeight(lower) / RISER)} steps.`, | ||
| }) | ||
| break | ||
| } |
There was a problem hiding this comment.
Short landing still builds stair
Medium Severity
A switchback whose leftover landing is shorter than MIN_FLIGHT is pushed into skipped as short_landing, but the stair is still created. core_too_small and already_built actually skip; this path only warns while still writing the stair.
Reviewed by Cursor Bugbot for commit 6be2b6d. Configure here.
There was a problem hiding this comment.
Confirmed: the stair is created and the short landing is only warned about under skipped. This tool has left this PR with the rest of its family (apartments and circulation are not part of bundle 2, removed in 56b7b1b), so it is not changed here; the fix (the warning moves to its own shortLandings list, and skipped keeps only cores that got nothing) is written and tested separately for when the family ships.
| // Along the bisector, as far as both edges move by `distance`. | ||
| const scale = distance / (1 + dot) | ||
| return [point[0] + sum[0] * scale, point[1] + sum[1] * scale] | ||
| }) |
There was a problem hiding this comment.
Roof outline miter can explode
Medium Severity
grown offsets each corner with distance / (1 + dot) of adjacent unit normals. Near-reversing edges make 1 + dot ~ 0, so the roof outline becomes huge or non-finite before segments are built.
Reviewed by Cursor Bugbot for commit 6be2b6d. Configure here.
There was a problem hiding this comment.
Real: the mitre distance / (1 + dot) is unbounded as two edges fold back (a 30 m sliver with a 1° point gave a 41.6 m roof segment, 0.2° gave 87 m; an exactly folded edge divides by zero). Fixed in 857016e: a corner whose mitre would pass 4× the distance (under about 29°, the limit an SVG stroke uses) gets the two edge-offset points instead, so the outline is always finite; right angles still mitre exactly. Tests in create-roof.test.ts: 'a sharp corner does not stretch the roof past the building', 'keeps a point of 0.2° within a wall's width of its corner', 'gives finite points where an edge folds back exactly on itself'.
…f 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>
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>
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>
…e_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>
… 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>
… 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>
…he 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>
…ed 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>
…ol 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>
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>
…tion 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>
| const next = { ...node } as Record<string, unknown> | ||
| if (ref) next[field] = ref | ||
| else delete next[field] | ||
| written[id] = next as AnyNode |
There was a problem hiding this comment.
Roof erase leaves finish field
Medium Severity
Erasing a roof or roof-segment finish deletes the role field on a copied node, then changedData records that key as undefined. applySceneChanges spreads the patch, so the preset stays on the node as undefined instead of being removed. Slot erase replaces the whole slots object and does clear. A roof erase: true can therefore leave the finish in place for later reads, batch simulation, and any store that ignores undefined patches.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 2143eec. Configure here.
There was a problem hiding this comment.
Real. paint's roof erase records the lost field as undefined, which the store's updateNode removes, but applySceneChanges (and mergeSceneChanges when a node is created and changed in one batch) spread the patch and left an own undefined key, so the batch's working scene, achieved counts and previews disagreed with the store. Fixed in 799e8f6: both now remove a field the patch sets to undefined. Test: 'erasing a roof's finish takes the field off the node, as the store does'.
…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>
…used 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>
…scene materials (#1034) * refactor(core): the wall paint plan lives in core planWallPaint and plannedWallIds move from nodes/src/wall/paint-plan.ts to core/src/systems/wall/wall-paint-plan.ts, unchanged, and core exports them. The editor's wall paint imports them from core; the agent's paint operation (next commit) writes a wall's faces through the same plan instead of a copy. Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(core,editor): view_scene looks inside a floor view_scene takes interior: { levelId }. The plan checks the level (interior_level_invalid; interior_hidden for a hidden level or ancestor; interior_transform_unsupported for a building tipped off the vertical) and, with no target, eye or camera given, frames the floor from 55 degrees above. The host gets only the level id: the MCP passes it to its SceneViewHost, and the editor's capture works the cutaway out from its own scene with core's sceneViewInterior. For that one render it hides the other floors and this floor's roofs and ceilings, cuts the floor's walls and their shadows at 0.65 of the storey (a ClippingGroup on the level), draws its walls whole whatever the wall mode, and lifts the user's solo and isolation on it (temporarilyShowPresentation in the viewer). Everything is put back after the render, through the capture's existing restore list. The plan now frames buildings where they stand: what is under a building is drawn in its frame, so its position and yaw move and turn the boxes, and an opening is seen from its outside on the site. A roof, or a roof level holding only roofs, frames its segments from its seat to its peak instead of refusing nothing_to_view. An authored snapshot pose passes the walls' finish refs when it swaps their materials, as the wall renderer does, so painted faces keep their finishes. From Adam's #1024. This keeps the capture on the snapshot path view_scene already uses (it waits for the scene to build and holds the viewport's last frame) instead of #1024's read-only transaction (per-node commit tracking in NodeRenderer, a frame-loop freeze, control and object state snapshots, asset readiness refusals). Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(core,mcp): paint and search_materials, with native scene materials paint gives walls, columns, slabs, ceilings, fences, doors, windows and roofs a finish in one call, as the editor's Paint does: the slots its slot paint writes, a wall's faces through the shared wall paint plan, a roof's or a segment's top, edge or wall role field (refused shadowed_field when the roof's segments have their own). exterior and interior resolve to a wall's faces from the rooms on its sides. The finish is a colour (the nearest library colour, said with how near), a library or scene ref, a native material, or erase. Any unknown target, role or material refuses the whole call. A native material is kept as a scene material, reused when the scene holds one the material schema reads as equal. The operation returns it in SceneChanges.materials and needs the scene's materials in AgentContext.materials; a host without them refuses a native material (materials_unavailable) and still paints refs and colours. Scene refs are checked against those materials, and an unknown library material's refusal points to search_materials. get_node returns the scene materials a node's finishes use. search_materials finds library refs by words, as the library writes them (timber is wood, render is stucco), by shade and by surface, and names the words no material answers. On the MCP both are shared tools. SceneOperations gains supportsMaterialUpserts and getMaterials: the bridge lends its materials as the context and applies SceneChanges.materials as upsert_material patches, written before the nodes in the same history step. upsert_material is not part of apply_patch's schema. From Adam's #1024, without the run_batch and plan-reference hooks it was written with. Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(core): an interior view refuses a target on another floor Every other floor is left out of an interior view, so a target on one framed nothing the picture shows. view_scene with interior and a target off that floor is refused interior_target_elsewhere, naming both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(core): paint shows over a room's finish, search matches whole words, interiors refuse what they leave out - paint: a wall face is painted whole, its slot and an override in each room whose wall finish shows over it, as the editor's Paint writes a click on that room's face; painted parts keep theirs and the note names them. A curtain wall's exterior or interior is refused with its roles. - search_materials: query words match whole words of a material (or begin one, from four letters), short and common words are dropped, and a flat colour suits every surface filter. - view_scene interior: a level holding only roofs and ceilings is refused, a target the picture leaves out (a roof, a ceiling) is interior_target_elsewhere, and a hidden site no longer blocks the floors on it. The contract says what is cut: everything on the floor at 0.65 of the storey height. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(viewer,editor): a capture draws walls with the renderer's palette and keeps the perspective it asks for - An authored pose or an interior view asks for whole walls on thumbnail:before-capture ({ wholeWalls: true }), and WallCutout swaps them with the resolver the renderer mounted it with (wallsForCapture). The capture's own swap used the plain wall palette, so a curtain wall's glass came out opaque. - A capture with a perspective view of its own (view_scene's) stays perspective when the user's camera is orthographic (capturesOrthographically); it took the user's orthographic view while the result reported the planned camera. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Adam NAILI <adam.naili.pro@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
There are 5 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 232a5b1. Configure here.
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>
…rer 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>


What does this PR do?
Bundle 2, editor half. Point and ask, building feedback that follows the work, steadier keyboard and audio, sturdier walls, slabs, ceilings and roofs, interior captures, persistent native materials, and the shared tool contracts that the hosted chat and the MCP server both use.
Ready for review. The owner marked it ready after testing the bundle on a server built from the PR heads, and is testing these exact heads on that server now; Wassim merges. The hosted half is pascalorg/private-editor#877, whose
editorgitlink pins this PR's head (cad1839f).Only bundle 2 is in this PR. A bundle PR contains only the features selected for that bundle (the 29 on the delivery board). Everything else stays in the lab (R&D) branch for a later bundle. The lab families that had been carried here by default were taken out on 13 October; they are listed below under "Not part of bundle 2".
Size: 66 commits plus 6 merge commits, on top of upstream
main(80d7a2f, #1033, as last fetched; re-check before merging): 400 files, +40,358 / −920 lines. About 18,000 of the added lines are tests and fixtures. (Figures as ofcad1839f; before the scope trim the PR stood at 489 files, +59,777.) Upstreammainis merged in twice, never rebased: 86f8e83 (#1026) earlier on 12 October and 80d7a2f (#1033, Blender bpy modules) that night, so this PR merges cleanly withmainas of then.What a person gets
verify_scenealso reports an object that is wholly or partly inside its wall (item_wall_body_overlap), one that goes through the wall (item_through_wall) and one that faces the wall (item_faces_wall); a new back-side wall object with no rotation now defaults to a half-turn yaw (the old zero default turned it to face the wall, which z-fought the cinema screen).view_scenecan show the inside of a chosen storey. It waits for the scene to finish building and sets aside nodes no system will settle, then restores the normal view.maincauses, fixed here).857016ea, from Bugbot).main(Blender (bpy) modules as a second geometry-script language #1033). Merged in. The compile refusals of this PR sit in the sharedfinishBuild, so a bpy build gets the same non-finite-transform refusal and "no visible meshes".add_object'sunmeasurable_object, scripted-door clips and walk-through, and the wall-object observations read the manifest and the loaded model, not the language. A test commit (6be2b6d9) pins this for bpy builds. Wheremain's tool text gave a method order ("read it first with get_source", "fields first; code only beyond them"), this PR keeps its factual wording and addsmain's facts about bpy.add_objectrefuses an object it cannot measure (unmeasurable_object) before building its asset; the geometry script's compile fails on a mesh at a non-finite position and names the mesh; the room labels' timers are called as functions, not as methods of the placer (an upstreammainbug, kept as its own commit9a1375c5because it was asked for in review). Plugin nodes are validated and respect vertical collision bounds; the geometry script compiles consecutive imports on one line; scene-tool evidence and batching are optional in core.Not part of bundle 2 (kept in R&D)
Removed from this PR on 13 October, one commit per family, because they are not among bundle 2's selected features. They stay in the lab branch for a later bundle. Anything already on upstream
mainstays.locate_photo),4cc6ee74. The photo toolstraighten_facade_photostays.dispute_coherence_itemand the checker behind it),60b78690.verify_scene,validate_sceneandvalidate_designaremain's and stay; new checkpoints record no checker issues.add_site_surface,add_steps, this PR'sadd_fence),dc015331.add_fenceismain's again.propose_unit_layouts,apply_unit_layout,name_units,create_stairs_and_lifts, circulation and reachability),56b7b1b9.main's unit tools stay.bbb4e1b6.request_upload: its hints and the resolver type,95813f7a.00c76f05.cad1839f.e9a36f6a): its nodes, schemas, tools and prompts.straighten_facade_photostays.Tests trimmed (
da081b46). Two independent audits read every test this PR adds. Where both named the same blocks as pinning implementation details rather than behaviour (animation feel, copied constants, pixel placement, copy text), those blocks went: 11 files, −666 lines. No test that guards a fix made on 12 October was dropped.Bugbot, round 1
857016ea(see Roofs), with tests increate-roof.test.ts.Tests and checks on this head (
cad1839f)Run on 13 October on
cad1839f, from the hosted PR worktree, with the editor built.bun run check-types(turbo)main's Blender tests)bun run skills:validate00c76f05;cad1839fonly removes modules nothing imports)Carried from
c9f54660, not re-run on this head: cli 107, ifc-converter 92 and editor app 51 pass;bun run check(biome) exit 0 with warnings only. Not run for this description:turbo run build, browser/e2e tests, GPU capture checks.The viewer test "pins the vendored closure to the installed R3F version" (
packages/viewer/src/lib/pointer-events.test.ts) passes here, where@react-three/fiberresolves to 9.8.1. It used to be red in the hosted repo (and on hostedmain), whose lock resolved 9.6.1; #877 now pins 9.8.1 with a root override, and it passes there too.Seen, not in this PR
Upstream
mainissues found while testing this branch. They are not fixed here; they are listed so they are not lost.view_scenetimeout contract. The hosted tool call gives up after 60 s without cancelling the capture, the view tool waits 75 s for its snapshot, and the capture's own budgets allow 120 s, so late capture work outlives the error. One deadline and real cancellation would fix it.thumbnail-generator.tsx, a capture can run on a pipeline that was cleaned up while it awaited; a version check right before each capture closes that gap. Possibly linked to an "uncaptured WebGPU error: destroyed texture used in a submit" seen in the browser; not proven.Environmentdisposes it on unmount, which can leave the live scene with a destroyedPMREM.cubeUvtexture. The hostedprocedural-items/preview.tsxalready clones and disposes its own copy; the others could do the same.Not in yet
Before merging
main.How to test
Use a disposable scene. Chat scenarios run in a compatible host and may use normal model credits.
openclip slides open from the action menu or E, and in the walkthrough; an open one can be walked through, a closed one blocks.verify_scenestays quiet. Push an object into its wall: the check names it.Screenshots / screen recording
A recording of the final pair is still needed.
Checklist
bun dev(the owner is testing these exact heads on a local dev server)bun checkexited 0 onc9f54660; tick once it passes on the final head)mainbranchNo merge, deployment, migration, credential change or paid model run is part of this PR.
Note
Medium Risk
Touches core agent operations, scene apply/merge semantics, and layout verification used by MCP and chat; behaviour changes (refusals, room zones, window placement, rotations) are broad but heavily fixture-tested.
Overview
Agent tools and scene writes gain
add_corner_window,merge_windows,create_roof,run_batch(ordered calls merged into one undo step, including scene materials), plus stricter material ref checks and session checkpoint/keepwiring inapply-outcome.Scripted geometry is rejected up front via
requireMeasured(unmeasurable_object) when compile bounds are non-finite (NaN or null after JSON), foradd_object, columns, and door/window paths—instead of building assets or surfacing raw schema errors.Rooms and openings:
create_roomreuses zones from existing walls, refuses silently renaming person-named rooms unlessrename: true, and windows from room creation are centred on storey height rather than a fixed sill. Wall-side items default back-side mounts to π yaw so they face into the room.Layout and verification expand: plan footprints use script manifest bounds (offsets/scales/rotation), floor rugs/underlays stop blocking placement, vertical separation between ceiling and floor items,
item_mid_roomfor wall-bound furniture away from walls, ceiling–slab overlap observations, and wall-hosted overlap/facing checks (cinema-screen fixture). Read APIs exposewallFaceChainsand zone wall intent; patches that set fields toundefinedremove optional keys like the store.Tests/fixtures add large declarative tables for corner/merge windows, walled
create_room, finish readback, native paint contract helpers, and many layout-clearance regressions.Reviewed by Cursor Bugbot for commit 2ddbaf4. Bugbot is set up for automated code reviews on this repo. Configure here.