Skip to content

feat(viewer): per-level base elevation parameter#312

Open
mvanhorn wants to merge 1 commit into
pascalorg:mainfrom
mvanhorn:feat/209-level-floor-height-parameter
Open

feat(viewer): per-level base elevation parameter#312
mvanhorn wants to merge 1 commit into
pascalorg:mainfrom
mvanhorn:feat/209-level-floor-height-parameter

Conversation

@mvanhorn

@mvanhorn mvanhorn commented May 15, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a per-level baseElevation parameter so each level can offset its computed stack position upward by a user-specified amount. The site panel surfaces a Base elevation MetricControl on each level (m, 2dp, 0.05 step) and the viewer's level system threads the offset through getLevelTargetY / getNextLevelCumulativeY helpers.

How to test

  1. Open the site panel.
  2. Expand any level.
  3. Set the Base elevation field to a non-zero meter value.
  4. Confirm the level (and all levels above) move up by that amount in 3D.
  5. Switch to exploded mode; confirm EXPLODED_GAP still adds on top of the cumulative offset.
  6. Run the test suite: bun test packages/core/test/level.test.ts packages/viewer/test/level-utils.test.ts.

Notes

check-types flags a pre-existing TypeScript error on the base branch unrelated to this change (packages/editor/src/store/use-editor.tsx:249childId implicit any). The error is present on origin/main before this branch is applied, so the failure isn't introduced by this PR. Happy to fix it in a sibling PR if useful.

Checklist

  • Ran bun dev and exercised the change locally
  • Ran bun check (biome clean)
  • bun check-types fails on pre-existing error (see Notes)
  • Tests added for the schema + utility paths
  • Targets main

Closes #209


Note

Medium Risk
Changes core 3D level positioning used by floorplan, selection, and solo/exploded modes; behavior is well-tested but mis-stacked levels could affect downstream spatial presentation.

Overview
Adds a per-level baseElevation (meters, default 0) so users can shift a level’s stacked Y position; the offset is additive and cumulative—it moves that level and every higher level in the same building, with negative values allowed.

The site panel exposes Base elevation on each expanded level via MetricControl (0.05 m steps, 2 dp). getLevelStackPositions now applies baseElevation when computing stack Y; LevelSystem and snapLevelsToTruePositions pass the field through so stacked, exploded, solo, and floorplan snap behavior stay consistent.

Schema tests cover defaults and parsing; stacking and level-system tests cover multi-building offsets, negatives, and presentation modes.

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

@Aymericr

Copy link
Copy Markdown
Contributor

This remains a distinct product capability, so I’m keeping it open. Before code review it needs a current-main rebase and one explicit semantic decision: is baseElevation an offset that shifts this level and every level above it, or an absolute Y for this level only? The current cumulative implementation chooses the former, while the field name can be read either way.

Please also cover stacked/exploded/solo modes, snapLevelsToTruePositions (bake/export), negative offsets, and selection/floorplan behavior with focused tests. Once that contract is stated, the implementation can be reviewed against it.

baseElevation is an offset: it shifts the level and every level above
it within the same building (cumulative). Integrated into the level
stacking computation so stacked, exploded, solo, floorplan, and
snap-to-true-positions all respect it. Includes focused tests.

Closes pascalorg#209
@mvanhorn
mvanhorn force-pushed the feat/209-level-floor-height-parameter branch from 9d14a64 to 92a550e Compare July 20, 2026 06:56

@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 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 92a550e. Configure here.

precision={2}
step={0.05}
unit="m"
value={Math.round(level.baseElevation * 100) / 100}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Legacy levels show NaN elevation

Medium Severity

The new Base elevation control uses Math.round(level.baseElevation * 100) / 100 without a fallback. Levels loaded from older project JSON can omit baseElevation because scene migration only normalizes level and children, not this field, so the control receives NaN even though stacking treats missing values as zero.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 92a550e. Configure here.

// cumulatively, every level above it in the same building. Negative offsets are valid.
const levelY = baseY + (entry.baseElevation ?? 0)
positions.set(entry.levelId, levelY)
cumulativeYByBuilding.set(entry.buildingId, levelY + entry.height)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Elevator stack ignores base elevation

Medium Severity

Per-level baseElevation now shifts stacked level meshes via getLevelStackPositions, but elevator level tables and first-person elevator colliders still build cumulative Y from story heights only. Non-zero offsets leave cab stops and shaft geometry out of sync with the floors users see in the editor.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 92a550e. Configure here.

@mvanhorn

Copy link
Copy Markdown
Contributor Author

Contract, stated explicitly: baseElevation is an offset. It shifts this level and every level above it in the same building (cumulative), it is not an absolute Y, and negative values are allowed. Rebased by porting onto the current level-stacking architecture in 92a550e so stacked, exploded, solo, floorplan, and snapLevelsToTruePositions all read it from the stacking computation, with focused tests for each of those plus negative offsets.

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.

Floor height parameter

2 participants