# LevelSpec 1.1 — what this implementation added

`public/research/docs/LEVELSPEC.md` is the original contract. Implementing it
surfaced a handful of gaps. Each addition below is a compiler-owned primitive,
in keeping with the original rule: the model states intent, the compiler owns
geometry. None of them hands the model a coordinate it did not already have.

## Spec additions

### `wall_overrides[].type: "open" | "glass"`

The original types are `hard`, `soft`, `reinforced`. Two more earn their place:

- **`open`** — the boundary exists semantically but carries no wall. Needed for
  outdoor route bands, where "lane A is adjacent to lane B" is a real adjacency
  and a wall there would be wrong. Without it the only way to express an open
  boundary was a full-width portal, which conflates *no wall* with *an opening
  cut into a wall*. They behave differently downstream: an `open` boundary has
  no run, no lintel, and no opening record.
- **`glass`** — blocks movement, does not block sight. It is a distinct
  penetration and occlusion class, and it is dynamic.

### `layers[].internal_walls: false`

Present in the original as a layer flag; this implementation defines it as
*resolve every interior boundary to `open` unless an override says otherwise*.
That makes an outdoor map authorable as "everything connects, except here",
which is how outdoor maps actually read.

### `spaces[].open_to: [spaceId]`

Per-space version of the same idea, for merging a handful of spaces without
listing every pair as an override.

### `spaces[].extra` / `spaces[].subtract`

Unions and differences of rectangles. Step 1 of the original extension ladder.
The surrounding courtyard in the siege benchmark is one rectangle minus the
building footprint.

### `spaces[].height` and `spaces[].z_offset`

Per-space ceiling height and floor offset, for atria, mezzanines and pits. A
wall run between two spaces of different heights is emitted at the taller of the
two, so the shell stays sealed.

### `layers[].railing` and `spaces[].railing`

A balcony edge is a boundary, but a full-height wall there is wrong. `railing`
emits exterior runs of that layer or space at the given height instead. The
headroom validator still uses the layer's real `height`.

### `layers[].roof: false`

Suppresses the roof slab, for outdoor layers and open decks.

### `portals[].width_cells: "full"`, and `kind: "open"` defaulting to full

Open the entire shared run. Distinct from `type: "open"` above: this cuts an
opening in a wall that exists; that one means no wall exists.

### `portals[].barricade: true`

Emits a dynamic panel filling the opening — a window barricade or a door leaf —
with a stable id, separate from the static shell.

### `spec.player`

The capsule the clearance validators measure against: `radius`, `height`,
`step`, `crouch`. Previously implicit.

### `spec.expect_fail: true`

Marks a spec that is supposed to fail validation. The CLI treats a failure as
success and a pass as a failure. Used by `zz_broken_by_design`, which exists so
the error channel has a fixture.

### Same-layer vertical connections

`from_layer` and `to_layer` may name the same layer. That makes a ramp or flight
between two spaces at different `z_offset` values authorable without inventing a
storey for it — the outdoor elevation changes on a lane map are exactly this. The
floor hole is punched on that one layer, cutting both spaces' slabs so the ramp
fills the gap.

## Conventions the compiler fixes

### Vertical connections are authored bottom-up

`from_layer` is the lower storey, `to_layer` the upper one. The compiler opens
the floor hole in `to_layer` and grows the flight from `from_cell` toward
`to_cell`. Authoring a descent as `ground → basement` puts the hole in the wrong
slab.

### A stair run spans `from_cell` through `to_cell` inclusive

Six cells apart is a seven-cell footprint. Stairs and cover must clear a wall
plane by at least half the wall thickness; if they do not, the validator returns
`PLACED_SOLID_CLIPS_WALL` naming both solids.

### One owner per junction square (pass 4b)

Not in the original document, and not optional. Where a run along X meets a run
along Y, the little square where their thickness bands cross is claimed by
exactly one of them: runs along Y yield, runs along X claim forward, and a run
with no collinear neighbour behind it claims backwards so outside corners stay
closed. Skipping this puts an overlapping pair at every corner of every map —
the same class of error as two rooms each emitting the wall between them, one
dimension down.

### Every wall straddles its boundary plane

