fix #434: delete the unreachable DebugPanel/DebugVM surface and the comments that advertised it
DebugPanel and DebugVM have not been constructible since Campaign V slice
V11 removed the ImGui frontend that hosted them: nothing in src/ ever
called their constructors, only a test did. Two consequences, both fixed
here — 35 environment reads inside them were unreachable, and roughly forty
XML doc comments across the diagnostics owners promised a runtime checkbox
that no longer exists. A flag documented as runtime-toggleable when it is
startup-only sends the next investigation down a path that cannot work.
Deleted DebugPanel.cs (340 lines), DebugVM.cs (548) and DebugVMTests.cs
(327). Corrected the surviving claims in PhysicsDiagnostics,
RenderingDiagnostics, CameraDiagnostics, PhysicsEngine and GameWindow to say
what is actually true: these flags are set from the environment at startup
or by direct assignment.
The one real dependant was CombatFeedbackSlot, whose binding target was
DebugVM. It now takes a plain Action<string>, which removes the dependency
without changing behavior — and makes visible that there is no behavior:
nothing binds the slot, so the combat refusals it carries ("No monster
target", "Enter melee or missile combat first") have been discarded all
along. Filed as #436 and pinned by a test, rather than papered over with an
invented chat message; the retail text and channel need the oracle first.
Deliberately untouched: F1's AcdreamToggleDebugPanel binding, which
GameplayInputCommandController consumes as a documented no-op so the key
does not fall through to a lower input scope; and the
DebugVmRenderFactsPublisher / DevToolsRuntimeSources chain, which is still
wired into production composition and deserves its own dead-code pass
instead of being pulled into this one.
Full hermetic suite 15,333 passed / 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e77dd7c413
commit
05bfe8d162
11 changed files with 129 additions and 1294 deletions
|
|
@ -1,7 +1,4 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Combat;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.UI.Abstractions.Panels.Debug;
|
||||
|
||||
namespace AcDream.App.Tests.Combat;
|
||||
|
||||
|
|
@ -11,49 +8,41 @@ public sealed class CombatFeedbackSlotTests
|
|||
public void ExpectedOwnerUnbindCannotClearReplacement()
|
||||
{
|
||||
var slot = new CombatFeedbackSlot();
|
||||
using DebugVM first = CreateViewModel();
|
||||
using DebugVM second = CreateViewModel();
|
||||
List<string> first = [];
|
||||
List<string> second = [];
|
||||
Action<string> firstTarget = first.Add;
|
||||
Action<string> secondTarget = second.Add;
|
||||
|
||||
slot.Bind(first);
|
||||
slot.Unbind(second);
|
||||
slot.Bind(firstTarget);
|
||||
slot.Unbind(secondTarget);
|
||||
slot.Show("first");
|
||||
Assert.Single(first.RecentToasts);
|
||||
Assert.Empty(second.RecentToasts);
|
||||
Assert.Equal(["first"], first);
|
||||
Assert.Empty(second);
|
||||
|
||||
slot.Unbind(first);
|
||||
slot.Bind(second);
|
||||
slot.Unbind(firstTarget);
|
||||
slot.Bind(secondTarget);
|
||||
slot.Show("second");
|
||||
Assert.Single(second.RecentToasts);
|
||||
Assert.Equal(["second"], second);
|
||||
}
|
||||
|
||||
private static DebugVM CreateViewModel() => new(
|
||||
static () => Vector3.Zero,
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => false,
|
||||
static () => false,
|
||||
static () => false,
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => float.PositiveInfinity,
|
||||
static () => "-",
|
||||
static () => false,
|
||||
static () => false,
|
||||
static () => 0,
|
||||
static () => 1,
|
||||
static () => 0,
|
||||
static () => false,
|
||||
static () => "0",
|
||||
static () => 0,
|
||||
static () => "Clear",
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => 0,
|
||||
static () => 60,
|
||||
static () => 16.7f,
|
||||
new CombatState());
|
||||
[Fact]
|
||||
public void RebindingADifferentTargetWhileBoundIsRejected()
|
||||
{
|
||||
var slot = new CombatFeedbackSlot();
|
||||
slot.Bind(_ => { });
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => slot.Bind(_ => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnUnboundSlotDropsItsMessages()
|
||||
{
|
||||
// #434/#436: this is the shipped behavior, not an aspiration —
|
||||
// nothing binds the slot in production, so combat refusal text
|
||||
// ("No monster target") is discarded. Pinned so the day it gets a
|
||||
// real binder, this test is the one that has to change.
|
||||
var slot = new CombatFeedbackSlot();
|
||||
|
||||
slot.Show("dropped");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,327 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.UI.Abstractions.Panels.Debug;
|
||||
|
||||
namespace AcDream.UI.Abstractions.Tests.Panels.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the Phase I.2 <see cref="DebugVM"/> — read-through ViewModel
|
||||
/// for the migrated debug panel. Verifies the combat-event subscription
|
||||
/// (replacing the old <c>DebugOverlay.BindCombat</c>), the toast ring
|
||||
/// cap, and that the diagnostic-flag bools round-trip without affecting
|
||||
/// the rest of the VM.
|
||||
/// </summary>
|
||||
public sealed class DebugVMTests
|
||||
{
|
||||
[Fact]
|
||||
public void DisposeDetachesEveryCombatSubscriptionExactlyOnce()
|
||||
{
|
||||
var combat = new CombatState();
|
||||
DebugVM vm = NewVm(combat);
|
||||
combat.OnKillerNotification("first", 1);
|
||||
Assert.Single(vm.CombatEvents);
|
||||
|
||||
vm.Dispose();
|
||||
vm.Dispose();
|
||||
combat.OnKillerNotification("second", 2);
|
||||
combat.OnAttackDone(3, 4);
|
||||
|
||||
Assert.Single(vm.CombatEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a minimal <see cref="DebugVM"/> with safe defaults for every
|
||||
/// constructor source. Tests that don't care about a particular source
|
||||
/// just leave it stubbed out.
|
||||
/// </summary>
|
||||
private static DebugVM NewVm(CombatState? combat = null)
|
||||
{
|
||||
combat ??= new CombatState();
|
||||
return new DebugVM(
|
||||
getPlayerPosition: () => Vector3.Zero,
|
||||
getPlayerHeadingDeg: () => 0f,
|
||||
getPlayerCellId: () => 0u,
|
||||
getPlayerOnGround: () => true,
|
||||
getInPlayerMode: () => false,
|
||||
getInFlyMode: () => false,
|
||||
getVerticalVelocity: () => 0f,
|
||||
getEntityCount: () => 0,
|
||||
getAnimatedCount: () => 0,
|
||||
getLandblocksVisible: () => 0,
|
||||
getLandblocksTotal: () => 0,
|
||||
getShadowObjectCount: () => 0,
|
||||
getNearestObjDist: () => float.PositiveInfinity,
|
||||
getNearestObjLabel: () => "-",
|
||||
getColliding: () => false,
|
||||
getDebugWireframes: () => false,
|
||||
getStreamingRadius: () => 2,
|
||||
getMouseSensitivity: () => 1f,
|
||||
getChaseDistance: () => 0f,
|
||||
getRmbOrbit: () => false,
|
||||
getHourName: () => "Dawnsong",
|
||||
getDayFraction: () => 0.25f,
|
||||
getWeather: () => "Clear",
|
||||
getActiveLights: () => 0,
|
||||
getRegisteredLights: () => 0,
|
||||
getParticleCount: () => 0,
|
||||
getFps: () => 60f,
|
||||
getFrameMs: () => 16.7f,
|
||||
combat: combat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullCombat()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new DebugVM(
|
||||
getPlayerPosition: () => Vector3.Zero,
|
||||
getPlayerHeadingDeg: () => 0f,
|
||||
getPlayerCellId: () => 0u,
|
||||
getPlayerOnGround: () => true,
|
||||
getInPlayerMode: () => false,
|
||||
getInFlyMode: () => false,
|
||||
getVerticalVelocity: () => 0f,
|
||||
getEntityCount: () => 0,
|
||||
getAnimatedCount: () => 0,
|
||||
getLandblocksVisible: () => 0,
|
||||
getLandblocksTotal: () => 0,
|
||||
getShadowObjectCount: () => 0,
|
||||
getNearestObjDist: () => 0f,
|
||||
getNearestObjLabel: () => "-",
|
||||
getColliding: () => false,
|
||||
getDebugWireframes: () => false,
|
||||
getStreamingRadius: () => 0,
|
||||
getMouseSensitivity: () => 1f,
|
||||
getChaseDistance: () => 0f,
|
||||
getRmbOrbit: () => false,
|
||||
getHourName: () => "",
|
||||
getDayFraction: () => 0f,
|
||||
getWeather: () => "",
|
||||
getActiveLights: () => 0,
|
||||
getRegisteredLights: () => 0,
|
||||
getParticleCount: () => 0,
|
||||
getFps: () => 0f,
|
||||
getFrameMs: () => 0f,
|
||||
combat: null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadThrough_PullsLiveValuesPerAccess_NoCache()
|
||||
{
|
||||
// The VM does NOT cache; every property read goes through the
|
||||
// backing Func<T>. Mutating an external box between two reads
|
||||
// must surface immediately.
|
||||
int counter = 0;
|
||||
var vm = new DebugVM(
|
||||
getPlayerPosition: () => Vector3.Zero,
|
||||
getPlayerHeadingDeg: () => 0f,
|
||||
getPlayerCellId: () => 0u,
|
||||
getPlayerOnGround: () => true,
|
||||
getInPlayerMode: () => false,
|
||||
getInFlyMode: () => false,
|
||||
getVerticalVelocity: () => 0f,
|
||||
getEntityCount: () => ++counter,
|
||||
getAnimatedCount: () => 0,
|
||||
getLandblocksVisible: () => 0,
|
||||
getLandblocksTotal: () => 0,
|
||||
getShadowObjectCount: () => 0,
|
||||
getNearestObjDist: () => 0f,
|
||||
getNearestObjLabel: () => "-",
|
||||
getColliding: () => false,
|
||||
getDebugWireframes: () => false,
|
||||
getStreamingRadius: () => 0,
|
||||
getMouseSensitivity: () => 1f,
|
||||
getChaseDistance: () => 0f,
|
||||
getRmbOrbit: () => false,
|
||||
getHourName: () => "",
|
||||
getDayFraction: () => 0f,
|
||||
getWeather: () => "",
|
||||
getActiveLights: () => 0,
|
||||
getRegisteredLights: () => 0,
|
||||
getParticleCount: () => 0,
|
||||
getFps: () => 0f,
|
||||
getFrameMs: () => 0f,
|
||||
combat: new CombatState());
|
||||
|
||||
Assert.Equal(1, vm.EntityCount);
|
||||
Assert.Equal(2, vm.EntityCount);
|
||||
Assert.Equal(3, vm.EntityCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageTaken_AppendsErrorEvent()
|
||||
{
|
||||
var combat = new CombatState();
|
||||
var vm = NewVm(combat);
|
||||
|
||||
combat.OnVictimNotification(
|
||||
attackerName: "Drudge", attackerGuid: 0x10u,
|
||||
damageType: 0u, damage: 12u, hitQuadrant: 0u,
|
||||
critical: 0u, attackType: 0u);
|
||||
|
||||
var events = vm.CombatEvents.ToList();
|
||||
Assert.Single(events);
|
||||
Assert.Equal(CombatEventKind.Error, events[0].Kind);
|
||||
Assert.Contains("Drudge", events[0].Text);
|
||||
Assert.Contains("12", events[0].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageDealt_AppendsInfoEvent()
|
||||
{
|
||||
var combat = new CombatState();
|
||||
var vm = NewVm(combat);
|
||||
|
||||
combat.OnAttackerNotification(
|
||||
defenderName: "Drudge", damageType: 0u,
|
||||
damage: 8u, damagePercent: 0.1f);
|
||||
|
||||
var events = vm.CombatEvents.ToList();
|
||||
Assert.Single(events);
|
||||
Assert.Equal(CombatEventKind.Info, events[0].Kind);
|
||||
Assert.Contains("Drudge", events[0].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EvadedIncoming_AppendsWarnEvent()
|
||||
{
|
||||
var combat = new CombatState();
|
||||
var vm = NewVm(combat);
|
||||
|
||||
combat.OnEvasionDefenderNotification("Mosswart");
|
||||
|
||||
var events = vm.CombatEvents.ToList();
|
||||
Assert.Single(events);
|
||||
Assert.Equal(CombatEventKind.Warn, events[0].Kind);
|
||||
Assert.Contains("Mosswart", events[0].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CombatEventRing_CapsAtMax_DropsOldest()
|
||||
{
|
||||
var combat = new CombatState();
|
||||
var vm = NewVm(combat);
|
||||
|
||||
// Pump well past the cap (25). The oldest entries must drop out.
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
combat.OnAttackerNotification(
|
||||
defenderName: $"Foe{i}", damageType: 0u,
|
||||
damage: (uint)i, damagePercent: 0.1f);
|
||||
}
|
||||
|
||||
var events = vm.CombatEvents.ToList();
|
||||
Assert.Equal(DebugVM.MaxCombatEvents, events.Count);
|
||||
// The newest entry must still be present.
|
||||
Assert.Contains(events, e => e.Text.Contains("Foe39"));
|
||||
// The oldest must have dropped.
|
||||
Assert.DoesNotContain(events, e => e.Text.Contains("Foe0,") || e.Text == "Foe0");
|
||||
Assert.DoesNotContain(events, e => e.Text.Contains("Foe5"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Toast_RingCapsAtMaxRecent()
|
||||
{
|
||||
var vm = NewVm();
|
||||
|
||||
for (int i = 0; i < 30; i++)
|
||||
vm.AddToast($"toast-{i}");
|
||||
|
||||
var toasts = vm.RecentToasts.ToList();
|
||||
Assert.Equal(DebugVM.MaxRecentToasts, toasts.Count);
|
||||
Assert.Contains(toasts, t => t.Text == "toast-29");
|
||||
Assert.DoesNotContain(toasts, t => t.Text == "toast-0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Toast_PreservesKind()
|
||||
{
|
||||
var vm = NewVm();
|
||||
vm.AddToast("warning!", ToastKind.Warn);
|
||||
vm.AddToast("error!", ToastKind.Error);
|
||||
|
||||
var toasts = vm.RecentToasts.ToList();
|
||||
Assert.Equal(2, toasts.Count);
|
||||
Assert.Contains(toasts, t => t.Text == "warning!" && t.Kind == ToastKind.Warn);
|
||||
Assert.Contains(toasts, t => t.Text == "error!" && t.Kind == ToastKind.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiagnosticFlags_DefaultFalse_RoundTripIndependently()
|
||||
{
|
||||
var vm = NewVm();
|
||||
Assert.False(vm.DumpMotion);
|
||||
Assert.False(vm.DumpVitals);
|
||||
Assert.False(vm.DumpOpcodes);
|
||||
Assert.False(vm.DumpSky);
|
||||
|
||||
vm.DumpMotion = true;
|
||||
Assert.True(vm.DumpMotion);
|
||||
Assert.False(vm.DumpVitals);
|
||||
Assert.False(vm.DumpOpcodes);
|
||||
Assert.False(vm.DumpSky);
|
||||
|
||||
vm.DumpSky = true;
|
||||
vm.DumpMotion = false;
|
||||
Assert.False(vm.DumpMotion);
|
||||
Assert.True(vm.DumpSky);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToggleFlags_DoNotAffectCombatRing()
|
||||
{
|
||||
var combat = new CombatState();
|
||||
var vm = NewVm(combat);
|
||||
|
||||
combat.OnAttackerNotification("X", 0u, 1u, 0.1f);
|
||||
Assert.Single(vm.CombatEvents);
|
||||
|
||||
vm.DumpMotion = true;
|
||||
vm.DumpVitals = true;
|
||||
Assert.Single(vm.CombatEvents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionHooks_InvokeSuppliedDelegates()
|
||||
{
|
||||
// The panel needs to invoke "cycle time", "cycle weather", "toggle
|
||||
// collision wires" actions when the corresponding Button is
|
||||
// clicked. The VM exposes these as Action; the panel calls them.
|
||||
int timeHits = 0, weatherHits = 0, wireHits = 0;
|
||||
var vm = NewVm();
|
||||
vm.CycleTimeOfDay = () => timeHits++;
|
||||
vm.CycleWeather = () => weatherHits++;
|
||||
vm.ToggleCollisionWires = () => wireHits++;
|
||||
|
||||
vm.CycleTimeOfDay?.Invoke();
|
||||
vm.CycleTimeOfDay?.Invoke();
|
||||
vm.CycleWeather?.Invoke();
|
||||
vm.ToggleCollisionWires?.Invoke();
|
||||
|
||||
Assert.Equal(2, timeHits);
|
||||
Assert.Equal(1, weatherHits);
|
||||
Assert.Equal(1, wireHits);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProbeIndoorBsp_ForwardsToPhysicsDiagnostics()
|
||||
{
|
||||
var originalEnabled = PhysicsDiagnostics.ProbeIndoorBspEnabled;
|
||||
try
|
||||
{
|
||||
var vm = NewVm();
|
||||
|
||||
vm.ProbeIndoorBsp = true;
|
||||
Assert.True(PhysicsDiagnostics.ProbeIndoorBspEnabled);
|
||||
Assert.True(vm.ProbeIndoorBsp);
|
||||
|
||||
vm.ProbeIndoorBsp = false;
|
||||
Assert.False(PhysicsDiagnostics.ProbeIndoorBspEnabled);
|
||||
Assert.False(vm.ProbeIndoorBsp);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PhysicsDiagnostics.ProbeIndoorBspEnabled = originalEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue