docs: Campaign LA — pinned launch-contract schema COMMITTED into plan LA1

The LA3 Opus review process note was right: the contract both sides
implement lived only in orchestrator prompts, which is exactly the drift
mode the pin exists to prevent (and it produced the paths-key CRITICAL).
The schema, field rules, probe-mode discriminator, and status vocabulary
are now a binding plan section; amendments change this text first,
implementations second. Ledger: LA3 fix round dispatched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-14 16:04:32 +02:00
parent 0bcc7ba3a3
commit db9ad53c1c
38 changed files with 2397 additions and 40 deletions

View file

@ -36,6 +36,9 @@ public sealed class GameWindow :
/ (double)System.Diagnostics.Stopwatch.Frequency;
private readonly AcDream.App.RuntimeOptions _options;
// Campaign LA slice LA1: no-op instance when --session-config didn't
// configure a statusFile (or the env-var launch path was used at all).
private readonly SessionStatusWriter _statusWriter;
private readonly AnimationPresentationDiagnostics _animationDiagnostics;
private readonly string _datDir;
private readonly WorldGameState _worldGameState;
@ -615,6 +618,7 @@ public sealed class GameWindow :
GraphicalHostPlatformServices platformServices)
{
_options = options ?? throw new System.ArgumentNullException(nameof(options));
_statusWriter = new SessionStatusWriter(options.StatusFilePath);
_platformServices = platformServices
?? throw new ArgumentNullException(nameof(platformServices));
_applicationPaths = _platformServices.Paths;
@ -1489,7 +1493,8 @@ public sealed class GameWindow :
_combatAttackOperations,
_combatFeedback,
_portalTunnelFallback,
Console.WriteLine),
Console.WriteLine,
_statusWriter),
this).Compose(
hostInputCamera,
contentEffectsAudio,
@ -1548,7 +1553,10 @@ public sealed class GameWindow :
livePresentation,
sessionPlayer),
frameRoots => new SessionStartCompositionPhase(
new SessionStartDependencies(Console.WriteLine))
new SessionStartDependencies(
Console.WriteLine,
_statusWriter,
_options.SessionId ?? "app"))
.Start(frameRoots));
}
@ -1636,13 +1644,30 @@ public sealed class GameWindow :
private void CompleteShutdown(bool releaseNativeWindow)
{
if (!_lifetime.HasShutdownRoots)
{
// Campaign LA slice LA1: capture BEFORE the shutdown roots run —
// by the time teardown completes, IsInWorld is always false
// regardless of whether a real session was ever connected.
// OnClosing() and Dispose() both funnel through this method;
// HasShutdownRoots's own guard means this fires exactly once,
// from whichever of the two reaches it first.
if (_runtime.Session.IsInWorld)
_statusWriter.Disconnected(_options.SessionId ?? "app", "stopped");
_lifetime.PublishShutdownRoots(CaptureShutdownRoots());
}
GameWindowLifetimeReport report = releaseNativeWindow
? _lifetime.CompleteAndReleaseNativeWindow()
: _lifetime.TryComplete();
if (report.Status == GameWindowLifetimeStatus.Complete)
{
// "exited" = terminal — only the true Dispose() call (not the
// OnClosing() native-window-close-request pass) represents the
// process actually being done.
if (releaseNativeWindow)
_statusWriter.Exited(_options.SessionId ?? "app", 0, "disposed");
return;
}
Console.Error.WriteLine(
$"[shutdown] status={report.Status}, blocked={report.BlockedStage ?? "none"}");
@ -1655,6 +1680,14 @@ public sealed class GameWindow :
if (report.Error is not null)
Console.Error.WriteLine($"[shutdown] {report.Error}");
if (releaseNativeWindow)
{
_statusWriter.Exited(
_options.SessionId ?? "app",
1,
"shutdown-incomplete");
}
}
private GameWindowShutdownRoots CaptureShutdownRoots() => new(