diff --git a/docs/research/2026-08-11-campaign-op-test-script.md b/docs/research/2026-08-11-campaign-op-test-script.md index 902848a2..a5a48c9d 100644 --- a/docs/research/2026-08-11-campaign-op-test-script.md +++ b/docs/research/2026-08-11-campaign-op-test-script.md @@ -108,11 +108,14 @@ for this slice, not a bug. session sees zero lines (everything already at the macro's target). **These six lines are byte-verified `RetailLogTextType.Magic` (0x07), NOT the client-local refusal type** — expect LIGHT BLUE text in the - SCROLLING CHAT TRANSCRIPT (not the transient SpewBox overlay), with - a timestamp prefix, same as any other Magic-colored system line. If - you see bright red text in a transient 5-second overlay instead (the - ClientLocal/SpewBox look used by the mid-air refusal and UA/RA - above), that is the pre-fix-round bug — report it. + SCROLLING CHAT TRANSCRIPT (not the transient SpewBox overlay). Do + NOT expect a timestamp prefix: acdream renders no chat timestamps + yet (the Display Timestamps consumer is OP4 scope, and there is no + chat-log file — TS-69), so a bare light-blue line is CORRECT here + (OP3 re-review R1). If you see bright red text in a transient + 5-second overlay instead (the ClientLocal/SpewBox look used by the + mid-air refusal and UA/RA above), that is the pre-fix-round bug — + report it. **Known, registered gap (TS-74): acdream has no persistent "turn to face camera" mouse-turning MODE yet, so this button changes stored preferences and sends the wire bit, but you will not see the diff --git a/src/AcDream.App/Composition/InteractionRetainedUiComposition.cs b/src/AcDream.App/Composition/InteractionRetainedUiComposition.cs index 53e77251..e97f72e9 100644 --- a/src/AcDream.App/Composition/InteractionRetainedUiComposition.cs +++ b/src/AcDream.App/Composition/InteractionRetainedUiComposition.cs @@ -811,9 +811,15 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory // into "not grounded", which fired the mid-air message // outside player mode — the opposite of retail and the // opposite of what the old comment here claimed. - IsGrounded: () => !d.PlayerMode.IsPlayerMode - ? (bool?)null - : d.PlayerController.Controller is { IsAirborne: false }, + // R2 (OP3 re-review, 2026-08-11): a NULL controller in + // player mode must ALSO yield null (silent) — the prior + // `is { IsAirborne: false }` pattern returned false for + // null and fired the refusal retail cannot produce. + IsGrounded: () => + d.PlayerMode.IsPlayerMode + && d.PlayerController.Controller is { } liveController + ? !liveController.IsAirborne + : (bool?)null, IsUseMouseTurningEnabled: () => CharacterOptionTable.TryGet( CharacterOptionId.UseMouseTurning, diff --git a/tests/AcDream.App.Tests/UI/Layout/OP2ReworkBlastRadiusConformanceTests.cs b/tests/AcDream.App.Tests/UI/Layout/OP2ReworkBlastRadiusConformanceTests.cs index 35ee0b73..bcb3e98a 100644 --- a/tests/AcDream.App.Tests/UI/Layout/OP2ReworkBlastRadiusConformanceTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/OP2ReworkBlastRadiusConformanceTests.cs @@ -89,6 +89,22 @@ public class OP2ReworkBlastRadiusConformanceTests host.ActivePageChanged += (_, _) => raised++; Assert.False(host.BehaviorActive); + + // R3 (OP3 re-review, 2026-08-11): a subscribe-then-assert-zero pin + // passes even with dormancy fully broken. The REAL dormancy + // mechanism is that ActivateTabBehavior (via RetailTabBinding. + // SetClick) was never called, so the authored tab BUTTONS carry no + // click handler — clicking a dormant host's tab can invoke nothing. + // SwitchTo itself deliberately has no dormancy guard (a controller + // may drive it directly), so the button wiring IS the contract. + foreach (UiTabTableEntry tab in host.Tabs) + { + var button = layout.FindElement(tab.ButtonElementId); + Assert.NotNull(button); + Assert.False(button!.HandlesClick, + $"dormant tab button 0x{tab.ButtonElementId:X8} has a click " + + "handler — import-time activation regressed (AD-73)"); + } Assert.Equal(0, raised); }