Skip to content

Underground: the ground steps aside over a basement, the world turns to earth below it, a ground line in the levels, and a foundation under every house - #1097

Merged
wass08 merged 71 commits into
pascalorg:mainfrom
AxiomeCG:feat/underground-20261016
Oct 20, 2026

Conversation

@AxiomeCG

@AxiomeCG AxiomeCG commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Base: pascalorg/editor main (82153a03c). Head: branch feat/underground-20261016 (cee68cf36). 44 commits; squash-merge.
Pairs with the private-editor PR "Underground: the chat and hosted MCP serve set_ground_level, Bones draws its foundation under the ground, the scene program reads grade". That PR pins this head and must be re-pinned to the squash commit once this one merges.

What a person can now do

  • See a basement from above. With a level below grade shown, the ground over it is a veil with a hole in it: the hole's edge is a clean section cut over earth walls, and a deep level reads as a pit with strata. Several levels below grade work: the hole follows the level shown and those between it and grade.
  • Dive under the ground. The orbit may go under any building, down to a floor 3 m under the lowest ground. Under the ground the world turns to a dark earth (soil sky, fog past the shown level, a vignette, a light from the eye); crossing the ground is a 0.4 s fade. Reduced motion or an animation setting of Off makes it instant.
  • Set the ground floor. The levels panel has Set as ground floor; the building names the level at grade (groundLevelId; absent = the lowest level not under the datum). One undo step. Adding a basement goes under the lowest floor and leaves the building where it stands.
  • See the foundation. Every building gets a generic foundation under its exterior walls (a concrete footing and a stem, derived from the walls), drawn under the ground and visible when diving. A plugin that draws its own foundation (Bones) takes that level out of the generic one (drawsFoundation).
  • Build a basement wall that is a foundation wall. A wall against the ground on a level below grade defaults to the building's regional construction: 8 in CMU with 2x4 treated furring laid flat inside (United States and where nothing is known), 25 cm cast concrete insulated and waterproofed outside (Europe). The ground floor is seated 8 in over grade, the top of the foundation. A partition inside the outline is an ordinary wall; a wall given a thickness or an assembly keeps it; existing walls are never rewritten.
  • View bar: a Ground toggle, Auto / Solid.
  • Agents (MCP): set_ground_level; list_levels answers groundLevels and, per level, gradeStanding and isGroundLevel; verify_scene says basement_at_grade and ground_level_unseated; the MCP guide names the ground line.

What is in it

  • core: groundLevelId on a building, the ground line (ground-level, grade-standing, subpath exports), buildGroundLevelChanges, buildLevelBelowChanges, belowGradeFloorY, the generic foundation and FOUNDATION_STANDARD, foundationReadingOf / construction by region (block, cast concrete), set_ground_level contract and operation, drawsFoundation on a node definition, the hole's box that follows the level shown.
  • viewer: the ground veil and ground window, the underground ambiance, the eye's fill, undergroundLook.
  • nodes/site: the hole, its edges, the pit, the generic foundation renderer.
  • editor: orbit dive and its clearance, view bar toggle, levels panel, addStoreyBelow.
  • mcp: set_ground_level and README/guide rows. Docs: wiki/architecture/{layers,materials-and-themes,vertical-model}.md, the pascal-3d skill's tool workflows.
  • No schema migration: groundLevelId is an optional field on the building node.

What stays on R&D

  • The other edge looks (noise, quiet, section) and the noise transition: still in the code as EDGE_LOOKS rows and a uniform, unexposed. The shipped look is DEFAULT_UNDERGROUND_LOOK = { edge: 'section-earth', transition: 'fade' }.
  • The roof veil; insert a storey between under the ground line (needs the porch roofs); stone construction and the facade-style choice of the foundation (read facade nodes); create_story_shell foundation walls (MCP-only on main); storey facts on list_levels; the grade-aware core checks (room-light, enclosure, unroofed storey, facade columns, circulation) and the Flow grade part.
  • Not here, by design: design brief, catalog, Flow fixes, facade work, terrain shaping tools, +0.20 seating of new houses.

Main wins duplicates: Wassim's floor foundations (#976), editing foundations (#1039) and recessed-slab-ground-holes.ts are untouched; the generic foundation is derived and independent of set_floor_foundation.

Tests

With dists rebuilt, against the baseline at 82153a03c (no new failure in any suite):

