# home/ — layout geometry + rule tests for `../home.html`

`home.html` is the Three.js visualization. The **pure, rule-bearing math** now lives
here in one framework-free module so it can be tested without a browser, and so the
tests can never drift from what the page actually builds (`home.html` imports this
same module).

## Files

| File | What it is |
|------|-----------|
| `geometry.js` | Pure layout geometry — no three.js. Footprint (OUTLINE / WALL_INNER), arcade layout on wall 14, per-floor wall openings, mitred inner ring, and the named-wall registry `WALLS`. Single source of truth. |
| `geometry.test.js` | The design rules encoded as `node:test` assertions. |
| `walls.js` | The perimeter **walls** as three.js meshes, built from `WALLS`: the walls, loggia back wall, arcade columns, and the wall-name labels. `createWalls(deps)` factory. |
| `slabs.js` | Everything horizontal **plus the corner stair** (a tread is just a small slab): floor slabs (`makeSlab` — the Floor-2 slab is cut with a stairwell opening), flat plates (`makePlate`), `makeCorbel`, the mid-landing (`makeLanding`), the reusable **`makeStep`** component (tread + both rods + support corbel), and the staircase (`buildStairSolid`). `createSlabs({THREE,GEO,world,extrudeXZ,concreteMesh})` factory. |
| `package.json` | `"type": "module"` so Node treats the `.js` files as ES modules. |

### Componentisation

The building is split by element type: **walls** → `walls.js`; **slabs / plates /
steps / stairs** → `slabs.js`. `home.html` wires them and keeps only the
renderer/scene/lights, the shared `extrudeXZ`/`concreteMesh` helpers, the roof +
ceiling deck, plan lines, and dimensions. A **"Walls & roof"** checkbox (`#wallsOn`)
hides the walls + roof (tracked in a `wallsRoof[]` list) while keeping slabs + stairs.
A stair step is a reusable component: `slabs.makeStep({ base, adv, wv, y, aLo, aHi,
width, thickness, cornerR, rod, support })` builds the WHOLE repeated unit — one tread
(rounded outer corners) **plus its two balustrade rods** (a corner rod from the tread
underside + a middle rod from the tread top surface, doubling the rod frequency) **plus
its support corbel** — all from a single `rod` spec; calling it per tread with a shared
`rod.top` gives a different rod length per step.

### The corner staircase (in `slabs.js`)

Quarter-turn (dog-leg) stair for **one storey** (floor 0 → 1) in the 108° corner at
OUTLINE vertex 13. Lower flight hugs `bigLeftWall`, upper hugs `bigFrontWall`; both
run parallel to their wall with **square treads**. The mid-landing **platform** is a
quadrilateral: internal edges on the wall faces (∥ walls), external edges ⟂ the walls
(the flight nosings — which is what keeps the treads square). **Style**: cantilevered
**ebony** stone treads (40 mm, going 27.5 cm, each extended 2.5 cm back under the step
above → 2.5 cm overlap, outer corners rounded to **twice the rod radius**), and a
Ø15 mm cylindrical rod balustrade — **two rods per step** (a corner rod concentric with
the rounded corner, from the tread underside; a middle rod from the tread top surface,
doubling the frequency), rising up through the step above and **cut where it meets the
floor slab above**. The lower flight's ground-level tread is dropped (redundant with the
ground floor). The Floor-2 slab is cut with the stair's footprint so the flights connect
the two floors. (The x-ray debug guide lines were removed once the stair was finished.)

### The named-wall registry (`GEO.WALLS`)

Each wall is one entry — `{ name, i, j, arcade? }` — where `i→j` is the OUTLINE
edge span (a span that skips a straight-through vertex, e.g. `4→6`, is the *merged*
run that reads as one wall). Walls are named **position-wise**: `bigFrontWall` is the
arcaded facade (formerly "wall 14" / edge 13), `bigRightWall` / `bigLeftWall` are the
two merged long runs, and so on. `wallLabel('bigFrontWall') === 'big front wall'`
turns a name into its painted display label. Both `walls.js` (meshes) and the tests
drive off this one list, so the code and the labels can never disagree.

