fix(runtime): production controller install never wired OnInterfaceText — the jump-in-air silence

Round-3 probe evidence pinpointed it: '[jump] ReportJumpRefusal
result=NotGrounded hasCallback=False' — the edge detection, OnWalkable
clearing, and refusal dispatch all worked; the callback was null because
CommitRuntimeOwnedController (the C3c production publication path) writes
_controller directly and never applied _onInterfaceText the way the
internal setter does. Two install paths, one wired. Regression test pins
the commit path.

Runtime tests 1,323/0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-10 10:01:05 +02:00
parent 1fd515436c
commit a5a7eb4fb6
2 changed files with 29 additions and 0 deletions

View file

@ -449,6 +449,13 @@ public sealed class RuntimeLocalPlayerMovementState
{
_controller?.RetireRuntimePublication();
_controller = controller;
// The PRODUCTION install path must carry the same wiring the
// internal Controller setter applies: this line was missing, so
// every live graphical controller ran with a null OnInterfaceText
// and client-local refusals (jump-in-air) silently vanished —
// proven by the round-3 probe line
// "[jump] ReportJumpRefusal result=NotGrounded hasCallback=False".
controller.OnInterfaceText = _onInterfaceText;
ControllerOwnershipEpoch++;
Interlocked.Increment(ref _revision);
}

View file

@ -172,6 +172,28 @@ public sealed class RuntimeLocalPlayerMovementStateTests
Assert.Equal(AcDream.Core.Chat.RetailLogTextType.ClientLocal, received[0].Type);
}
[Fact]
public void OnInterfaceText_CommitRuntimeOwnedController_CarriesTheCallback()
{
// The PRODUCTION install path (CommitRuntimeOwnedController via the
// publication lifecycle) bypasses the internal Controller setter.
// It shipped WITHOUT the OnInterfaceText application, so every live
// graphical controller ran with a null callback and client-local
// refusals silently vanished — round-3 probe evidence:
// "[jump] ReportJumpRefusal result=NotGrounded hasCallback=False".
using var movement = new RuntimeLocalPlayerMovementState();
var received = new List<string>();
movement.OnInterfaceText = (text, _) => received.Add(text);
var controller = new PlayerMovementController(new PhysicsEngine());
movement.CommitRuntimeOwnedController(controller);
Assert.NotNull(controller.OnInterfaceText);
controller.OnInterfaceText!(
"commit-path line", AcDream.Core.Chat.RetailLogTextType.ClientLocal);
Assert.Equal(["commit-path line"], received);
}
[Fact]
public void OnInterfaceText_SetAfterControllerInstall_StillAppliesToTheCurrentController()
{