Suite main this head
core 4955 pass / 0 fail 5100 pass / 0 fail
viewer 810 pass / 1 fail 862 pass / 1 fail
nodes 4961 pass / 1 skip / 0 fail 5012 pass / 1 skip / 0 fail
editor 2590 pass / 3 fail 2629 pass / 3 fail
mcp 1128 pass / 0 fail 1152 pass / 0 fail
editor check-types 2 errors the same 2 errors

The viewer failure (R3F 9.8.2 pointer-event differential), the 3 editor failures (standalone history source invalidation) and the 2 type errors (ceiling-bracket-batch.test.ts) exist on main. Biome: 0 errors, 166 warnings (165 on main; the added one is a core fixture imported by relative path in thumbnail-generator.test.ts).

Pictures (headless WebGPU, no GPU farm): from above with the section-earth edge, below, the fade midway, a foundation from under the ground, a basement with its walls. Not tested by a person in a browser.


Note

High Risk
Changes vertical stacking, terrain/grade interpretation, and automatic wall construction in agent operations—errors could mis-seat buildings or assign wrong foundation assemblies across regions.

Overview
Underground and ground-line modeling lands in core and agent tools: buildings can name a ground floor (groundLevelId), set_ground_level seats the stack (foundation top over grade, renumber indices, optional building Y shift), and add_level below uses buildLevelBelowChanges so existing floors keep their world positions.

Below-grade behavior is computed from terrain, named ground line, and per-slab floors (excluding site flatwork): levelGradeStanding, hole/cover footprints (belowGradeFootprints, coveredBelowGradeFootprints), pit depth, and scene-view notes/auto interior for fully buried levels. list_levels and verify_scene surface ground seating and mis-seated basements.

Foundation walls apply on below-grade add_wall / create_room by site region (US CMU+furring vs Europe cast concrete); explicit thickness/assembly unchanged. set_zone_intent can store daylight (artificial light + mechanical ventilation) in zone metadata beside other fields.

Package subpath exports add ./grade-standing and ./ground-level; contracts document groundLevelId and daylight metadata.

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

AxiomeCG and others added 30 commits October 16, 2026 18:56
…y the person and the agent

With a level below grade active, the site's ground (the lot's fill, its
sculpted terrain and the horizon disc) no longer lies over it: the owner's
villa Lower Ground and the Victor's parking were under an opaque lid.

- core: isLevelBelowGrade(nodes, levelId): the level's world floor (stack
  base, building height on the site, highest standing slab top) more than
  0.3 m under the flat ground. Villa Lower Ground and Victor Parking are
  below; the Victor's Floor 1 (-0.11 m) and the villa's Piano Nobile are not.
- viewer: groundAside (not persisted) and a 'ground-aside' hidden reason in
  scene-visibility: a held surface is out of the colour passes, the shadow
  map, every raycaster and Follow's sight, so a click reaches the floor the
  eye sees. siteSurfaces() moves here from bake-thumbnail, with
  siteGroundSurfaces() for the surfaces flagged as ground.
- nodes: the site's ground meshes carry the flag and hold themselves aside
  through a ref while groundAside is set; the lot line and contours stay.
- editor: both level displays keep groundAside on the rule (levels above
  hidden, selected level below grade), never in the site phase.
- capture: an interior view sets the ground aside under a floor below grade
  and stands it under one at grade; a picture of the whole building
  (view_scene, hero, sheets) stands on its ground whatever the person's
  level. sceneViewPlan looks into a below-grade level targeted alone, so
  view_scene does on both lanes.

The live hold uses scene-visibility's layer reasons, not visible=false: a
capture can then lift or apply it for one render without touching what the
sheets hide by visibility.

Tests: the predicate on the owner numbers; the plan's default interior; the
hold and its capture lift; the site renderer's surfaces held and released
(flat and terrain); the flag's rule and the site-phase exception; the agent's
planned eye sees the basement floor, not the lawn, on both owner scenes
(trimmed fixtures).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s pictures

Step 2 of the ground veil (Design A): where step 1 set the ground aside,
a faint veil is now drawn in its place, so grade still reads.

