Repository navigation
core: move old roofs to the roof level, by hand or by agent - #1047
Merged
Merged
Conversation
Aymericr
force-pushed
the
feedback/roof-level-move
branch
from
October 13, 2026 20:29
7dc3c14 to
0292ac0
Compare
Projects built before the roof level, and roofs a tool wrote onto the storey whose walls carry them, keep hiding that storey's plan in solo and exploded views. Rebuilt on main after #1039 landed: its roof-level rule (planRoofLevel, coveredStoreyId, roofsOver) stays as it is; this adds the move. - services/roof-move.ts: isRoofOnStoreyWalls (a roof bearing on the wall tops of the storey holding it; a porch roof on its beam and a lower wing's roof on the storey above are not), and planRoofsOffStorey, which moves those roofs to planRoofLevel's level in one set of changes, each container dropped by the storey's offset so it stands where it stood. - planRoofLevel returns that storeyOffset, for the plugins that write roofs measured from the storey's floor; roofLevelAbove reads its target without planning a level. - The roof's panel offers "Move to the roof level" ("Move to <storey>" under an upper storey), in 2D and 3D, as one undo step. - move_roofs_to_roof_level: the same move for agents, one contract and one core operation, on the MCP with its annotation policy, in the README's tool table and the agent guide. - verify_scene's roof_mixed_in_storey keeps main's reports and names the move for the roofs it takes off. - A roof above the storey it covers at a height of its own keeps its height over that storey when the storey is made taller or shorter (roofsKeptOverStorey, in the roof elevation sync). - A roof level holding a plugin's roof planes is not reported roofless. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aymericr
force-pushed
the
feedback/roof-level-move
branch
from
October 13, 2026 21:30
0292ac0 to
0b3a6c4
Compare
… still to be built roofsKeptOverStorey took the level directly under the roof's level as the storey the roof covers. With a support level or an empty level between them, which is where planRoofLevel puts the roof level, it found no storey there. A fixed-height roof standing on the storey's walls then rose off them when the storey, or the level between, was made taller (Bugbot on #1047). - The storey is coveredStoreyId's, the one the move itself reads. - A level put in, taken out or reordered between them still carries the roof with its level, as #1039 decided for "+ level". Over levels between, only a roof bearing on the storey's walls is kept on them, so a roof that "+ level" lifted stays on top of the new level when it grows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r its floor planRoofsOffStorey sized a new roof level by the roofs' rise over their eaves alone. Walls taller than their storey seat the eaves above the new level's floor, so the roofs stood above its height (Bugbot on #1047). The level now takes the roofs' highest point over its floor, the eaves' lift included, as create_roof sizes one, and the plan's create says the same. 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 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4e440fa. Configure here.
planRoofsOffStorey kept a moved roof on `walls` follow when the follow seated it where it stood. Over a support level or a level still to be built, the follow reads only the roof level's walls and the ones directly under it, finds none, and answers the roof's own height, so the roof kept a follow that could not see its storey. When the storey grew, it rose with the roof level off the walls (Bugbot on #1047). Such a roof now keeps its height (`support` level), which roofsKeptOverStorey keeps on the storey's walls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What does this PR do?
Rebuilt on main after #1039 (and #1044) landed. It is now four commits on editor
mainf9bcb0c5(#1052, the editor commit hostedmainpins):0b3a6c4d, the move, thend4460ebd,4e440fa3andb7b7ec60, three fixes from Bugbot's review (below). Everything #1039 already had is gone from the diff: its roof-level rule (planRoofLevel,coveredStoreyId,roofsOver,servesAsRoofLevel) stays exactly asmainhas it. This PR adds only the move, by hand and by agent.A roof written onto the storey whose walls carry it can be moved to the level the roof rule gives it, without changing how it looks. Existing projects need this: roofs built before the roof level, and by tools that ignored it, still cover their storey's plan in solo and exploded level views.
services/roof-move.ts(new):isRoofOnStoreyWalls(nodes, roofId)names a roof held by the storey whose wall tops it bears on, within 0.15 m. A porch roof on its beam bears lower, so it is not one. A lower wing's roof on the storey above bears on the walls below, so it is not one either.planRoofsOffStorey(nodes, storeyId)moves only those roofs. Each goes toplanRoofLevel's level: the roof level over the top storey (created or declared in the same changes), otherwise the storey above. A roof level it creates is as tall as the roofs rise over its floor, the eaves' lift included, ascreate_roofmakes one. Each roof group drops by the storey's offset, so it stands where it stood. A roof that follows walls switches to a fixed height where the walls on its new level would lift it, or where the follow would not see the storey's walls (a support level or an empty level between them). A second move leaves a lower wing's roof that the first move put on the storey above.roofsKeptOverStorey(before, after): a roof group above the storey it covers (coveredStoreyId), at a fixed height (supportlevel: moved roofs, the agents' and the auto roof's), keeps its height over that storey when the storey, or a support level or empty level between them, is made taller or shorter. A level put in, taken out or reordered between them carries the roof with its level, as Editing foundations: roofs on the building's roof level, add_fence shared, corner-window editing and review fixes #1039 decided for "+ level". The roof elevation sync applies it without an undo step of its own, as it already does for a roof that follows walls. A roof standing on its own level's walls is left alone.roof-level.ts:planRoofLevelreturnsstoreyOffset(the storey's floor from the target level's floor), for the roof writers that measure heights from the storey.roofLevelAbovereads whereplanRoofLevelwould put a roof without planning a level, so the panel can ask on every store change.coveredStoreyIdandservesAsRoofLevelare exported for the plugins.bg-foreground/[0.08],rounded-full).move_roofs_to_roof_level: the panel's move for agents. One contract (agent-tools/roofs.ts), one core operation (agent-operations/move-roofs.ts), with its case table. It is registered on the MCP with its annotation policy (destructive: it re-parents existing roofs), and listed in the README's tool table and the agent guide. Refusals:level_not_found,no_roof_on_walls.verify_scene'sroof_mixed_in_storeykeepsmain's reports (a roof on a storey that covers it, with walls, rooms or a stair there). It also reports a roof standing on its storey's own walls thatmainread as a lower wing's roof, and for those it namesmove_roofs_to_roof_level, the panel action, and "never delete and rebuild them". A porch roof on its beam is still reported, without the move. A roof level holding a plugin's roof planes is no longer reported asroof_level_no_roof.systems/roof/roof-elevation.tssplits out the footprint test it already ran (wallsUnderRoof,roofWallTop), so the move reads the same walls the elevation sync does.main's rule that a lower wing's roof follows the walls below only is kept.wiki/architecture/vertical-model.md(Roof levels) describes the move.Resolved against
main:storeyCoveredBy, the bearing-based coverage reader of the earlier branch, is dropped formain'scoveredStoreyId. The roof-level tests aremain's, withstoreyOffsetadded and the move's cases appended. The MCP guide test listsroof_mixed_in_storeyas a code the guide may name.How to test
support: 'walls'at y = 2.7.move_roofs_to_roof_level { levelId }does what the button does. A second call answersno_roof_on_walls.Ran on
b7b7ec60, from the hosted root (pascalorg/private-editor#913's worktree):bunx turbo run check-types --filter='./editor/packages/*'(9/9), Biome on the changed files.bun test editor/packages/core editor/packages/mcp/src editor/packages/nodes/src/roof: 5369 pass, 0 fail. On0b3a6c4d, witheditor/packages/editor/src/components/ui/command-paletteadded: 5366 pass.agent-surface-parity,agent-tool-tableandhosted-mcp-tool-effectspass with the tool on both surfaces.In the browser, in the hosted app (pascalorg/private-editor#913), on
0292ac09: that is this commit before its rebase onto #1052, which touches only the command palette.move_roofs_to_roof_levelstep ("Moved roofs to the roof level") gave the same result, and undo restored it.Checklist
bun devbun checkto verify)mainbranchReview
Two reviews of the earlier branch and its hosted half (pascalorg/private-editor#913): an adversarial review, and GPT-6 Astra.
Fixed here:
4e440fa3, medium). The follow reads only the roof level's walls and those directly under it, so the roof rose with the roof level when the storey grew. Fixed inb7b7ec60: such a roof keeps its height, androofsKeptOverStoreykeeps it on the storey's walls. The new case fails on4e440fa3.d4460ebd, medium). It was sized by the roofs' rise alone, so with walls taller than their storey the roofs stood above it. Fixed in4e440fa3: it is as tall as the roofs rise over its floor, ascreate_roofmakes one. pascalorg/private-editor#913 sizes the hosted writers' roof level the same way.0b3a6c4d, medium).roofsKeptOverStoreytook the level directly under the roof level as the storey it covers. With a support level or a level still to be built between them, it kept nothing, so a fixed-height roof standing on the storey's walls rose when the storey grew. Fixed ind4460ebd, which reads the storey fromcoveredStoreyId. With levels between, it keeps only a roof bearing on that storey's walls, so a roof that "+ level" lifted stays on top of the new level. The new tests fail on0b3a6c4d.move_roofs_to_roof_levelis the lossless move, andverify_scenenames it.roofLevelAbove.Not changed, with the reason:
🤖 Generated with Claude Code
Note
Medium Risk
Re-parents roof nodes and can create or declare roof levels; scene graph and elevation sync behavior change for migrated roofs, though geometry is intended to stay fixed.
Overview
Adds a lossless migration path for legacy roofs parented to occupied storeys: new
move_roofs_to_roof_levelagent tool and roof panel "Move to the roof level" both runplanRoofsOffStorey, re-parenting only roofs that bear on that storey's wall tops (porch beams and lower-wing roofs on the storey above are excluded) onto the target fromplanRoofLevel, preserving world position, ids, and segments.roof-move.tsimplements detection (isRoofOnStoreyWalls), the move plan (includingstoreyOffseton roof-level plans), androofsKeptOverStoreyso roofs at fixed height above a storey stay aligned when storey or intermediate level heights change; the roof elevation sync applies that on level edits.verify_scenerefinesroof_mixed_in_storey(names the move for on-wall roofs), stopsroof_level_no_roofwhen plugin roof planes exist, and treats lower-storey roofs on the floor above as quiet. Docs, MCP registration, fixtures, and extensive tests accompany the change.Reviewed by Cursor Bugbot for commit b7b7ec6. Bugbot is set up for automated code reviews on this repo. Configure here.