9. The visual layer
Shapes, motion and effects are phrases built on a few natives in
std/look, exactly like behaviour. Looks live in ordinary packages and
run in look context (spec/05): they observe the trace, draw, and
can never change what happens.
9.1 Observers
Taught in 12. Looks
A when on a trace event is an observer. It runs once per occurrence of
the event, on the presentation clock, and sees exported state as it was
at that point of the trace: the state after that fact (spec/05). A
timer (after) sees the state as of the last fact applied before its
presentation time.
when (sim.Crashed n)
look.Fade (card of n) to 0.3
9.2 Drawing
Taught in 12. Looks
drawing = "draw" arg "as" word [ "for" arg ] [ block ] ;
attachment = word "of" arg ; (* look context: a shape attached with draw … for *)
draw (look.Circle 8) as dotcreates a shape nameddot, a local visible for the rest of the enclosing block. A draw name need not be used (drawing is itself the effect), and drawing the same name again in the same scope rebinds it; that is not shadowing. A new shape starts at the scene centre (look.Center) until it is placed.for vattaches the shape tov, a node or a text or number key, under that name, for the whole run: any observer can then usecard of nortag of "user:1".Replacement. Drawing a name that is already attached to
vreplaces the old shape. The new shape takes the old one'sx,y,scale,rotationandopacity, andfill,strokeandtextwhere both kinds have them; Place constraints and the layout slot move to the new shape. If the old shape was drawn in the same instant, it is deleted silently and the new one shows at once. Otherwise the old shape fades out over 300ms and is then deleted, and the new one fades in over 300ms. A replacement is not brand-new (the morph rule, §9.3).The optional block is reactive: its statements are re-run whenever any state they read changes, and the renderer morphs from the old look to the new one. This gives the 3Blue1Brown transform feel without writing animation code. The read-set is replaced on every run. It holds the pairs (node, field), (node, up) and node membership, and
sim.StateRows nreads every exported field ofn. Instances re-run in creation order, after the observers of the fact. Shapes drawn withoutforduring a reactive run belong to the instance and are deleted before it re-runs.Deletion. A deleted shape stays attached as a tombstone: every look phrase on it does nothing, so a late
card of nnever fails because a node left. Deleting a group deletes its members. The only deletion is the unexportedlook.delete;look.Removeis the public spelling (decision 64).Groups.
look.Group {a, b}makes a shape whose members'xandyare relative to the group. The group starts at its first member's position. A shape belongs to at most one group (a second isbad-argument).FillandStrokeon a group apply to its non-label members; opacity and position compose. Group members take no part in layout.Place is a constraint.
look.Place s above|below|beside tgivessan offset fromtandsfollows everyxandychange oftwith the same times, untilsitself is moved (Tweenofxory,Orbit,Flightor a newPlace), which replaces or breaks the constraint (decision 69). A cycle isbad-argument. Sizes: circle2rby2r; rectwbyh; label8per character by18; group the bounding box of its members; line andlook.Centerzero; gap 8.above:dy = -(ht/2 + 8 + hs/2);below:dy = ht/2 + 8 + hs/2;beside:dx = wt/2 + 8 + ws/2.Pinning.
Tweenofxory,Place(as the moved shape),Orbit,FlightorTravelon a node card takes it out of the layout for good (§9.4).Defaults. The scene is 1600 by 1000, origin top-left,
ydown. A new shape sits at the centre(800, 500)with opacity 1, scale 1, rotation 0. A circle and a rect have no fill and no stroke; a rect has corner radius 12; a label is filledtheme.Ink; a line is strokedtheme.Lineand its ends follow the shapes it joins.card of nis the attachment form (look context only; elsewhere acontexterror). The word is a literal, not a name, so a shape local namedcarddoes not interfere, and a phrase headcardin the current package takes the sentence instead (spec/03§3.3). It reads the shape attached tonundercard; a missing attachment is the run-time errormissing-key. It is not a field: attachments belong to the scene, and their keys may also be text or numbers (tag of "user:1").
when (sim.Added [n: raft.Node])
draw (look.Card for n) as card for n
look.Fill card with (RoleColor n.raft.Role)
The typed binding [n: raft.Node] (spec/05 §5.6) gives n the fields
of raft.
9.3 Motion
look.Tween [p: text] of [s: shape] to [v: value] over [d: time]is the only motion primitive. Properties:"x","y","scale","opacity","rotation"(degrees),"fill","stroke","text". Values: for"x"and"y"a number or a shape (that shape's world position when the tween starts; looks never read coordinates, decision 65);"scale"a number at least 0;"opacity"a number in 0..1;"rotation"a number;"fill"and"stroke"a color;"text"text. Any other property or value, or a negative duration, isbad-argument.- One motion curve. Easing is the speed curve of a movement. Tween takes
none: every animation uses the house curve, smootherstep
6t⁵ − 15t⁴ + 10t³, so all packages move alike. - Supersede. A tween on a property cuts the part of a running tween on that property after its start (decision 67). A cut number keeps its interpolated value at that moment, rounded to 0.01; a cut color or text snaps to its target. A tween to the current value changes nothing.
- The morph rule.
Fill,Stroke,Place,Orbit, layout moves and reactive property changes apply at once to a brand-new shape (drawn in this instant, not a replacement); on any other shape they tween over 300ms.Tweenkeeps its own duration. look.Flight [s: shape] from [a: shape] to [b: shape] for [m: msg]movessfromatobon a straight path with the house curve. It starts now and lands at the presentation slot of thenet.Arrivedofm, or stops halfway at the slot of itsnet.Dropped, so no look-ahead is needed (decision 63). Ifmhas already landed,sjumps to the end point; if it never lands (the run stopped),sstops halfway at the scene's last slot.mmust be bound by a net event (net.Id m), elsebad-argument; a laterxorytween onscancels the pending flight.look.Effect "glow" on slightssnow and clears it 600ms later; any other effect name isbad-argument. Glow is the only effect in 0.2; readers draw aneffectvalue they do not know asnone, so effects can be added within scene format 1 (implementation/04-formats.md).look.Orbitputs a shape on the circle of radius 300 around the centre at a fractionfof a turn:x = cx + 300·sin(2πf),y = cy − 300·cos(2πf), and pins it.- Everything else (
look.Fade,look.Pulse,look.Travel,look.Remove) is defined in Synod on top oflook.Tween,after, and the unexportedlook.delete.Tweenof"x"or"y"accepts a shape, which is what letsTravelbe written in Synod. - Numbers the builder computes (interpolations, layout, orbit, offsets) are rounded to 0.01; numbers a look passes in are kept. Sine and cosine are computed by a deterministic routine, never by the platform's.
9.4 Layout
Node cards are placed by layout, never by coordinates in behaviour code.
look.Layout "ring" (also "row", "grid"; any other kind is
bad-argument) arranges every shape attached to a node under the name
card, for nodes not removed, whose card is not deleted or pinned, in node
id order. With N members and index i from 0:
ring: fractioni/N, radius 300, 0 at 12 o'clock, clockwise (the Orbit formula).row:x = 800 + (2i − (N−1))·80,y = 500.grid:cis the smallest integer withc² ≥ N,r = ceil(N/c),x = 800 + (2·col − (c−1))·100,y = 500 + (2·row − (r−1))·80.
After each activation in which membership or the layout kind changed the
layout re-flows: new cards are placed at once, the others morph (§9.3). Other
shapes are positioned relative to cards (look.Place … above, below,
beside, §9.2). A card that is pinned (§9.2) stays out of the layout.
9.5 Colors and type
- Colors are named by zero-hole phrases. A color literal is allowed only
as the
resultof such a phrase:define Accent result #F4A340 - A color's name is the phrase that returns it. A
std/themephrase keeps its bare name (Accent); any other is<qualifier>.<Name>with the qualifier as in the trace (raftlook.Hot). The scene lists colors by name and hex (implementation/04-formats.md). std/themenames the standard palette:theme.Ink,theme.Paper,theme.Line,theme.Quiet,theme.Accent,theme.Good,theme.Warning,theme.Critical. The default theme is the Synod brand (navy ground, pale nodes, amber accent; seeassets/icon.svg).- Type. In Synod 0.2 a label is drawn in the player's sans at 16 px, weight
500, centered; looks cannot choose size, weight or the mono face. Choosing
them is planned as additive label props (
font,size,weight) in format 1 (decision 72).
9.6 The presentation clock
The scene builder maps trace order onto screen time. Presentation times are
whole milliseconds. Pace is an integer in thousandths, 250 to 4000
(1000 is 1×); scale(ms) = ms·1000/pace, rounded down. Every look duration
(after, Tween, and the builder's constants below) is scaled by pace
(decision 66); an after duration in microseconds becomes milliseconds by
rounding down. Normative rules:
- Order is preserved. If fact A precedes fact B in the trace, A's animation starts no later than B's.
- Flights are readable. A message flight (from
net.Senttonet.Arrivedornet.Dropped) lasts at leastscale(400ms)on screen; later facts shift to make room. - Idle gaps are compressed. The simulated gap between two facts shows as
gap × 8milliseconds (the gap in simulated microseconds times 8 over 1000, rounded down), but at mostscale(600ms). - Tweens finish. A fact waits until the shapes it touches are free of
tweens and morphs already running on them (effects and pending flights do
not count). The shapes it touches are the live shapes attached under any
name to any node of the fact (
node,from,to, and both lists ofnet.Partitioned), plus the owner shapes of reactive blocks whose read-set meets the fact's changes. A group counts its members. - Pace. A global pace factor (0.25× to 4×) scales everything.
- Proportional mode (a player toggle,
--mode proportional) instead maps screen time linearly to simulated time, for lessons where timing is the point: tail latency, racing timeouts, clock skew. The slot of a fact ist · 30000 / End,Endbeing the time of the last fact (0 ifEndis 0), then scaled by pace and never earlier than the previous slot; equal slots are allowed. Only rules 1 and 5 apply.
In ordered mode the first fact starts at 0; each later fact starts at least
scale(30ms) after the previous one. The slot is the largest of: the
previous slot plus the compressed gap, the end of the busy shapes of rule 4,
the send slot plus scale(400ms) for an arrival or drop, and the previous
slot plus scale(30ms).
An after inside an observer counts presentation time from the moment its
observer starts. Timers interleave with facts by presentation time: a timer
due no later than the next fact's slot runs first, seeing the state as of the
last fact applied before it. After the last fact, timers due within
scale(2s) of the last slot run; later ones are dropped. More than 100000
timer firings at one instant is run-limit. The scene's duration is the
latest of the last slot, the last key and the last marker.
9.7 The default look
std/look/basic is used when a program uses no look. A program uses a
look when std/look is in its package graph (spec/06 §6.6). Otherwise
the observers of std/look/basic run before the program's own. Nodes are
cards on a ring labelled with the node's name (sim.Name n), each card lists the node's exported
state fields as name value rows that morph on sim.Changed, messages
are dots that fly between cards and are labelled with their record type,
dropped messages fade midway, crashed nodes grey out, sim.Broken
flashes the whole scene border in theme.Critical, and lesson.Noted
shows a caption. Look packages may use std/look/basic and add to it.