- Each solid ground surface (the lot's fill, the terrain's surface, cut
  edges and skirt, the horizon disc) has a veil twin: same geometry and
  place, a direct child of the site. The veil is a MeshBasicNodeMaterial
  (TSL): 10% of the theme's ground colour, unlit, double-sided, no depth
  write, alpha 0 to the scene pass's normal and diffuse attachments (as the
  dust and the circulation ribbon), never picked, stripped from exports.
  The disc's veil covers only the ground past the lot (positionWorld
  even-odd test, insideLotNode now takes the point) and fades out by twice
  the lot's radius.
- The veil is on the scene layer, not GRID_LAYER: the capture camera
  disables GRID_LAYER (thumbnail-generator), and the agent's pictures must
  show the same veil.
- State: the host's level display sets belowGrade (the fact); groundMode
  'auto' | 'solid' is the person's override (not persisted); groundVeiled()
  derives what the renderers read. The capture ignores the override: an
  agent looking into a basement needs the floor.
- Capture: temporarilyVeilGround(veiled) holds the solid ground and shows the
  twins for one render (a below-grade interior), or the reverse (a picture
  of the whole building), then gives the person's view back.
- Follow's sight: the veil is never hit (no-op raycast) and its plain
  opacity is the veil's, under Follow's 0.1 see-through bar.