Interior and exterior alike. One rule keeps storeys aligned: a ground-floor wall
against a courtyard and the first-floor wall above it against open air land on
the same two planes. An earlier version pushed exterior walls fully outside the
slab, which offset every storey line by half a wall thickness.

### Walls span the floors they actually separate

A wall sits on the higher of the two floors on either side of it and reaches the
higher of the two ceilings. Where one side sits lower, a **skirt** closes the gap
underneath — emitted on the lower side only, so it never meets the higher side's
floor slab. Anchoring walls to the layer elevation instead would leave a crawl
gap wherever a space sits below its neighbour, which on a map with per-area
elevation is most of them.

### Junction caps carry their own elevation

A run that claims a junction square from a crossing run (pass 4b) is claiming
ground that sits over the *other* run's cells — which may be at a different
elevation. Those claimed stretches are emitted as separate solids based at the
floor of the cells they actually cover. Emitting them as part of the run body
put a wall through a neighbour's floor slab at every split-level corner.

### Cover sits on the floor beneath it

`covers[].z_offset` is a lift above the floor of the cell the cover starts in,
not an absolute elevation. On a level with per-space `z_offset` the two differ,
and the absolute reading buries half the crates in the ground.

### A layer's ceiling is the floor slab of the layer above

Roof slabs are emitted only over cells that nothing is stacked on. This removes
the coincident ceiling/floor pair by construction rather than by tolerance.

## Validator additions

| Code | Meaning |
| --- | --- |
| `SPACE_OVERLAP` | Coalesced per space pair with a cell count, not one per cell. |
| `PLACED_SOLID_CLIPS_WALL` | A stair, ramp, ladder or cover runs into a wall band. |
| `TREAD_TOO_SHALLOW` | A staircase has no room to run (< 22 cm treads). |
| `DUPLICATE_COPLANAR` | Coincident faces sharing an outward normal — the z-fighting class. Near-coincidence within 1 cm counts, because nudging by a millimetre is not a fix. |
| `INTERSECTING_VOLUMES` | Solids occupying the same volume. |
| `PORTAL_SIDE_UNMATCHED` | The pair shares a boundary, but not one facing the requested side. |
| `VERTICAL_ENDPOINT_UNOCCUPIED` | A stair or hatch endpoint is outside every space. |
| `MARKER_UNREACHABLE_CELL` | A gameplay marker sits outside every space. |

Cover blocks a navigation cell when it is taller than the player's step height.
Vaulting is a gameplay affordance, not a navigation guarantee, so the graph
routes around it — and a marker placed on top of a crate is reported rather than
silently connected.

## The navmesh bake

`src/core/navmesh.ts` is not part of the spec, but it is part of the contract:
it is the check that the compiled geometry actually delivers what the spec
claimed. It reads only the solids, never the spec, so its answer is independent.

Rules it applies, and why each one is what it is:

| Rule | Reason |
| --- | --- |
| A surface is walkable if the clearance above it is at least the agent height | headroom |
| Obstacles **within one step height** of the surface are steps, not obstructions | otherwise every staircase erodes away, because the next tread is inside the agent's radius |
| The agent's foot circle must find support at a comparable height on all four sides | otherwise a 24 cm wall top is "walkable" and the map fills with phantom ledges |
| Agent radius is tested exactly at four probe points, not by eroding the sample grid | at 0.25 m sampling, grid erosion by 0.35 m closes every 1 m doorway |
| A thin wall between two samples is caught by probing the midpoint | a 24 cm wall can sit between two samples without clipping either agent cylinder |
| An island is *playable* only if it stands on a declared space's own floor | a crate top is a ledge; and in a space with `z_offset` that floor is not the layer elevation |
| The gate runs on the **breached** state, dynamic surfaces removed | a boarded front door is a design decision; a room no amount of breaching reaches is a defect |

The sealed bake — barricades up, hatches closed, soft walls intact — is reported
alongside it, because "how much of this map can you reach before you destroy
anything" is a real design question and the split makes it answerable.

## Still open

The original document's extension ladder is unchanged and unfinished. Rectangles
are step 1. Arbitrary grid polygons, exact-arithmetic 2D constrained polygons,
ramps as first-class sloped surfaces, splines, and modular-kit sockets remain
future work — added one at a time, each as a compiler-owned primitive, when a
real design need appears.