## Running the tests

From this directory:

```
node --test
```

(or `npm test`). No dependencies to install — it uses Node's built-in test runner.

## The rules the tests protect

Each of these took real back-and-forth to settle; the tests catch a regression
before it ships:

1. **Footprint** — OUTLINE is exactly 14 vertices; WALL_INNER is 12. The reflex
   (concave) corners of the reversed-S are exactly vertices **3, 4, 10, 11**.
2. **Openings rule** — **only wall 14 (OUTLINE edge 13)** has openings. Every other
   edge is solid — **walls 3 & 4 in particular** are always solid on every floor.
3. **Arcade** — 5 equal doors fit between wall 4's perpendicular projection onto
   wall 14, with **0.30 m** twin-column gaps and **0.15 m** end insets, giving a
   **~1.59 m** door clear width. Door centres are evenly spaced by (door + gap).
4. **Per-floor composition of wall 14**
   - Floor 0: 4 horseshoe doors + 1 double-height **portal slot** (centre axis).
   - Floor 1: the **portal crown** (grounded — reaches the wall base, so the opening
     is clean with no slab crossing it) + 2 window holes.
   - Floor 2: 5 horseshoe doors (upper loggia).
5. **Continuous facade** — wall panels span the full floor pitch from a plinth one
   slab-thickness below the floor line up to floor height, so there is no floor-line
   crease in solid mode.
6. **Seamless corners** — the inner wall ring is **mitred** (`insetOutline`): one
   inner vertex per OUTLINE vertex, shared between neighbouring panels, so the
   reflex-notch walls (3 & 4) meet exactly with no gap, overlap or shift.

## Notes on `home.html`

`home.html` imports `geometry.js` as `GEO` and wraps its plain `{x,z}` / `{x,y}`
points in `THREE.Vector3` / `THREE.Vector2`. It creates the element modules —
`createWalls({…})` and `createSlabs({…})` — then in the level loop calls
`slabs.makeSlab(i)`, `walls.makeWalls(i)`, `walls.makeBackWall()` (omitted on Floor 2),
`walls.makeColumns(i)`, `walls.buildWallLabels()` (once the webfont loads), and adds
`slabs.buildStairSolid()` to Floor 1's level group. What stays in `home.html`: the
renderer/scene/camera, lighting, ground, plan lines + sketch mode, the shared
`extrudeXZ`/`concreteMesh` helpers, the **roof** (`makeRoof`) + ceiling deck, and
the dimensions.

Fixes that accompanied this extraction:
- **Line cuts** between stacked floors were z-fighting from coincident horizontal
  wall faces — box walls no longer emit top/bottom quads (interior, never visible).
- **Walls 3 & 4 shift** came from a plain perpendicular inner offset; the walls now
  use the mitred `INNER_RING` so corners are shared exactly.
- **Fan/triangulation lines on the floor-2 slab (sketch)** came from cutting the
  portal with CSG — CSG re-triangulates the whole cap and leaves T-junctions that
  `EdgesGeometry` can't hide. The portal is now a clean **notch** in the slab
  polygon (`slabPolygon`), so the cap is flat and coplanar like every other floor.
  CSG (`three-bvh-csg`) is no longer used by the page.
- **A straight wall reading as two walls (sketch)** — OUTLINE has collinear
  straight-through vertices (5 & 12), so a straight wall is built from two panels.
  `makeWalls` now **merges all box-wall panels into one welded geometry**: real
  corners keep their edge, but the collinear join is coplanar and gets dropped.
- **Wall faces not mating at wall 14's corners (gap/wedge)** — wall 14 is a prism
  with SQUARE ends, so its inner-end corner is the perpendicular foot of edge 13
  (`OUTLINE[c] − o13·WALL_T`), while a mitred neighbour meets it at a different
  point on the same line → a sliver that opens on some angles. The two box walls
  touching wall 14 (edges 12 & 0) now use that **same perpendicular foot** as their
  wall-14-side inner corner, so their end face is coincident with wall 14's end
  face — a clean butt joint, no gap. (An earlier "corner filler" attempt was wrong
  and made it worse; it was reverted.)