- view_scene: the note says a floor below grade is seen through the veiled
  ground (MCP lane here; the chat lane passes the plan's interior too), and
  the description says a below-grade level targeted alone is looked into.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and the grade ring

The rest of the ground veil's step 2 (Design A):

- View bar: over a level below grade, "Below grade · ground veiled" (or
  "solid") with Auto / Solid, the person's override (useViewer.groundMode),
  beside the Flow switch of each 3D view.
- Lights: while the ground is veiled, a shadowless directional light shines
  from the eye along the view and the hemisphere's ground colour takes its
  sky colour (lerped like the rest): the live counterpart of the interior
  capture's lightInterior. Intensity 1, to be judged on screen.
- Grade ring: core's belowGradeFootprints(nodes) joins each below-grade
  level's floor slabs into outlines in the site's plan (through the
  building's transform, terrain-support-query's levelLocalToWorldXZ, now
  exported within core); the site draws an unbroken dark ribbon along each,
  as a veil (shown with it, in captures too). For every below-grade level,
  not only the active one, so an agent's picture of another basement has it.
- Terrain no longer shades a veiled basement: the held solid surfaces are
  out of the shadow map too (the key light's shadow camera sees layers 0 and
  SHADOW_ONLY only), so no new code was needed for castShadow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… hill stands in the way

A level counted as below grade only under the flat datum, so a hillside
raised 6 m at the street left every floor 'at grade' and the ground
never veiled. Core now answers a level's standing against the sculpted
terrain over its own footprint (levelGradeStanding: at, partly, or
below grade, the flat-datum answer untouched on flat sites); the view
bar names a partly-buried floor, the grade ring follows it, and
view_scene's notes state the facts.

Where terrain stands between the eye and the shown level or building —
the owner's plateau beside a flattened pad — a ground window opens:
per fragment in TSL, a ray from the eye through a ground fragment that
goes on to meet the shown box is veiled there and solid elsewhere,
mask-cut from the solid ground (shadows too) with the veil sheet drawn
only in the window. A few throttled CPU sight lines
(terrainBetweenEyeAndBox) decide whether it opens and what the bar
says, and keep clicks honest through the veiled patch. The site phase
keeps solid ground; Ground: Auto/Solid stays the person's override.

Captures apply the same window from their own camera
(temporarilyGroundWindowForCapture in interior and view_scene poses),
so a picture from uphill sees the house on both agent lanes.
…e, partly buried from above

Three review blockers on the ground window and roof veil:

- Both agent lanes ask with perspective/ortho poses, not cameraPose,
  so exterior view_scene inherited the person's window while its note
  claimed a veil. Posed exterior captures now go through
  exteriorGroundForCapture and decide from their own camera; the
  person's window is restored by the same restore owner, on failure too.
- Exterior and whole-building captures inherited the person's hidden
  or cut roof. They now emit wholeRoof beside wholeWalls; the roof
  renderer lifts its veil for the frame (clip off, layers, materials
  and picks restored) and the next live frame puts it back. Interior
  captures emit no wholeRoof and keep the veil.
- A partly buried floor could stay solid from above: sight samples
  over the footprint were discarded, and the five corner/centre lines
  never met ground sculpted over the middle rooms. Sight lines now
  run to a grid over the floor with in-footprint samples counted
  (the straight-down line included), so the window opens there; and
  sceneViewNote's partly-below-grade line states only the standing —
  the veil claim comes from the pose-based fact, which the capture
  now backs.
…st the shown level, vignette, the sky's light down and warm, the veil in the earth's tone, eased over 0.7 s

One factor drives it: the shown level below grade with the ground on Auto, or the eye under the
ground at its own position (a free orbit under the lid). It eases in and out over a short
crossing, instant when the animation setting is Off or motion is reduced, and Solid turns it off
with the veil. The shown level's materials are untouched; the eye's own light comes up as the
sky's comes down. A capture is taken in the world's own light. The view bar names the level
quietly once the scene says it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e, the eye's light up as the sky's comes down, the grade ring as the lid's rim

Rendered on a real GPU against both owner scenes: the rooms keep 90-100% of their light, the
backdrop turns to a warm soil, the grade ring reads as a light rim. The wiki says what each
piece is and where it lives.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ure; the fog's box comes from the selection when the host keeps no ground window; the hero bake holds the ambiance off

The fill no longer mounts at full: the ambiance owns its intensity (0 to 2.4 over the crossing,
0 in a capture, so a picture taken from elsewhere gets no live light from the person's eye).
Without a ground window box from the host (Preview, the embed) the fog keeps the shown level, or
the building, clear from the selection alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…er a level below grade seen from above, the dark mode only when the eye is under the ground

Above the ground the ground stays solid and a hole opens in it over the below-grade footprint (a
signed-distance texture baked from the grade ring's polygons, one texture fetch and one perlin
lookup per ground fragment, a thin slate rim along the cut, no shadows on the solid ground while
it is open); picks and Follow's sight fall through its guaranteed core. The faint veil sheets
remain for the ground window and for a capture. Below the ground the scene goes to the dark
scene theme's own tokens (backdrop, fog, vignette, sky light, grade ring), the crossing a noise
mask closing over the screen, eased 0.7 s, instant with Animation Off. The trigger is only the
eye crossing the ground surface (0.10 m band each side). The view bar's label is as it was.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ys the day before the blobs reveal it

The dark mode's palette is one place beside the scene theme tokens (UNDERGROUND_PALETTE):
every stop a grey, since neither dark scene theme is neutral (night is navy). Backdrop gradient
stops, vignette, the hole's rim, the grade ring and the sky light's tint read from it. During
the crossing the day's backdrop greys and dims over the first half, so the anthracite blobs
spread over a neutral picture, not a blue sky.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ent the scene has a level below grade, so changing floors changes uniforms only

Measured on the villa: the switch to the basement had a 430-450 ms frame, its CPU time in TSL node
builds (the fill light mounting changes every lit material's shader). With the fill always mounted
the warm switch is 17 ms. A viewer store flag hasLevelBelowGrade (set by GroundHole from the
below-grade footprints) makes the ground materials carry the hole's mask only when it is true, and
the fill is mounted once from then on. Tests: the mask is in the ground from the first level below
grade, the eye's fill stays once mounted, and switching floors adds no light and rebuilds no ground
material.
…t feather and a thin light line (rim variant a)
…building where it stands (a new lowest floor at offset 0 had lifted every floor above it by its height)
…tween it and grade, per level standing computed once), and the stack's box for the underground's fog
…n level and those between it and grade, fog over the shown stack, eye kept over the ground when the pivot goes down) and the walkthrough (solid ground, no dive, the earth outside a basement window)
…s to a deep level; wiki: several levels below grade and the walkthrough
…e, as core's groundHeightAt takes it on this base
…rade (groundLevelId, default read off the elevations), levels under it read below grade, set_ground_level seats the building and renumbers, a basement stacks under the lowest floor without moving the ground floor (add_level), list_levels and verify_scene read it; the reference inventory classifies groundLevelId
…one undo step), set_ground_level on the MCP, the building's groundLevelId as a plain level id
…along the hole's own contour down to the shown level's floor, a dark floor under what the level does not cover; uniforms only, none while the eye is under the ground
…at or hatched face), quiet noise, today's — all uniforms; wiki
…et's earth walls); the hatched look removed; the fade is the default transition
…earth walls and the simple fade

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng and stem, derived), the standard foundation numbers in one place, a drawsFoundation capability, the deepest drawn Y and the lowest ground
… terrain and wall helpers (terrainFieldOf and groundHeightAt take the site alone; getWallCenterlineSegments joins the wall curve helpers)

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

