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

@ -50,6 +50,32 @@ public sealed record LiveSessionStartResult(
LiveSessionCharacterSelection? Selection = null,
Exception? Error = null);
/// <summary>
/// Campaign LA slice LA1: one roster entry as reported by
/// <see cref="CharacterList.Parsed"/> — decoupled from the wire type so the
/// lifecycle-host seam does not leak <c>AcDream.Core.Net.Messages</c> shapes
/// into every consumer.
/// </summary>
public readonly record struct LiveSessionRosterEntry(
uint Id,
string Name,
uint SecondsGreyedOut);
/// <summary>
/// Campaign LA slice LA1: the account's active-character roster, reported to
/// <see cref="ILiveSessionLifecycleHost.ReportRoster"/> right after
/// <c>CharacterList</c> arrives and BEFORE selection — see
/// <c>docs/plans/2026-08-14-launcher-campaign.md</c> LA1 item 2. Hosts forward
/// this to their status stream (<c>characterList</c> event) and, later
/// (LA7/LA8), to the character-select screen. Deleted characters are
/// deliberately excluded — the same candidate set
/// <see cref="CharacterList.TrySelectFirstAvailable"/> already uses.
/// </summary>
public sealed record LiveSessionRosterReport(
string AccountName,
int SlotCount,
IReadOnlyList<LiveSessionRosterEntry> Entries);
/// <summary>
/// Runtime boundary for the domain and presentation sinks attached to one
/// exact <see cref="WorldSession"/> generation. The controller owns the
@ -61,6 +87,10 @@ public interface ILiveSessionLifecycleHost
void ResetSessionState(RuntimeGenerationToken retiringGeneration);
void ReportConnecting(string host, int port, string user);
void ReportConnected();
/// <summary>Campaign LA slice LA1: reported once per successful
/// <c>CharacterList</c> receipt, right before character selection. See
/// <see cref="LiveSessionRosterReport"/>.</summary>
void ReportRoster(LiveSessionRosterReport roster);
void ApplySelectedCharacter(LiveSessionCharacterSelection selection);
void ApplyEnteredWorld(LiveSessionCharacterSelection selection);
void DetachSession(WorldSession session);
@ -610,6 +640,13 @@ public sealed class LiveSessionController
return new LiveSessionStartResult(LiveSessionStartStatus.Deferred);
CharacterList.Parsed? characters = _operations.GetCharacters(session);
if (characters is not null)
{
host.ReportRoster(BuildRosterReport(characters));
if (!IsCurrent(scope, generation))
return new LiveSessionStartResult(LiveSessionStartStatus.Deferred);
}
if (characters is null
|| !TrySelectCharacter(
characters,
@ -838,6 +875,29 @@ public sealed class LiveSessionController
private LiveSessionStartResult ConnectedResult()
=> new(LiveSessionStartStatus.Connected, _activeSelection);
/// <summary>Campaign LA slice LA1: projects the wire-shaped
/// <see cref="CharacterList.Parsed"/> into the decoupled
/// <see cref="LiveSessionRosterReport"/>. Deleted characters are
/// excluded, matching <see cref="CharacterList.TrySelectFirstAvailable"/>'s
/// candidate set.</summary>
private static LiveSessionRosterReport BuildRosterReport(
CharacterList.Parsed characters)
{
var entries = new LiveSessionRosterEntry[characters.Characters.Count];
for (int i = 0; i < entries.Length; i++)
{
CharacterList.Character character = characters.Characters[i];
entries[i] = new LiveSessionRosterEntry(
character.Id,
character.Name,
character.SecondsGreyedOut);
}
return new LiveSessionRosterReport(
characters.AccountName,
characters.SlotCount,
entries);
}
private static bool TrySelectCharacter(
CharacterList.Parsed characters,
LiveSessionCharacterSelector? selector,