Author diagrams

Use the strict 0.4 grammar

Place typed nodes, rotate canonical geometry, and connect validated semantic ports.

sheet
2 / 13
rev
v0.4
sections
3
compiled
3

01 / NodesDeclare one bounded component per line

One declaration, one line:

kind:ID "label" at (x, y) #color [options]

Identifiers are case-sensitive and become source-map keys. An unknown, duplicated, or kind-incompatible option is a compile error carrying a one-based line number.

orientation accepts exactly right, down, left, or up, and is rejected outright for a node no rotation can mean — a junction has no facing. What rotates is geometry: the body, its ports, their outward normals, the collision rectangle, and the routing corridors, all together. What never rotates is the vocabulary. A vertical transistor is still wired through gate, drain, and source, so rotating a symbol never rewrites the connections around it.

source:VBUS "V_{bus}" at (390, 90) #blue [type=voltage-dc orientation=down]
transistor:QH "high side" at (390, 240) #amber [type=nmos orientation=down]
junction:SW "SW" at (390, 350) #cyan
transistor:QL "low side" at (390, 460) #purple [type=nmos orientation=down]
port:PHASE "phase" at (680, 350) #emerald

VBUS.positive -> QH.drain #blue [line]
QH.source -> SW.node #amber [line]
SW.node -> QL.drain #purple [line]
SW.node -> PHASE.in #emerald [line marker-end=arrow]
compiled by @schemd/core → shown in the rail

02 / WiresConnect semantic ports and signal domains

A connection is two terminals and an arrow:

A.port -> B.port #color [options]

Routes are line, bezier, or ortho. Signal domains are electrical, digital, quantum, and classical, and a width mismatch is rejected before layout rather than drawn as a plausible lie.

net=NAME gives a group of segments one topology identity. Segments that share an exact component.port join implicitly — every branch off one junction.node is therefore one net — and a name can additionally join segments that never touch. Every segment in a net must agree on domain and width; conflicting names at a shared terminal are errors. Unnamed topologies still get deterministic, source-ordered $1, $2, … identities in the AST, the source map, and full-mode SVG (data-net-id).

Geometry is contract-checked. Bodies may touch at an edge but never overlap, and every route family is collision-checked against bodies, labels, and earlier wire channels. Two separate nets may cross only as a strict perpendicular orthogonal crossing, and the later trace receives a bridge; two segments of the same net stay continuous with no bridge, because they are one conductor. Collinear overlap between separate nets, endpoint contact, diagonal crossings, and bridge clusters too dense to render are all compile errors with source lines.

A reversal bus is the case that exercises all of it at once — every wire must cross every other:

port:A0 "A0" at (90, 90) #blue [width=1]
port:A1 "A1" at (90, 200) #cyan [width=1]
port:A2 "A2" at (90, 310) #amber [width=1]
port:A3 "A3" at (90, 420) #purple [width=1]
port:B0 "B0" at (790, 420) #blue [width=1]
port:B1 "B1" at (790, 310) #cyan [width=1]
port:B2 "B2" at (790, 200) #amber [width=1]
port:B3 "B3" at (790, 90) #purple [width=1]

A0.out -> B0.in #blue [ortho digital net=BIT0]
A1.out -> B1.in #cyan [ortho digital net=BIT1]
A2.out -> B2.in #amber [ortho digital net=BIT2]
A3.out -> B3.in #purple [ortho digital net=BIT3]
compiled by @schemd/core → shown in the rail

Before 0.4 this failed from the third wire on: the router scored reuse of an occupied channel as expensive-but-legal while the validator rejected it outright, so the router could return a route it had already proved would be thrown away. Now a contact the validator rejects costs the router infinity through the same predicate, every trace reserves its terminal approach before any wire is placed, and a blocked channel offers a lane a pitch to either side.

03 / ValidationUse family options, never untyped attributes

Options are validated by name against the kind we named. The common ones are type, orientation, inputs, outputs, width, controls, targets, wires, parameter, phase, matrix, operator, and control; availability per kind lives in the component API. There is no untyped attribute bag, and that is the point — a typo is a compile error, never a silently wrong drawing.

ic is the clearest case. Its quoted left, right, top, and bottom lists each become case-sensitive ports, and the body grows to fit the longest side, so we never hand-size the box:

port:SDA "SDA" at (80, 190) #blue
port:SCL "SCL" at (80, 290) #amber
ic:U1 "ADC, 24-bit" at (460, 240) #cyan [left="SDA,SCL,DRDY" right="AIN0,AIN1" top="AVDD" bottom="AGND"]
port:AIN "sensor" at (860, 200) #emerald
ground:GND "AGND" at (460, 420) #slate

SDA.out -> U1.SDA #blue [ortho]
SCL.out -> U1.SCL #amber [ortho]
U1.AIN0 -> AIN.in #emerald [ortho marker-end=arrow]
U1.AGND -> GND.in #slate [ortho]
compiled by @schemd/core → shown in the rail

Note that we wired U1.SDA by its real pin name. in and out still fall back to the first suitable input and output side when we genuinely do not care which — but on a package with named pins, we usually do.