Comment thread packages/core/src/lib/deepest-drawn.ts
deepestDrawnY took the highest standing slab top and subtracted the thickest
slab of the level, recessed plates included. On a split-level floor that is
too shallow (the lowest underside is a different slab), and a thick recessed
plate could deepen it past anything drawn. Each slab now gives its own
underside (elevation less thickness); the footing sits under the lowest floor
underside, never above the walls' base, as the generic foundation places it,
and a recessed slab (a basin) counts by its own underside with no footing.

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

Copy link
Copy Markdown
Contributor

Heads up: the latest push fails the quality job in @pascal-app/ifc-converter#test:

(fail) scaling > grows linearly with the number of rooms
expect(large / small).toBeLessThan(6)
Expected: < 6
Received: 6.139764452168366
  at tests/export-review.test.ts:351:27

It's just over the limit, so it may be a perf regression from the basement changes rather than flakiness. Could you take a look and get it green? Once CI passes we'll do a visual review.

AxiomeCG and others added 7 commits October 20, 2026 14:36
Eleven files conflicted; main wins what it solved, the underground work stays beside it.

view_scene (core, MCP): main's Solo/Exploded presentation and room interior ({zoneId}) stay as they
are; the underground branch's below-grade reading is added to them. A level below grade targeted
alone is looked into when no interior is asked (belowGradeInterior, after main's interiorRequest);
sceneViewNote carries the below-grade, ground-in-the-way and room-eye notes together; the contract
text is main's with the sentence on a level below grade; the MCP result keeps main's
presentation field.

Viewer: temporarilySetGroundAside beside main's temporarilyHideFromScene; DEFAULT_UNDERGROUND_LOOK
beside main's resolveLook / chooseLook and both tests; lights.tsx keeps main's store-driven look
(getSceneTheme is no longer used there) and the underground sky lights. lights.test.tsx was added
on both sides: main keeps the name, the underground ambiance tests move to lights-ambiance.test.tsx.

Nodes: the site ground keeps the underground veil and takes main's showGround gate, on the horizon
disc's veil too, so an authored landscape that replaces the ground replaces its veil.

shared-tools.ts: setGroundLevelTool and validateDesignTool are both imported.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
showGround false says other geometry (an authored landscape, hosted on a level above the ground)
supplies the ground. Shown over a basement, the level display hides the storeys above it, the
landscape with them, and the site drew nothing: the basement's outer walls and footing were seen
through nothing. While a level below grade is shown (belowGrade, set by the level display), the
site's ground now stands in, with its hole, veil and pit, drawn as with showGround on, so only the
opening is seen and the walls only when diving. With a level at or above grade shown, or no level
below grade in the scene, showGround is the author's and the two grounds never draw together.

siteGroundShown (viewer store) is the rule; the site renderer reads it. Tests: the pure rule, the
stand-in on, off, live with the display, on a sculpted site, with the hole in its mask, and main's
showGround behaviour with no level below grade. Wiki and the pascal-3d skill say it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The footing under a level below grade tops out at the underside of its floor plate, and the pit seen through the hole drew its dark floor 5 cm under the top of that plate: for the default plate (0.05 thick) the two are one plane, so the 10 cm of footing outside the wall flickered against the pit floor. The same holds for a thinner plate and for one whose top is the level plane.

core: levelPlateUndersideWorldY is the one reading of that underside (genericFoundation took it from its own copy), groundPitFloorY is where the pit draws its floor: 5 cm under the plate's top as before, and 2 cm under the underside where those would meet. The pit draws its floor and its walls' foot there; the footing and the stems are untouched, so the footing stays visible through the hole as it was meant to be.

Tests: the footing's top against the pit floor on four plates (red at the previous head, with the owner's numbers: footing -2.9002..-2.697, pit floor -2.697), the pit floor function, the underside, and the renderer's uniform end to end.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With a level below grade shown (the floors above hidden, the ground open over it), Follow's look at the whole house, which comes after furniture lands and after every finished commit, showed the floors above and so closed the ground; the next piece on the basement opened it again. In a turn of seven pieces on the basement and one over it the hole closed six times and opened five.

