feat: port retail magic lifecycle and retained spell UI

Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-15 10:55:22 +02:00
parent 7b7ffcd278
commit 07be994d97
84 changed files with 17822 additions and 1051 deletions

View file

@ -38,6 +38,47 @@ public class InputDispatcherIsActionHeldTests
Assert.False(dispatcher.IsActionHeld(InputAction.MovementForward));
}
[Fact]
public void IsActionHeld_CombatBindingOnSameChord_ShadowsGamePolling()
{
var (dispatcher, kb, _, bindings) = Build();
var chord = new KeyChord(Key.W, ModifierMask.None);
bindings.Add(new Binding(chord, InputAction.MovementForward));
bindings.Add(new Binding(
chord,
InputAction.CombatCastCurrentSpell,
Scope: InputScope.MagicCombat));
dispatcher.SetCombatScope(InputScope.MagicCombat);
kb.EmitKeyDown(Key.W, ModifierMask.None);
Assert.False(dispatcher.IsActionHeld(InputAction.MovementForward));
Assert.True(dispatcher.IsActionHeld(InputAction.CombatCastCurrentSpell));
dispatcher.SetCombatScope(null);
Assert.True(dispatcher.IsActionHeld(InputAction.MovementForward));
Assert.False(dispatcher.IsActionHeld(InputAction.CombatCastCurrentSpell));
}
[Fact]
public void IsActionHeld_ExplicitShiftCombatChord_ShadowsBareMovementFallback()
{
var (dispatcher, kb, _, bindings) = Build();
bindings.Add(new Binding(
new KeyChord(Key.W, ModifierMask.None),
InputAction.MovementForward));
bindings.Add(new Binding(
new KeyChord(Key.W, ModifierMask.Shift),
InputAction.CombatCastCurrentSpell,
Scope: InputScope.MagicCombat));
dispatcher.SetCombatScope(InputScope.MagicCombat);
kb.EmitKeyDown(Key.W, ModifierMask.Shift);
Assert.False(dispatcher.IsActionHeld(InputAction.MovementForward));
Assert.True(dispatcher.IsActionHeld(InputAction.CombatCastCurrentSpell));
}
[Fact]
public void IsActionHeld_returns_false_when_no_binding_for_action()
{