## Duet build update: architecture alignment pass
Duet (the 2-player, screen-reader-first jam variant) is still its own standalone HTML/JS build —
it doesn't share code with the main C#/.NET/Unity engine, and that's staying true. But I wanted its internal structure to at least follow the same *shape* as the real engine's documented architecture, so anyone moving between the two codebases later isn't relearning a totally different mental model.
**What changed:**
- Replaced the single flat move-handling function with the same Ordered Engine Sequence the main engine's ARCHITECTURE_OVERVIEW.md defines: PlayerIntent → OnValidateIntent → OnApplyMove → OnResolveCapture → OnResolveSystems → OnCheckEndState → Snapshot → UI Refresh. Each stage is its own function now, instead of one function doing everything at once.
- Added an actual Snapshot layer — the UI (board render, audio cue) now only ever reads from a cloned snapshot of state, never touches live game state directly. Matches the engine doc's "consumers should react to snapshots" rule.
- Named the Radius of Ruin / Veiled logic as explicit systems (RadiusOfRuinSystem, VeiledStateSystem) instead of inline loops.
**Bug fix this surfaced:** the original build only set a piece's Veiled status on *first* entry into the Radius of Ruin. If a piece was already Veiled and re-entered the aura, nothing happened
— the timer just kept counting down on its original schedule. The engine doc is explicit that re-entry should *refresh* the duration, not get ignored or stack. Fixed to match spec, and checked it with a small headless test (tick down to 1 turn remaining, re-trigger, confirm it resets to full duration).
**What didn't change:** the rules, the board, the accessibility architecture (ARIA live regions driving your own screen reader), the audio cue, or the fact that this is plain HTML/JS/Web Audio
— no build step, no engine dependency, no GenAI anywhere in it.
Same game. Cleaner internals, and one real correctness fix. Full build's up on the itch page now.