That look is now a look at the shown level (stayOnLevel: the level whole, no roof to clear, no floor to change) while the ground is veiled over it. An edit on a floor the basement view hides still switches to that floor and back, and the person's own display is what Follow keeps, not the finished house.

Tests: the basement's standing under three pieces (red at the previous head: six flips instead of none), one excursion to the ground floor (one close and one reopening), and stayOnLevel.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The ground was a closed sheet across the whole footprint of a basement until its level was shown,
so a stairwell, a floor opening, a section or a walkthrough on the basement stair met a plate of
ground instead of the basement. The part of a basement under the footprint of the levels over it
is now cut open in the ground whatever level is shown, the walkthrough included. From outside
nothing changes (the storeys stand over it); a basement reaching out under the garden stays closed
past the house, no pit in the garden, until its level is shown, and the hole of the level shown
does the rest as it did.

coveredBelowGradeFootprints (core) is the rule: per building, the part of each visible level
below grade (a walk-out only where the ground stands over it) under the floor slabs of the visible
levels over it that are not fully buried, a slab's own openings not counted; a courtyard outline
is left closed. The site renderer cuts it as geometry, like a recessed slab (ground fill, horizon
disc and sculpted terrain, clipped to the lot), so picks pass through with no CPU twin and the
hole, ring, pit and fog are untouched. It closes in one case: a level below grade shown with the
ground on Solid, where nothing covers it and Solid asks for solid ground.

Tests: the cover rule (under the house, under the garden, deep levels, openings, placement, hidden
levels, another building, courtyard, a cellar under a walk-out) and the ground (ground floor,
walkthrough, basement on Auto and Solid, live with the display, no basement, hidden levels, a
sunken room beside it, sculpted ground, a ray, showGround off). Wiki says it.

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

The footing z-fight and Follow Pascal fixes (drawnFloorY, groundPitFloorY, levelPlateUndersideWorldY)
come in beside the ground stand-in and the always-open ground. Conflicts were textual only: the core
imports and exports of below-grade-footprints, its test file (both sides appended tests), and the
`buried` selector of the site renderer, which now carries drawnFloorY and covered together.

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

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

Comment thread packages/core/src/lib/below-grade-footprints.ts
AxiomeCG and others added 2 commits October 20, 2026 17:37
A level shown fully below grade is seen through the hole in solid ground. The ground window also
opened over it on any site that has a terrain field (terrainBetweenEyeAndBox asks the height at the
eye's own XZ, which is above a buried floor on any ground, sculpted or not). The window cuts the
solid ground in the box's silhouette and stands the 10% veil in its place: the basement's outer
walls were seen through a translucent ground in front of the opening instead of only the opening.

GroundWindowSystem now closes the window while the ground is wholesale-veiled (groundVeiled: a level
below grade shown, Ground on Auto, not partly below grade, not the walkthrough). A level at grade, or
only partly below it, keeps the window, as the view bar and the wiki say; Solid keeps it closed. A
capture still asks the window: it sets the solid ground aside already, so the cut lands on nothing.

Test: at grade the window opens; fully below grade it does not (the word, the box, the veil sheets);
partly below it does; Solid closes it. Red without the change. The wiki says it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
add_site_surface lays a lawn, a driveway or a path as a slab on the building's ground level, marked
metadata.flatwork. The grade logic read it as a floor of that level: a lot-wide lawn under a hill
made the ground level read partly below grade, and the hole opened over the lawn's buried part (the
hillside); the same lawn would cover every basement reaching under the garden, and stretches the
level's ground box over the whole lot.

isSiteFlatwork (core) names it, with the check agent-operations/site.ts already made inline, and the
three slab walkers leave it out: levelGradeStanding's footprint samples, the buried and cover plates
of belowGradeFootprints and coveredBelowGradeFootprints, and the level's ground box.

Tests: a lawn on the ground level over a hill leaves it at grade and the hole the basement's (control:
the same lawn as a plain slab is a floor, partly below grade, the hole reaches x = 15); a lawn does not
cover a basement reaching under the garden (control: a plain slab does, up to the hill); the ground
floor's box is the house. The three are red without the change. The wiki says it.

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

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

