feat(gameserver): server-paced incantation FSM (slice 2a, #36) #75

Merged
marco merged 1 commit from feat/incantation-fsm into main 2026-07-19 08:48:05 +02:00
Owner

The core of the spell system (slice 2a of #36): you speak a spell word by word, the server paces emission, and the words appear overhead — the public tell. A modified client can't rush the sequence (server-driven), so the tell + anti-bot property always hold.

Server-side only for now — the client still casts via the instant Cast path; migrating it to the paced words (input + overhead render) is the next PR.

What lands

  • PowerWord (Xen/Ghy elements, Vur/Kor/Ren forms, Lop cancel) + a QueueWord intent + a PowerWordSpoken broadcast (AoI, not the chat path).
  • SpellSystem gains a per-caster incantation FSM:
    • QueueWord sets the next word (element first, then a form; Lop cancels; a form-first / second-element is rejected with a catalog cue).
    • Step (run each tick) emits a due word (~0.75s pacing via EmitIntervalTicks), charges mana per word (no refund; free for a GM), surfaces it as a SpokenWord, and on a completed element+form resolves the bolt (LOS+range revalidated → fizzle; reuses the shared damage path). No next word within ~2s (QueueTimeoutTicks) resets it.
  • The tick threads SpokenWords + resolutions through TickResult; WorldTick broadcasts PowerWordSpoken in AoI and the bolt/cues like the request path did.
  • All cues via the string catalog (new SystemMessageId: SpeakElementFirst, SpeakFormNext, IncantationCancelled, IncantationFaded) — per the rule.

Verification

just lint → 0 warnings. just test → full suite green (GameServer 153, +8 incantation tests): two-word bolt emits both words then lands; form-first / second-element rejected; Lop cancels; timeout resets; mana charged per word; out-of-mana mid-cast resets; free cast spends nothing.

Definition of Done (delta)

  • A player queues element→form; the server emits them paced, broadcasts each overhead (AoI), and resolves the bolt on completion.
  • Wrong order / no element / timeout / out-of-mana each reset with a catalogued cue.
  • Mana is charged per emitted word (no refund); a GM casts free.
  • Server-authoritative (client sends only QueueWord); whole solution builds, zero warnings, tests green.

Next: 2a-client (word keybinds + overhead tell, remove the instant Cast), then 2b (shield/heal + invalid-combo).

The **core of the spell system** (slice 2a of #36): you speak a spell **word by word**, the server paces emission, and the words appear overhead — the public **tell**. A modified client can't rush the sequence (server-driven), so the tell + anti-bot property always hold. **Server-side only for now** — the client still casts via the instant `Cast` path; migrating it to the paced words (input + overhead render) is the next PR. ## What lands - **`PowerWord`** (Xen/Ghy elements, Vur/Kor/Ren forms, Lop cancel) + a **`QueueWord`** intent + a **`PowerWordSpoken`** broadcast (AoI, not the chat path). - **`SpellSystem`** gains a per-caster **incantation FSM**: - `QueueWord` sets the next word (element first, then a form; Lop cancels; a form-first / second-element is rejected with a catalog cue). - `Step` (run each tick) emits a **due word** (~0.75s pacing via `EmitIntervalTicks`), **charges mana per word** (no refund; free for a GM), surfaces it as a `SpokenWord`, and on a completed **element+form** resolves the bolt (LOS+range revalidated → fizzle; reuses the shared damage path). No next word within ~2s (`QueueTimeoutTicks`) **resets** it. - The tick threads `SpokenWords` + resolutions through `TickResult`; `WorldTick` broadcasts `PowerWordSpoken` in AoI and the bolt/cues like the request path did. - **All cues via the string catalog** (new `SystemMessageId`: SpeakElementFirst, SpeakFormNext, IncantationCancelled, IncantationFaded) — per the rule. ## Verification `just lint` → 0 warnings. `just test` → full suite green (**GameServer 153**, +8 incantation tests): two-word bolt emits both words then lands; form-first / second-element rejected; Lop cancels; timeout resets; mana charged per word; out-of-mana mid-cast resets; free cast spends nothing. ## Definition of Done (delta) - [x] A player queues element→form; the server emits them paced, broadcasts each overhead (AoI), and resolves the bolt on completion. - [x] Wrong order / no element / timeout / out-of-mana each reset with a catalogued cue. - [x] Mana is charged per emitted word (no refund); a GM casts free. - [x] Server-authoritative (client sends only QueueWord); whole solution builds, zero warnings, tests green. Next: **2a-client** (word keybinds + overhead tell, remove the instant Cast), then **2b** (shield/heal + invalid-combo).
feat(gameserver): server-paced incantation FSM (slice 2a, #36)
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m28s
97e71bdcaf
The core of the spell system: you speak a spell word by word, the server paces
emission, and the words appear overhead (the public tell). Server-side only for
now — the client still casts via the instant Cast path; migrating it is next.

- PowerWord (Xen/Ghy elements, Vur/Kor/Ren forms, Lop cancel), a QueueWord
  intent, and a PowerWordSpoken broadcast (AoI, not the chat path).
- SpellSystem: a per-caster incantation FSM. QueueWord sets the next word;
  Step (run each tick) emits a due word (~0.75s pacing), charges mana per word
  (no refund; free for a GM), shows it via SpokenWord, and on a completed
  element+form resolves the bolt (LOS+range revalidated → fizzle; reuses the
  shared damage path). No next word within ~2s resets it; Lop cancels.
- Element first, then a form; a form-first / second-element is rejected. All
  cues go through the string catalog (new SystemMessageId values).
- The tick threads SpokenWords + resolutions through TickResult; WorldTick
  broadcasts PowerWordSpoken in AoI and the bolt/cues like the request path.

Shield/heal forms + the invalid-combo cue are slice 2b. Tested: two-word bolt
lands, form-first/second-element rejected, cancel, timeout, mana per word,
out-of-mana reset, free cast.
marco merged commit 4ba8edfd4f into main 2026-07-19 08:48:05 +02:00
marco deleted branch feat/incantation-fsm 2026-07-19 08:48:05 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo!75
No description provided.