Fencer
One combatant in the fence duel. Attaches to a bare entity — it asks `fencer_body` for its rig in `awake`, so it needs no Model or mesh of its own.
Fencer
One combatant in the fence duel. Attaches to a bare entity — it asks
fencer_body for its rig in awake, so it needs no Model or mesh of its own.
Owns footwork along the piste (world X, depth and height held) and the
windup → active → recover state machine behind the four commitments: thrust,
sweep, lunge and parry. Every commitment carries the body over its own
advance, so distance and commitment can be made in one action; the step stops
at the fencer's own minReach rather than carrying it inside its own measure.
Footwork is bounded by the other fencer, not by the property line: a man who has drawn back into his own garden is still reachable, which is the whole reason to hold the measure. Breaking ground is slower than taking it.
A commitment that reached nobody — short of its reach, or crowded inside it, or
a guard that turned nothing aside — enters recovery from a debt. So does one
that was parried. The debt is the commitment's own (duel.whiffOf /
duel.parriedOf), so it scales with what the commitment bought: a thrust turned
aside costs half a second and a lunge turned aside costs the better part of two.
That debt is the opening the other fencer is meant to punish, and
phaseTime < 0 in recover is how a reader tells a real opening from an
ordinary recovery.
The lunge is recovered to the line it was thrown from. It is the one
commitment marked recoil: whatever of its 1.30 m of advance it managed to
cover, it gives back over its own recovery, travelling backwards at the pace it
went out and unable to answer anything while it does. recoilLeft is the ground
still owed. So the lunge buys half a metre of reach nothing else has, and buys
no position at all — which is what stops it being a free way to close the
measure.
A commitment made while riposte is open comes out at RIPOSTE_QUICK of its
wind-up, reaches RIPOSTE_REACH further, cannot be turned aside, and plants
RIPOSTE_POSTS posts instead of one.
The component is input-agnostic except for one case: a role = "player" fencer
on a local entity reads the move axis and the thrust / sweep / lunge /
parry actions itself. A role = "rival" fencer is driven by FencerAI
through the same public methods a human's input reaches.
Fields worth reading from outside: role, action, phase, phaseTime,
riposte, ripostePending, whiffed, frozen, touches, facing.
action is "" while idle, not "guard"; the idle phase is "guard".
Methods: commit(name), walk(dir, dt, line), resetTo(line),
receiveTouch(), beParried(), clash(), openRiposte(), markContact(),
freeze(on), isStriking(), currentReach(), currentMinReach().
Requires the duel and fencer_body modules and @builtin::modules.zinput.
DuelMatch umpires between two of these; it is what calls receiveTouch /
beParried / clash / openRiposte / markContact.
Scoped to this part · feeds back into the world's score.