Skip to main content

Add a storey

Put an upper storey onto a single-storey description, then confirm that the section adds up and that the new rooms can reach the outside.

File paths in the output below are absolute when you actually run these commands. They are shortened to bare filenames here for readability.

Before you start

1. Declare the level

level is a fact about the whole building, and the entry file (the base layer) declares it exactly once, even when the building is split across files. The positional z is millimetres above datum, h: is the standard ceiling height, slab: is the thickness of the floor construction on that storey.

level L1 0    h:2400 slab:150
level L2 2900 h:2400 slab:500
level R  5800 slab:500

Declare the topmost level too, even though no space sits on it. R above is that level: L2's storey height (R's z minus L2's z) only exists because that line exists. Without it the top storey has no ceiling above it, and a ceiling running straight through the roof draws no complaint.

A level must be declared before it is used. Write /L2/… spaces above the level line and their level is undetermined. The grammar and the attributes it accepts are in level.

2. Write the spaces on the new storey

If the first segment of the path is a declared level name, the space belongs to that level.

space /L2/bed  bedroom X1..X2 Y1..Y3 name:Main-bedroom
space /L2/hall hall    X2..X3 Y2..Y3 name:Upper-hall

If you keep level names out of paths — rooting paths at the dwelling, /home/bed1 — say it with level:.

space /home/bed1 bedroom X1..X2 Y1..Y3 level:L2 name:Main-bedroom

Write neither, and a space that has a region has no determinable level. That is SUF02, an error.

✖ nolevel.muro:line 10: /home/a has a region, but its level cannot be determined (give it at the head of the path or with level:)

Writing the path /L2/… is not itself a declaration of the level — the level L2 2900 line is still required.

3. Connect the storeys

The default between two spaces stacked on top of each other is a floor. Write nothing and there is no stair and no void: the upper storey floats, and check stays green. Stairs, shafts and voids are covered in Connect storeys. One line is enough for now.

boundary /L1/hall /L2/hall type:stair

A relation that spans storeys belongs to no single storey's layer. Put it in the base layer if the building is split across files.

4. Write doors from the new rooms to the stair

The default between two spaces that touch is a wall with no door in it. Adding rooms upstairs connects them to nothing.

boundary /L2/bed /L2/hall t:120
  door w:800

Check it

All of the above in one file:

koyu 1.0
name Two storeys
unit mm

grid X 0 3640 7280
grid Y 0 3640 7280

level L1 0 h:2400 slab:150
level L2 2900 h:2400 slab:500
level R  5800 slab:500

space /L1/ldk  ldk     X1..X2 Y1..Y3 + X2..X3 Y1..Y2 name:LDK
space /L1/hall hall    X2..X3 Y2..Y3 name:Entry-and-stair
space /L2/bed  bedroom X1..X2 Y1..Y3 name:Main-bedroom
space /L2/hall hall    X2..X3 Y2..Y3 name:Upper-hall
space /L2/void void    X2..X3 Y1..Y2 name:Over-the-living-room
space /out exterior name:Outside

boundary /L1/ldk /L1/hall t:120
  door w:800 edge:E
boundary /L1/hall /out edge:E t:150 spec:EW
  door w:900 name:Front-door
boundary /L2/bed /L2/hall t:120
  door w:800

boundary /L1/hall /L2/hall type:stair
boundary /L1/ldk /L2/void type:void
npx tsx src/cli.ts check two.muro
✔ Consistent — 6 spaces / 7 boundaries
  Structural consistency only — architectural validity is what koyu validate says, separately

koyu levels prints the section as text. Whether ceiling plus slab fits inside the storey height is right there on the arrow line.

$ npx tsx src/cli.ts levels two.muro
R	z:5800	slab:500
L2	z:2900	h:2400	slab:500
  ↑ storey height 2900 = ceiling 2400 + slab 500
L1	z:0	h:2400	slab:150
  ↑ storey height 2900 = ceiling 2400 + slab 500

Whether you can actually get upstairs is koyu doors's question. It prints the fewest doors and the route.

$ npx tsx src/cli.ts doors two.muro /L2/bed /out
2 doors — /L2/bed → /L2/hall → /L1/hall → /out

Green does not mean you can get upstairs

check says that what is written does not contradict itself as data. Forget the vertical boundary and the upper storey floats, still green. Here is the same file with the one line from step 3 removed.

$ npx tsx src/cli.ts check two-sealed.muro
✔ Consistent — 6 spaces / 6 boundaries
  Structural consistency only — architectural validity is what koyu validate says, separately

$ npx tsx src/cli.ts doors two-sealed.muro /L2/bed /out
Cannot reach /out from /L2/bed

Run doors or koyu validate every time you add a storey. Find spaces you cannot reach sweeps for sealed rooms mechanically.

Where it goes wrong

The ceiling eats into the storey above — HGT01 (error)

If ceiling height plus the slab of the storey above exceeds the storey height, the height invariant is broken. Raise the entry hall's ceiling alone:

space /L1/hall hall X2..X3 Y2..Y3 name:Entry-and-stair h:2600
✖ two.muro:line 13: /L1/hall collides into the floor above: ceiling height 2600 + L2's slab 500 = 3100 > storey height 2900

Lower the ceiling, raise level L2's z to buy storey height, or thin the slab. Only a lower storey that is almost entirely void may declare a ceiling height that crosses a storey.

No floor, no ceiling — SUF03 (warning) / SUF01 (error)

Forget slab: on the upper level and not one floor is generated on that storey; drop h: as well and the ceiling height is undetermined. No default is invented where a value is missing — the sufficiency checks say so out loud rather than letting a thin model ship silently.

koyu 1.0
name Missing pieces
unit mm

grid X 0 3640
grid Y 0 3640
level L1 0 h:2400 slab:400
level L2 2900

space /L1/a room X1..X2 Y1..Y2 name:Downstairs
space /L2/b room X1..X2 Y1..Y2 name:Upstairs
space /out exterior name:Outside
boundary /L1/a /out edge:S t:150
  door w:900
⚠ thin.muro:line 8: Level L2 has no slab:, so not one floor is generated on this storey
✖ thin.muro:line 11: The ceiling height of /L2/b cannot be determined (neither the space's h: nor level L2's h: is there)

Diagnostic codes come out of check --json. The table from code to cause is the diagnostics reference.

Next