From a5a7eb4fb630088ef0f6049e72eafb4b09d39ac9 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 10 Aug 2026 10:01:05 +0200 Subject: [PATCH] =?UTF-8?q?fix(runtime):=20production=20controller=20insta?= =?UTF-8?q?ll=20never=20wired=20OnInterfaceText=20=E2=80=94=20the=20jump-i?= =?UTF-8?q?n-air=20silence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../RuntimeLocalPlayerMovementState.cs | 7 ++++++ .../RuntimeLocalPlayerMovementStateTests.cs | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerMovementState.cs b/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerMovementState.cs index 464f5b9a..0b55e575 100644 --- a/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerMovementState.cs +++ b/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerMovementState.cs @@ -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); } diff --git a/tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerMovementStateTests.cs b/tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerMovementStateTests.cs index f2723e9c..e64009b8 100644 --- a/tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerMovementStateTests.cs +++ b/tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerMovementStateTests.cs @@ -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(); + 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() {