Comment thread packages/core/src/lib/deepest-drawn.ts
AxiomeCG and others added 3 commits October 20, 2026 17:51
belowGradeFloorY read levelFloorWorldY, the top of the highest standing slab, and groundPitFloorY
hung the dark floor 5 cm under it. On a level with a raised part and a sunken room the pit sat at
the raised plate, so the dark floor passed through the sunken room instead of under it.

levelLowestFloorWorldY gives the top of the lowest floor slab (a recessed slab and site flatwork are
not floors, as for the other storey walkers); the pit hangs from it, so no floor of the level lies
under the dark floor. The same value now sizes the wall's topsoil band, which scales with the depth
to the floor. A level with one floor reads as before.

Tests (the first red without the change): a split-level basement puts the pit 5 cm under the sunken
room's top and above none of its floors; a basin and a lawn laid lower do not take the pit down.

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

The flatwork commit left it out of the grade samples, the buried and cover plates and the ground
box, but levelPlateUndersideWorldY (the pit's clearance and the generic foundation's footing plane),
levelFloorWorldY's standing top and deepestDrawnY still read a lawn or driveway on the level as a
floor slab. A driveway laid lower than the house's slab took the pit floor, the footing and the dive
depth down with it.

One isFloorSlab predicate (not recessed, not flatwork) in storey.ts serves the standing top, the
lowest top and the underside; deepestDrawnY skips flatwork. The wiki lists them.

Tests (all three red without the change): a driveway deeper than the house's slab leaves the dive
depth, the pit's clearance from the plate underside and the footing's top where the house puts them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/core/src/agent-tools/view-scene.ts

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

Comment thread packages/core/src/lib/below-grade-footprints.ts Outdated
AxiomeCG and others added 2 commits October 20, 2026 18:36
…ts highest floor

The hole's cut over a level partly below grade (belowGradeFootprints) and the standing's
buried-sample count (levelGradeStanding) compared every slab to levelFloorWorldY, the top of the
highest floor. The sunken room of a split level was judged against the raised part's top, so the
ground over the room was not "over the floor" until it stood 0.3 m above the raised part, and the
hole stayed closed (or the level read at grade) over space that was already under the terrain.

Each slab is now judged at its own floor: levelPlaneWorldY (the level's stacked base and the
building's height) plus the slab's elevation. footprintFloorSamples carries that Y with each
sample; footprintSamples is its plan-only view.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
levelGroundBox floored its box at levelFloorWorldY, the top of the highest floor. The ground window
aims its sight lines at that floor, so ground standing over the sunken room of a split level but
under the raised part's top did not arm the window, and the fog's clear region and the window's
box stopped above the room. The box now runs from the lowest floor (levelLowestFloorWorldY) to the
same top as before, the highest floor's ceiling. A level with one floor is boxed as before.

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

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

Comment thread packages/core/src/lib/grade-standing.ts Outdated
AxiomeCG and others added 4 commits October 20, 2026 19:08
… of its basement

The walker's floor is the merged collider mesh, and the site ground in it (the flat slab, or the
terrain) was a sheet across the whole lot. The ground is drawn open over the covered below-grade
footprint (coveredBelowGradeFootprints) so the stairs show, but the sheet stayed in the collider: a
walker going down the stair from the ground floor met it at grade and could go no deeper.

cutGroundCollider cuts the ground's triangles against the same rings in plan (heights and normals
interpolated from the source triangle, facing kept; a failed clip keeps the triangle whole), for
the flat slab and the terrain alike. Outside the rings the ground is as before.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
levelGradeStanding fell back, with no terrain field, to isLevelBelowGrade, which reads the highest
floor slab. A split level whose raised part sits at the datum read at grade, so the ring, the hole
footprint and the pit's depth never took in the sunken room beside it.

With no field and no ground line named, each floor slab is now judged against the datum (the
sampled path's reading, with a flat ground): none buried is at grade, all buried is below grade,
some is partly below grade. levelPlates puts only the buried slabs of such a level in its buried
set. A level of floors at one height reads as it did. isLevelBelowGrade stays the whole-level
predicate (it derives the ground level); its doc says so.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A habitable room (bedroom, living, kitchen) needs a window to outside or the IRC R303.1 exceptions:
artificial light and mechanical ventilation. The declaration had no writer both surfaces share. The
shared set_zone_intent patch now takes daylight: { artificialLight, mechanicalVentilation } | null,
kept in the zone's metadata under daylight (no schema field, no migration; null clears it, other
metadata stays). The tool contract and the command's patch schema both carry it, so the chat and
the hosted MCP offer it from one contract; the description says what it is for.

