9. Components, Editing, and Multi-building
9.1 def (components)
Section titled “9.1 def (components)”def defines a slot-bearing Component, unified with theme and site by the same mechanism. This
keeps the reference system from fracturing across editing, theming, and multi-building connection.
- Parameterization (variable size, etc.) is allowed. Recursion is forbidden.
- A
defmay declarerequires version>=X; the minimum version of a composite is the max of its parts (Versioning and Editions).
def cottage class=house size=9x7: floor id=floor mat_slot=floor walls id=walls class=outer mat_slot=wall height=4 door id=door class=entry side=front at=center roof id=roof kind=gable mat_slot=roof9.2 Editing model
Section titled “9.2 Editing model”Explicit IDs + auto stable addresses, combined. Important members carry id=; unspecified members
get a meaning-based stable address auto-assigned by the compiler. Addresses derive from
parent / role / side / level / offset rather than generation order, so they stay stable under
appends.
Edits are patch DSL against a selector/address:
edit window[class=vent][level=floor2] set shape=archedit window@front[0] set mat_slot=accent_glassedit door[id=entry] set side=front at=centerEditing at the level of a concept (“make only the second-floor windows arched”) must be possible
without breaking the whole. Edit diffs look only at intent_state (Blockstate Model),
so a change in derived results does not harm edit stability.
9.3 Multi-building (site)
Section titled “9.3 Multi-building (site)”Do not make the AI do absolute-coordinate arithmetic. Place via topological relational constraints; resolving to absolute coordinates is the compiler’s responsibility.
site village: place id=home1 use=cottage theme=medieval at=origin place id=home2 use=cottage theme=medieval east_of=home1 gap=4 connect home1.door to home2.door path=@gravelEach struct exposes ports (position / normal / width), and connect joins them. Villages and castles
that exceed the structure block’s 48³ limit are expressed as the composition of multiple structs.
9.3.1 Coordinate convention
Section titled “9.3.1 Coordinate convention”eastadvances along+x;northretreats along-z. This matches thefrontis+zconvention from §5.4 — a building whosefrontfaces south sits with its facade on+z, andnorth_of=Xputs the next placement behind it.- The Y axis is unaffected by topological selectors; every placement currently lands at
y = 0.
9.3.2 Origin selectors
Section titled “9.3.2 Origin selectors”Each place carries exactly one of at, east_of, north_of:
| Selector | Effect | Notes |
|---|---|---|
at=origin | Anchors the placement at world (0, 0, 0). | The only legal at= value. The first place in a site must use this anchor — there is no implicit at=origin default. |
east_of=ID gap=N | New origin = prior (x + dims.x + N, y, z). | ID must name a place declared earlier in the same site body. gap is in blocks and is edge-to-edge (0 → walls touch). Defaults to 0 when omitted. |
north_of=ID gap=N | New origin = prior (x, y, z − dims.z − N). | Same ID and gap rules as east_of. |
Combining selectors (at + east_of, or east_of + north_of) is rejected with
E_INVALID_PLACE_ORIGIN; using at= with anything other than origin is the same error.
9.3.3 Cross-scope references
Section titled “9.3.3 Cross-scope references”use=NAMEmust name a top-leveldef. Unknown names fail withE_UNRESOLVED_PLACE_REF, with a nearest-match suggestion when one fits the standard spell cap (§10.6 ofversioning-editions.md).theme=NAMEmust name athemedeclared in the same file. Unknown themes fail withE_UNRESOLVED_THEME_REF, again carrying a nearest-match note.- A
defthat noplace use=NAMEreferences is reported asW_UNUSED_DEF(advisory) so a typo on theuse=side does not silently produce an empty build. - Two
placerows in one site cannot share anid=; the duplicate is flagged withE_DUPLICATE_PLACE_IDand the diagnostic carries a span pointer back to the first declaration.
9.3.4 Output naming
Section titled “9.3.4 Output naming”The compiler writes one .nbt per place, named after the id= (e.g. home1.nbt,
home2.nbt). The world-space origin and the (site, def, theme) provenance for every placement is
recorded in build.cairn.lock under placements, so a downstream consumer can rebuild the layout
without re-running the coordinate solver.
9.3.5 Ports and connect
Section titled “9.3.5 Ports and connect”A connect FROM.PORT to TO.PORT path=@MATERIAL row lays a 1-block-wide walkway between two named
ports on placements within the same site.
Ports. A port is the (place, member_id) pair PLACE.PORT resolves to. Ports are exposed on
door and window members of the referenced def; stair / roof ports are reserved for a future
extension. The port’s world position is “one block outside the member’s side= wall, at the
placement’s ground row (place_origin.1)” — front/back/left/right map to +z/-z/-x/+x
(§9.3.1). The wall-local offset is taken from the door’s at= value (one of center, left, or
right — see §5.4) for a door and from the rectangle’s geometric centre
(offset + size.w / 2) for a window; either way, the placement’s overhang
shifts the port out into the overhang ring beyond the structure’s outer face. Numeric door
offsets (at=N) are reserved for a future extension. A window must fit both horizontally
(offset + size.w ≤ wall_length) and vertically (y + size.h ≤ walls.height) — a window that
the openings pass would defer cannot anchor a walkway either, and the row drops with a
W_DEFERRED_MEMBER whose notes list the door / window / reserved-role contracts in turn. A
window’s authored y= does not lift the port off the ground row — the walkway is a 1-voxel-thick
flat strip whose Y must agree with the other endpoint — and a sym=true window contributes a
single port at the primary offset side (the mirrored cut still appears in the wall, but the
id= resolves to one coordinate).
Path. The walkway runs as a Manhattan L (x-axis leg, then z-axis leg) at the two ports’ shared
Y — 3D path search (staircases, multi-level walkways) is intentionally out of scope so the port
surface can land in one piece. Cells that overlap an existing structure floor are skipped and the
row earns one W_WALKWAY_BLOCKED warning so the author can widen the placement gap. The warning
also carries a machine-readable payload (data: { kind: "walkway_blocked", skipped: N }) in the
--format json output so LSP quick-fixes and CI annotators can read the skip count without
re-parsing the human-readable message — see §11.2 of spec/lint.md.
Material. The path=@TOKEN value lifts through the same mat_slot= pipeline used for member
materials — concrete tokens like @gravel work without a registry pack; abstract tokens like
@path.gravel require the pack’s materials catalog and surface W_ABSTRACT_TOKEN_DEFERRED /
E_UNKNOWN_ABSTRACT_TOKEN on a miss.
Output. Each connect row writes one .nbt named after its site and ports (e.g.
hamlet_walkway_home1_entry__home2_entry.nbt) and records a walkways: entry in the lockfile
carrying the world origin, dims, and resolved path material — enough to rebuild the strip without
re-running the resolver.
Diagnostics.
E_CONNECT_ARITY— the row’s positional shape is notFROM.PORT to TO.PORT(a half is missing, the literaltokeyword is missing or replaced by another token, or extra positionals trail the row pastTO.PORT). Anchored at the missing-positional cursor, the offending separator, or the run of trailing extras so thefile:L:Cpointer lands where the fix goes.E_UNRESOLVED_PORT— the right-of-dot port id does not name a member of the referenced def (with a nearest-matchdid you meannote when one fits the standard spell cap).E_AMBIGUOUS_PORT— the def exposes the sameid=on more than one member; rename the collision so the reference is unique.E_MISSING_PATH_MATERIAL— the row omitspath=; walkway lowering has nothing to lay.E_UNRESOLVED_PLACE_REF— the head place id (left of the dot) does not name a prior place in this site, shared with §9.3.3.W_WALKWAY_BLOCKED— the L-shaped path crossed an existing structure floor; the colliding cells are skipped and the rest of the strip still lays. JSON payload exposes the skip count asdata.skippedso tooling does not need to re-parse the message text.W_DUPLICATE_WALKWAY— the same(from, to)port pair has already been laid in this site; the duplicate row is dropped.