feat(battlenode): inject SessionBattleEngine into BattleSession in pure shadow (Phase 2 N1 exit)

The engine is constructed per session, seated once from the master seed + both
shuffled decks (F-N-5), and fed each frame via ShadowIngest — all inside a
try/catch in ComputeFrames so a shadow failure can never break live dispatch
(ND1/ND6). Routes still come from the existing handlers: wire output is
byte-for-byte unchanged. FrameDispatchContext gains the Engine ref for N2+.

csproj: PrivateAssets=compile on the engine ref so its global-namespace type
surface (MessagePackSerializer, UserConfig, UserCard, ChallengeConfig, ...) does
not leak transitively into SVSim.EmulatedEntrypoint (which references BattleNode)
and collide with that project's own types; the runtime DLL still flows.

All 238 BattleNode unit tests pass; EmulatedEntrypoint builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-06 15:35:35 -04:00
parent fa86739ac2
commit e982300c6d
3 changed files with 59 additions and 3 deletions

View File

@@ -17,6 +17,12 @@ internal sealed class FrameDispatchContext
internal required string BattleId { get; init; }
internal required BattleSessionState State { get; init; }
/// <summary>The session's shadow engine (design ND2/F-N-6). In Phase-2 N1 it is fed in pure shadow
/// and read by no handler; N2+ handlers source opponent-facing fields from it. Always non-null;
/// <see cref="Engine.SessionBattleEngine.IsReady"/> is false until the engine is set up (and stays
/// false if headless setup is unavailable in the host — the shadow then no-ops).</summary>
internal required Engine.SessionBattleEngine Engine { get; init; }
/// <summary>The opponent is an AI-passive (ack-only) bot: it runs no handshake — no
/// <see cref="IHasHandshakePhase"/> — and receives no relayed frames (the client drives its own
/// AI; the server only acks). This is the participant property that replaces the per-handler