DuelHud
The shell around the fight, and the shape of a session. Sits beside `DuelMatch` on the `DuelDirector` entity.
DuelHud
The shell around the fight, and the shape of a session. Sits beside DuelMatch
on the DuelDirector entity.
The shell is furniture at the edges
The boundary itself is the real health bar — it stands in the world where both fencers can see it — so the shell reads the ceremony rather than covering it. Four screens:
| screen | layer | what it is |
|---|---|---|
hedgerow_assizes | 10 | the ribbon along the top edge: the two scores, the boundary strip with the leading post standing in it, and under it the umpire's call on a caption band |
assize_read | 10 | the read strip along the bottom edge: a chip per fencer naming the commitment as its wind-up starts, the measure gauge between them, and a footer carrying the docket line and the live key hints |
assize_riposte | 11 | the riposte banner, which exists only while a riposte is open, tucked under the caption where the sky is |
assize_card | 12 | the writ that opens a docket and the verdict that closes an assize — anchored over the sky, never over the lawn |
assize_scrim | 8 | a top-down ramp that settles the sky behind the writ. The verdict gets none: the board at the end of a bout is the picture |
Measured at a 1280×720 canvas: the ribbon group is 38–66 pt tall (the caption band is only as tall as the words in it and collapses to nothing when the court is quiet) and the read strip 68 pt, so the shell occupies 5.3–9.2 % of frame height at the top, 9.4 % at the bottom, and 7.9–8.2 % of frame area, with 79–83 % of the frame height carrying nothing at all.
The canvas is the same 1280×720 whatever the framebuffer is — ui.screenSize()
is pinned there while the window is 1920×1080 — so the shell is laid out on a
720p canvas and upscaled uniformly, and every fraction above holds at both
sizes. pinCanvas(960, 600) moves the ribbon to x 210 and the read strip to
y 522, which is the proof that the layout is canvas-relative rather than
measured against numbers somebody typed.
The docket
An assize is one bout. A docket is the court's whole sitting: every neighbour from the one you name down to the last on the roster, heard in order, with the ground won and lost carried forward.
docketFrom— the neighbour the sitting opened at (1–rosterSize)docketHeard/docketWon— assizes concluded, and how many you tookdocketGround— metres of boundary held on the day, summed across assizesdocketRecord— the same thing as a readable string,pettifer:lost:-10:0-9:29s | wickham:won:+10:10-6:74spublic:docket()returns all of it as structured data, one row per assize
The docket advances on the transition out of state == "over", whichever
path takes it there — this card's button, the restart key, or an agent calling
DuelMatch:restart() — so no path has to know the docket exists. The next
neighbour is armed the moment an assize ends, by writing DuelMatch.skill,
.neighbour and .neighbourKey before any restart can read them.
Identity, and what this component owns
DuelMatch owns identity (neighbourKey) and time (matchClock); this
component owns the prose — names, seats, writs, notes and the umpire's whole
vocabulary. Both optional fields are detected by enumerating the component
rather than by touching a name, because reading an absent field logs an engine
error on every frame. When they are absent the HUD falls back to its own roster
index and its own bout clock, so neither component has to land first.
Commitments and key hints are generated from Duel.ACTIONS and the live input
profile (Zin.actions.get), so a stroke added to duel.module or a rebound key
appears on the writ, in the measure gauge and in the hint line with no edit here.
The front door
A player arriving gets the writ: what the fence means, what a touch does to the boundary, the four commitments with their live keys and the triangle between them, and the whole roster as tiles. Naming a tile opens the sitting at that neighbour and hears every one below them in turn, so all of the roster is reachable from a cold arrival.
The opening is gated on this component's own boot flag — a module local,
fresh exactly when the component is — and on nothing that persists. Every
counter that persists has already proved it can reach a value the door never
opens at again: assizes was a replicated field gating the writ on == 0, it
reached 1 the first time anybody opened a docket in this world, and after that a
new player arrived inside a stranger's losing bout with nothing on screen. The
sitting is now held in a module-local table and the fields are written from
it every tick, so the layer's overlay restoring a docket counter five seconds
after a load is undone inside a frame and the fields stay what they are for — a
readable mirror for everyone else.
The board itself is only put back when it is not being fenced. Arriving in the middle of somebody's live bout raises the writ and leaves their fight alone.
The way out
N rises the court from anywhere — mid-bout, on a verdict, on the
court-rises card. It clears the docket, puts the boundary back to nought, names
the first neighbour again and shows the writ. It is what makes the opening
impossible to lock out, so it does not depend on any other component: the scheme
is asked for an action named rise first, and the raw KeyN code is used when
the scheme has none. A rise action added to fencing_controls is picked up
with no edit here — including its touch button, which the raw code cannot
give.
The card's own RISE — A FRESH SITTING button does the same thing with a
pointer.
Clicks are recorded, not obeyed on the spot
Every action the cards offer ends in DuelMatch:restart(), which rebuilds the
garden, the fence and the rival — over a second of work on a loaded machine.
Run straight from the UI callback it was cut off by the script watchdog partway
through, leaving the board at nought-all with state still over while
gui.clickElement reported ok. onCallback records what was asked and the
component's own update serves it. A board that reads over with nobody
named is not a verdict — it is a restart that did not finish — so it carries
no card, goes on nobody's docket, and is asked for again.
Methods
public:beginAssize(index)— open a fresh sitting at that neighbourpublic:callNext()— call the next assize on the open docketpublic:riseCourt()— rise the court and return to the writpublic:chosenNeighbour()—{ index, key, name, skill }of who stands oppositepublic:docket()— the sitting as data, one row per assize concludedpublic:pinCanvas(w, h)— lay the shell out against a pretend canvas
Fields
screen / readScreen / cardScreen / scrimScreen (registered screen names),
neighbour, card ("" | "writ" | "verdict" | "docket"), cardAge,
assizes, refresh, boutClock, and the six docket fields above. None of them
replicate: the sitting belongs to whoever is sitting in it, and every one of
these is a mirror of the module-local copy that actually holds it.
Runs in both edit and play so the shell is visible while authoring.
Scoped to this part · feeds back into the world's score.