Move the localhost default off BattleNodeOptions and out of Program.cs
into an appsettings.json "BattleNode" section, and make AddBattleNode
throw at startup if the value is missing or blank. Deployments now must
set the URL explicitly, and dev keeps working via the checked-in value.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-BattleId slot keyed dict. Pair returns the first arriver to the
second; ParkAsync awaits a TCS and returns the second arriver. Timeout
defaults to BattleNodeOptions.WaitingRoomTimeout (60s); evict on timeout
keeps the dict clean. Singleton in DI; consumed by the handler in the
next task.
Add a per-project README in SVSim.BattleNode/ that covers:
- Architecture (the six concern folders)
- The connect-handshake sequence verified end-to-end at smoke
- A wire-format-gotchas table for the spec divergences caught during
v1 (headers vs query for credentials, schemeless node URL with
/socket.io/ path, required card_master_id, required resultCode=1,
Matched in response to InitBattle not InitNetwork, EIO3 0x04 prefix
on binary frames, FromJson conditional-expression number-boxing)
- What the v1 scripted opponent does and what is hardcoded
- A "where to extend" table for v2 work
- The full test layout and cross-references to specs/plans
Fill in XML docs on the public surface that previously had none:
- BattleNodeExtensions.AddBattleNode / UseBattleNode (DI + middleware
wiring, including the pipeline-order note that auth runs before
UseWebSockets)
- BattleNodeWebSocketHandler class + HandleAsync (the validation chain)
- BattleSession.ComputeResponses (the lifecycle state machine, with
the NoStock flag's meaning)
- ScriptedLifecycle class (v1 scope, resultCode injection rule,
pointer to the "where to extend" section)
- MatchingBridge class (mint-id + register flow)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>