|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Cyrano is a Node.js service implementing the **EFP2 (Ethernet Fencing Protocol 2)** — a UDP-based protocol for networked fencing scoring machines. It bridges scoring machines (Masters) with competition management software (Managers), results servers, and display clients. |
| 8 | + |
| 9 | +## Commands |
| 10 | + |
| 11 | +```bash |
| 12 | +npm test # Run all tests (requires --experimental-vm-modules) |
| 13 | +npm test -- tests/path/to/file.test.js # Run a single test file |
| 14 | +npm test -- --testNamePattern="pattern" # Run tests matching a name |
| 15 | +npm run coverage # Run tests with coverage report |
| 16 | +npm run build # Bundle with esbuild → build/ |
| 17 | +npm start # Run built app |
| 18 | +npm run clean # Remove node_modules and reinstall |
| 19 | +``` |
| 20 | + |
| 21 | +## Architecture |
| 22 | + |
| 23 | +**Entry point:** `index.js` — UDP server on port 50100 |
| 24 | + |
| 25 | +**Protocol pipeline:** `cylex.js` (tokenizer) → `cyrano.js` (processor) → `commands/` (handlers) |
| 26 | + |
| 27 | +- `src/protocol/cyranoTokens.js` — Protocol constants (`EFP2`, `|` separator) |
| 28 | +- `src/protocol/cylex.js` — Splits raw messages by `|` into token arrays |
| 29 | +- `src/protocol/cyrano.js` — Validates EFP2 header, dispatches to command handlers |
| 30 | +- `src/commands/index.js` — Auto-loads all command handlers from the `commands/` directory |
| 31 | +- `src/commands/*.js` — Individual command handlers (HELLO, PING, STOP, MSG, DISP) |
| 32 | + |
| 33 | +## EFP2 Protocol Reference |
| 34 | + |
| 35 | +**Message format:** `|EFP2|COMMAND|param1|param2|...|` (ASCII, fields delimited by `|`) |
| 36 | + |
| 37 | +### Network Topology |
| 38 | + |
| 39 | +| Node | IP | Ports | |
| 40 | +|------|----|-------| |
| 41 | +| Scoring machines Server | 172.20.0.1 | 50100 (from Masters), 50103 (from Managers) | |
| 42 | +| Backup Scoring machines Server | 172.20.0.2 | same as primary | |
| 43 | +| Master (piste x) | 172.20.x.1 (x=1..60) | 50100 (from Servers), 50101 (from Additional devices) | |
| 44 | +| Additional devices (piste x) | 172.20.x.y (y=1..255) | any | |
| 45 | +| Manager | 172.20.0.y (y=9..32) | any | |
| 46 | +| Server of the Results | 172.20.0.8 | 50100 (from Servers), 50103 (from Managers), 50104 (from Clients) | |
| 47 | +| Clients | 172.20.129–254.x | any | |
| 48 | + |
| 49 | +### All Commands |
| 50 | + |
| 51 | +| Command | Description | Params | |
| 52 | +|---------|-------------|--------| |
| 53 | +| `HELLO` | Node is online | 0–1 (optional piste code) | |
| 54 | +| `PING` | Check node presence; recipient must reply `\|EFP2\|HELLO\|\|` | 0 | |
| 55 | +| `STOP` | Disconnect / stop receiving | 0 | |
| 56 | +| `DISP` | Set new bout on piste | 18 (see below) | |
| 57 | +| `INFO` | Piste state (score, time, lights, cards, etc.) | ~40 fields | |
| 58 | +| `ACK` | Bout result accepted | 0 | |
| 59 | +| `NAK` | Bout result rejected | 0 | |
| 60 | +| `NEXT` | Referee requests next match | 1 (piste code) | |
| 61 | +| `PREV` | Referee requests previous match | 1 (piste code) | |
| 62 | +| `TEAM` | Team member list for one side | 19 (piste, side, 3 members + reserve, 9 round assignments, unique ID) | |
| 63 | +| `GETTEAM` | Request team list from Server of Results | 2 (piste code, side) | |
| 64 | +| `REPLACE` | Team member substitution | 3 (piste code, side, fencer number 1–3) | |
| 65 | +| `BOUTSTOP` | Cancel current DISP / clear piste | 1 (piste code) | |
| 66 | +| `MSG` | Text message to display | 2 (piste code or `ALL`, message text ≤128 chars) | |
| 67 | +| `STANDBY` | Switch apparatus to sleep mode | 1 (piste code) | |
| 68 | +| `BROKEN` | Lost contact with piste | 1 (piste code) | |
| 69 | +| `DENY` | Deny a request | reason string | |
| 70 | +| `UPDATED` | XML competition data updated | 2 (event ID, competition code) | |
| 71 | + |
| 72 | +### TEAM structure (20 tokens) |
| 73 | +`members` is an array of 4: indices 0–2 are the active fencers, index 3 is the reserve. |
| 74 | +`rounds` is a 9-element array of fencer numbers (1, 2, or 3) indicating who fences each round. |
| 75 | + |
| 76 | +### DISP fields (fields 3–20) |
| 77 | +PisteCode, EventID, CompetitionCode, Phase, Order, BoutID, TimeBegin, Stopwatch, |
| 78 | +Right ID, Right Name, Right Nation, Right MemberID, Right MemberName, |
| 79 | +Left ID, Left Name, Left Nation, Left MemberID, Left MemberName |
| 80 | + |
| 81 | +### Piste codes |
| 82 | +Numeric `1`–`59` for standard pistes; named `BLUE`, `YELLOW`, `GREEN`, `RED`, `FINAL` for final area pistes. |
| 83 | + |
| 84 | +### Competition codes |
| 85 | +`EIM`/`EIW`/`ETM`/`ETW` (épée), `FIM`/`FIW`/`FTM`/`FTW` (foil), `SIM`/`SIW`/`STM`/`STW` (sabre), `MIX` |
| 86 | + |
| 87 | +### Master state machine |
| 88 | +States: `Standalone` → `Indefinite` → `Not active` → `Halt`/`Fencing`/`Pause`/`Waiting`/`Ending` |
| 89 | +- Enters `Not active` only on receiving `DISP` |
| 90 | +- Enters `Ending` on manual bout deactivation; sends `INFO` every second for up to 4s awaiting `ACK`/`NAK` |
| 91 | +- `ACK` → `Not active`; `NAK` or timeout → `Waiting` |
| 92 | + |
| 93 | +## Testing |
| 94 | + |
| 95 | +Tests mirror the `src/` structure under `tests/`. Jest runs with ES module support (`--experimental-vm-modules`). No transpilation is needed for tests — the project uses native ES modules. |
0 commit comments