Skip to content

Commit 69467d3

Browse files
authored
Merge pull request #19 from seigel/claude/magical-hodgkin
Add DEMO.md documenting the bout lifecycle demo
2 parents b1a47e4 + 19138ac commit 69467d3

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

demo/DEMO.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# EFP2 Demo — Bout Lifecycle
2+
3+
Demonstrates a **Server** (competition software) and a **Master** (scoring machine) communicating over real UDP packets on loopback, using the full EFP2 protocol stack built in this repository.
4+
5+
## Run it
6+
7+
```
8+
npm run demo
9+
```
10+
11+
This bundles `demo/run.js` with esbuild and executes it. No external dependencies, no separate processes — both actors run in the same Node.js process on two UDP sockets.
12+
13+
---
14+
15+
## Network topology
16+
17+
```
18+
Server 127.0.0.1:50100 Competition software — manages bouts, accepts results
19+
Master 127.0.0.1:50101 Scoring machine on piste RED
20+
```
21+
22+
This mirrors the real EFP2 topology defined in the spec:
23+
24+
| Node | IP | Port |
25+
|------|----|------|
26+
| Server | 172.20.0.1 | 50100 (from Masters) |
27+
| Master (piste x) | 172.20.x.1 | 50101 (from Additional devices) |
28+
29+
---
30+
31+
## Bout fixture
32+
33+
The demo uses real competition data from the protocol examples:
34+
35+
| Field | Value |
36+
|-------|-------|
37+
| Piste | `RED` |
38+
| Event ID | `24` |
39+
| Competition | `EIM` (Épée Individual Men) |
40+
| Phase | `T32` (Table of 32) |
41+
| Bout ID | `32` |
42+
| Right fencer | IVANOV Sidor — `RUS` — ID `33` |
43+
| Left fencer | LIMON Jua — `FRA` — ID `531` |
44+
| Final score | **5 – 3** (right wins) |
45+
46+
---
47+
48+
## Lifecycle walkthrough
49+
50+
### 1. Handshake
51+
52+
The Master comes online and announces itself with `HELLO`. The Server receives it and responds by assigning a bout with `DISP`.
53+
54+
```
55+
MASTER → HELLO pisteCode=RED
56+
|EFP2|HELLO|RED|
57+
58+
SERVER → DISP RED — IVANOV Sidor (RUS) vs LIMON Jua (FRA)
59+
|EFP2|DISP|RED|24|EIM|T32|1|32|14:45|3:00|33|IVANOV Sidor|RUS|||531|LIMON Jua|FRA|||
60+
```
61+
62+
### 2. Not active
63+
64+
On receiving `DISP` the Master transitions to **Not active** and sends an `INFO` confirming that state, with score 0–0.
65+
66+
```
67+
MASTER → INFO state=Not active score=0–0
68+
|EFP2|INFO|RED|24|EIM|T32|1|1|32|14:45|3:00|A|0|N|0|0|0|0|0|0|
69+
33|IVANOV Sidor|RUS|||0|0|0|0|0|0|0|
70+
531|LIMON Jua|FRA|||0|0|0|0|0|0|0|
71+
```
72+
73+
The `INFO` state field encodes the Master's current state. State codes used in this demo:
74+
75+
| Code | State |
76+
|------|-------|
77+
| `A` | Not active — bout assigned, waiting for referee to start |
78+
| `F` | Fencing — bout in progress |
79+
| `E` | Ending — referee deactivated, awaiting `ACK` or `NAK` |
80+
81+
### 3. Fencing
82+
83+
The referee starts the bout. The Master sends an `INFO` for each touch, advancing the score. The lamp fields (`1`/`0`) indicate which fencer triggered the scoring.
84+
85+
```
86+
MASTER → INFO state=Fencing score=1–0 (right scores)
87+
MASTER → INFO state=Fencing score=1–1 (left scores)
88+
MASTER → INFO state=Fencing score=2–1
89+
MASTER → INFO state=Fencing score=3–1
90+
MASTER → INFO state=Fencing score=3–2
91+
MASTER → INFO state=Fencing score=4–2
92+
MASTER → INFO state=Fencing score=4–3
93+
MASTER → INFO state=Fencing score=5–3 (right wins — final touch)
94+
```
95+
96+
Each wire message carries the full bout context. For example, the final touch:
97+
98+
```
99+
|EFP2|INFO|RED|24|EIM|T32|1|1|32|14:45|3:00|F|0|N|0|0|0|0|0|0|
100+
33|IVANOV Sidor|RUS|||5|0|0|0|0|1|0|
101+
531|LIMON Jua|FRA|||3|0|0|0|0|0|0|
102+
```
103+
104+
### 4. Ending
105+
106+
The referee deactivates the bout. The Master enters the **Ending** state and broadcasts `INFO` once per second for up to 4 seconds, waiting for an `ACK` or `NAK` from the Server.
107+
108+
```
109+
MASTER → INFO state=Ending score=5–3 (1/4)
110+
|EFP2|INFO|RED|24|EIM|T32|1|1|32|14:45|3:00|E|...|5|...|3|...|
111+
```
112+
113+
### 5. ACK — result accepted
114+
115+
The Server receives the `Ending` INFO and sends `ACK` to accept the result. The Master clears its ending timer and transitions back to **Not active**.
116+
117+
```
118+
SERVER → ACK
119+
|EFP2|ACK|
120+
121+
MASTER → INFO state=Not active score=5–3 (bout complete)
122+
```
123+
124+
---
125+
126+
## Timeout path (NAK / no response)
127+
128+
If the Server sends `NAK`, or if no `ACK`/`NAK` arrives within 4 seconds (4 Ending broadcasts), the Master gives up and would transition to **Waiting** state. The demo logs this path:
129+
130+
```
131+
MASTER → INFO state=Ending score=5–3 (4/4)
132+
timeout — no ACK after 4 s — would enter Waiting state
133+
```
134+
135+
To trigger it, comment out the `ACK` send in `demo/run.js` inside the `'E'` state handler.
136+
137+
---
138+
139+
## How it uses the protocol stack
140+
141+
Every message in the demo flows through the same code used in production:
142+
143+
| Direction | Function | File |
144+
|-----------|----------|------|
145+
| Build outgoing wire bytes | `compose(commandObject)` | `src/protocol/cyrano.js` |
146+
| Parse incoming wire bytes | `process(rawMessage)` | `src/protocol/cyrano.js` |
147+
| Tokenise raw string | `tokenize(raw)` | `src/protocol/cylex.js` |
148+
| Per-command serialise | `build(obj)` | `src/commands/*.js` |
149+
| Per-command deserialise | `parse(tokens)` | `src/commands/*.js` |
150+
151+
The demo does not reach into command files directly — it only calls `compose()` and `process()` at the protocol layer, the same API any real integration would use.

0 commit comments

Comments
 (0)