Tests: the command (set, beside other metadata, cleared, untouched by other patches, both booleans
or a refusal), the tool's patch, and two shared cases (declare; clear) that the table runs on the
core operations, the MCP and the chat alike. The metadata-key inventory lists daylight as a value,
not a reference. The wiki says it (space-detection).

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

A basement's outline walls are built as foundation walls (FOUNDATION_CONSTRUCTIONS): CMU block with
furring, or insulated and waterproofed cast concrete. Their assembly has no finish layer, so the
exterior face fell back to the white slot default, above grade and from under the ground, unlike
the generic foundation drawn under them.

wallAssemblyExposedStructure (core) names what a wall's exterior face shows when its assembly has
no cladding and no lining over a masonry or concrete core: 'cmu' or 'concrete', null for a wall
with a cladding, a lined partition, a framed or foam core, or an unresolved exterior side. The
viewer's wall materials dress that face with the generic foundation's own materials, moved to
viewer/src/lib/foundation-materials.ts and shared with generic-foundation.tsx: courses of block
with joints and the block colours (courses from the world's Y, bonded along the face's world
planar U), the same grained concrete; solid shading keeps a flat colour. Paint on the face still
wins, interior faces are unchanged, and no WALL_ASSEMBLY_PRESETS entry changes its look.

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

Copy link
Copy Markdown
Contributor Author

Heads up: the latest push fails the quality job in @pascal-app/ifc-converter#test:

(fail) scaling > grows linearly with the number of rooms
expect(large / small).toBeLessThan(6)
Expected: < 6
Received: 6.139764452168366
  at tests/export-review.test.ts:351:27

It's just over the limit, so it may be a perf regression from the basement changes rather than flakiness. Could you take a look and get it green? Once CI passes we'll do a visual review.

Thanks for flagging. That run (88042f5) was a one-off: the scaling test compares two timings and landed at 6.14 against a limit of 6. It has passed on every CI run of this PR since then, at every later head including the current one, and the full CI on c4b8822 went green. The PR doesn't touch ifc-converter, so this reads as runner load rather than a regression. The remaining red on some runs is the 5 verify_scene tests that fail on main too.

cursor Bot pushed a commit that referenced this pull request Oct 20, 2026
Conflicts resolved keeping main's behavior, carried into the scene runtime:
- history-control: main's commit authors, write guards and derived-write notes; the
  derived-write notes and write guards are per scene environment, like the PR's other commit
  state, so a detached scene's writes never reach the live commit or end the editor's preview.
- roof elevation: main's wall-top support (#1097) and roofsKeptOverStorey (#1112 era roof
  levels) run in both the editor's system and reconcileDocument; roofsKeptOverStorey selects
  roofs and reads level elevations only once a roof stands over a storey.
- DECISIONS.md: main's E-017 stays; the scene runtime entry is E-018.
- MCP: main's tool executor and recorder wrap the PR's scene scope; compileObject reads the scene
  before its first await and takes a pre-minted id; main's activeLevelId, briefs, bpy and
  materials go through the bridge's runtime.
- agent operations: main's new code selects what it reads (queryNodes) or says why it reads the
  whole scene, per the raw-scene-enumeration rule.
…ot how the scene is built

Trims nine test files of the underground work (ground veil/hole/standin/covered, generic
foundation, lights ambiance, underground ambiance, thumbnail capture, below-grade follow) by 960
lines. Each file keeps its owner- and Bugbot-reported defects as behaviour checks and drops mesh,
material and userData inventories and cases that re-prove a core rule. Scene builders and
tables are shared. Tests only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@wass08
wass08 merged commit 1ba76e7 into pascalorg:main Oct 20, 2026
5 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 55d7251. Configure here.

.join(' ')
.split(',')
.map((name) => name.trim()),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dutch addresses miss European region

Medium Severity

regionOfAddress folds the last address token and looks it up in EUROPEAN_COUNTRY_NAMES, which lists netherlands and nederland but not the netherlands. A dossier that ends with the common English form therefore stays generic and new basement walls get the 8 in CMU stack instead of cast concrete.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 55d7251. Configure here.

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.

3 participants