fix(headless,runtime): OP7 review fixes + docs: OP3 re-review REOPEN (narrow)

TWO work products share this commit (a staged-index collision between the
coordinator's docs commit and the OP7 fixer's staged files — content
verified complete and coherent; only this message was wrong before the
amend):

1. OP7 review fixes (all nine findings from
   docs/research/2026-08-11-op7-review.md):
   - M1: HeadlessSessionDescriptor is a record; WithAccount uses 'with' non-destructive record copy,
     so a future property cannot be silently dropped; direct-CLI
     regression test proves CharacterOptions survives --user/--password.
   - M2 root fix: LiveSessionEventRouter skips BOTH Replace and the
     options notification on a trailer-truncated PlayerDescription — a
     truncated re-seed can no longer install zeroed words under an armed
     latch for OP7's automation to flush into 0x01A1.
   - SF1: schema keys validate as ordinal strings against the allowed
     names (numeric / comma-combined aliases rejected). SF2: both-true
     fellowship exclusion rejected at load, naming both keys. SF3: the
     onLoginCompleteSent observer moved after transit.EndTeleport().
     SF4: production-hook coverage for all three LoginComplete sites.
     SF5: test-script OP7 wire expectation corrected (batched ids ride
     only the 0x01A1).

2. docs/research/2026-08-11-op3-rereview.md — OP3 re-review verdict
   REOPEN (narrow): M1 byte-decode independently re-verified (6a 07 at
   all six sites); residuals R1 (gate script promises a timestamp prefix
   acdream doesn't render), R2 (null-controller player-mode still
   refuses), R3 (dormancy pin lacks stimulus) — coordinator fixes follow.

Full Release suite at this tree: 12,956 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 03:19:35 +02:00
parent 386076af0f
commit 7b60e71b85
11 changed files with 1007 additions and 23 deletions

View file

@ -194,6 +194,105 @@ public sealed class RuntimeLiveEntitySessionControllerTests
Assert.Equal(0x01020001u, runtime.Portal.Snapshot.DestinationCell);
}
[Fact]
public void DirectSinkContentLessSpawn_InvokesOnLoginCompleteSentHookExactlyOnce()
{
// SF-4 (Campaign OP OP7 review fix, 2026-08-11): the pre-existing
// wiring tests drove the login-complete half only through
// HeadlessSessionHost.OptionsSeeder's TEST seam, never through this
// controller's own production onLoginCompleteSent call sites — a
// future edit that dropped or misdirected the OnSpawned
// content-less immediate-admission invoke (:199-201) would have
// been invisible to the whole suite. Drives it with a counting
// Action, matching the fix's "the seeder's TryRun latch sees the
// LoginComplete half via the production path" requirement.
using StartedRuntime started = StartRuntime();
GameRuntime runtime = started.Runtime;
const uint playerGuid = 0x50000004u;
runtime.PlayerIdentity.ServerGuid = playerGuid;
using var session = new WorldSession(
new IPEndPoint(IPAddress.Loopback, 9000),
new FixtureTransport());
session.GameActionCapture = _ => { };
int hookInvocations = 0;
var controller = new RuntimeLiveEntitySessionController(
runtime,
session,
onLoginCompleteSent: () => hookInvocations++);
LiveEntitySessionSink sink = controller.CreateSink();
WorldSession.EntitySpawn spawn =
Spawn(playerGuid, incarnation: 1);
sink.Spawned(spawn);
Assert.Equal(1, hookInvocations);
// A repeat Create at the same incarnation must not re-fire the
// hook — mirrors the pre-existing gameActions.Single() assertion
// for the underlying LoginComplete send itself.
sink.Spawned(spawn);
Assert.Equal(1, hookInvocations);
}
[Fact]
public void DirectSinkPortalCompletion_InvokesOnLoginCompleteSentHookAfterTeleportEnds()
{
// SF-4 companion: covers TryAdvancePortalCompletion's own
// onLoginCompleteSent invoke (the third production hook site lives
// one level up in HeadlessSessionHost.CreateEventRoute's direct
// first-entry callback, exercised at the host level). This also
// doubles as the SF-3 ordering regression test: the hook body reads
// TransitOwner state from INSIDE the callback, so if the invoke
// ever regressed back to firing between the LoginComplete send and
// transit.EndTeleport(), IsSessionIdle would observe false here.
using StartedRuntime started = StartRuntime();
GameRuntime runtime = started.Runtime;
const uint playerGuid = 0x50000005u;
runtime.PlayerIdentity.ServerGuid = playerGuid;
using var session = new WorldSession(
new IPEndPoint(IPAddress.Loopback, 9000),
new FixtureTransport());
session.GameActionCapture = _ => { };
int hookInvocations = 0;
bool? sessionIdleAtSecondHookInvocation = null;
var controller = new RuntimeLiveEntitySessionController(
runtime,
session,
onLoginCompleteSent: () =>
{
hookInvocations++;
if (hookInvocations == 2)
{
sessionIdleAtSecondHookInvocation =
runtime.TransitOwner.CaptureOwnership().IsSessionIdle;
}
});
LiveEntitySessionSink sink = controller.CreateSink();
WorldSession.EntitySpawn spawn =
Spawn(playerGuid, incarnation: 1);
sink.Spawned(spawn);
Assert.Equal(1, hookInvocations);
sink.TeleportStarted(1u);
sink.PositionUpdated(new WorldSession.EntityPositionUpdate(
playerGuid,
spawn.Position!.Value with
{
LandblockId = 0x01020001u,
PositionX = 30f,
},
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: 2,
TeleportSequence: 1,
ForcePositionSequence: 0));
Assert.Equal(2, hookInvocations);
Assert.True(runtime.Portal.Snapshot.Completed);
Assert.True(sessionIdleAtSecondHookInvocation);
}
[Fact]
public void DirectSinkProjectsAcceptedLocalWorldStateThroughOneHostSeam()
{