fix(ui): port source-aware toolbar drops
Plan retail shortcut drops from a complete post-lift snapshot so fresh inventory items use cyclic-right displacement while toolbar aliases restore to their vacated slot. Apply local mutations atomically, preserve raw shortcut fields, emit exact Remove/Add ordering, and retire AP-102 after the live ACE gate. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
b5b230c860
commit
3281e0acb4
8 changed files with 462 additions and 23 deletions
162
tests/AcDream.Core.Tests/Items/ShortcutDropPlannerTests.cs
Normal file
162
tests/AcDream.Core.Tests/Items/ShortcutDropPlannerTests.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
using AcDream.Core.Items;
|
||||
|
||||
namespace AcDream.Core.Tests.Items;
|
||||
|
||||
public sealed class ShortcutDropPlannerTests
|
||||
{
|
||||
[Fact]
|
||||
public void FreshItemToEmpty_AddsOnlyAtTarget()
|
||||
{
|
||||
var slots = Empty();
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanDrop(
|
||||
slots, ShortcutDropSource.FreshItem, sourceSlot: 11, targetSlot: 4,
|
||||
new ShortcutEntry(11, 0xA001u, 0u));
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
ShortcutMutation.Add(new ShortcutEntry(4, 0xA001u, 0u)),
|
||||
}, plan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreshItemToOccupied_MovesDisplacedToFirstEmptyOnRight()
|
||||
{
|
||||
var slots = Empty();
|
||||
slots[5] = new ShortcutEntry(5, 0xB001u, 0x11223344u);
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanDrop(
|
||||
slots, ShortcutDropSource.FreshItem, sourceSlot: 0, targetSlot: 5,
|
||||
new ShortcutEntry(0, 0xA001u, 0u));
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
ShortcutMutation.Remove(5),
|
||||
ShortcutMutation.Add(new ShortcutEntry(5, 0xA001u, 0u)),
|
||||
ShortcutMutation.Add(new ShortcutEntry(6, 0xB001u, 0x11223344u)),
|
||||
}, plan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreshItemToOccupied_CyclicSearchWrapsToSlotZeroSide()
|
||||
{
|
||||
var slots = Full();
|
||||
slots[2] = null;
|
||||
slots[16] = new ShortcutEntry(16, 0xB001u, 0u);
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanDrop(
|
||||
slots, ShortcutDropSource.FreshItem, sourceSlot: 0, targetSlot: 16,
|
||||
new ShortcutEntry(0, 0xA001u, 0u));
|
||||
|
||||
Assert.Equal(ShortcutMutation.Add(new ShortcutEntry(2, 0xB001u, 0u)), plan[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreshItemToFullBar_DropsDisplacedWhenNoEmptySlotExists()
|
||||
{
|
||||
var slots = Full();
|
||||
slots[5] = new ShortcutEntry(5, 0xB001u, 0u);
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanDrop(
|
||||
slots, ShortcutDropSource.FreshItem, sourceSlot: 0, targetSlot: 5,
|
||||
new ShortcutEntry(0, 0xA001u, 0u));
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
ShortcutMutation.Remove(5),
|
||||
ShortcutMutation.Add(new ShortcutEntry(5, 0xA001u, 0u)),
|
||||
}, plan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShortcutAliasToOccupied_RestoresDisplacedToVacatedSource()
|
||||
{
|
||||
var slots = Empty(); // alias was already removed from source slot 3 at lift
|
||||
slots[5] = new ShortcutEntry(5, 0xB001u, 0x55667788u);
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanDrop(
|
||||
slots, ShortcutDropSource.ShortcutAlias, sourceSlot: 3, targetSlot: 5,
|
||||
new ShortcutEntry(3, 0xA001u, 0x11223344u));
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
ShortcutMutation.Remove(5),
|
||||
ShortcutMutation.Add(new ShortcutEntry(5, 0xA001u, 0x11223344u)),
|
||||
ShortcutMutation.Add(new ShortcutEntry(3, 0xB001u, 0x55667788u)),
|
||||
}, plan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreshDuplicate_RemovesOldOccurrenceBeforeSearchingForDisplacedSlot()
|
||||
{
|
||||
var slots = Full();
|
||||
slots[5] = new ShortcutEntry(5, 0xB001u, 0u);
|
||||
slots[7] = new ShortcutEntry(7, 0xA001u, 0xA5C31234u);
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanDrop(
|
||||
slots, ShortcutDropSource.FreshItem, sourceSlot: 0, targetSlot: 5,
|
||||
new ShortcutEntry(0, 0xA001u, 0u));
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
ShortcutMutation.Remove(5),
|
||||
ShortcutMutation.Remove(7),
|
||||
ShortcutMutation.Add(new ShortcutEntry(5, 0xA001u, 0u)),
|
||||
ShortcutMutation.Add(new ShortcutEntry(7, 0xB001u, 0u)),
|
||||
}, plan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonObjectRecord_IsVisuallyEmptyForCyclicSearch()
|
||||
{
|
||||
var slots = Full();
|
||||
slots[6] = new ShortcutEntry(6, 0u, 0x01020304u);
|
||||
slots[5] = new ShortcutEntry(5, 0xB001u, 0u);
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanDrop(
|
||||
slots, ShortcutDropSource.FreshItem, sourceSlot: 0, targetSlot: 5,
|
||||
new ShortcutEntry(0, 0xA001u, 0u));
|
||||
|
||||
Assert.Equal(ShortcutMutation.Add(new ShortcutEntry(6, 0xB001u, 0u)), plan[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullStackMerge_RekeysAllMatchesAndPreservesRawSpellWord()
|
||||
{
|
||||
var slots = Empty();
|
||||
slots[2] = new ShortcutEntry(2, 0xA001u, 0x11223344u);
|
||||
slots[9] = new ShortcutEntry(9, 0xA001u, 0x55667788u);
|
||||
|
||||
ShortcutMutation[] plan = ShortcutDropPlanner.PlanFullStackMerge(
|
||||
slots, 0xA001u, 0xB001u);
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
ShortcutMutation.Remove(2),
|
||||
ShortcutMutation.Add(new ShortcutEntry(2, 0xB001u, 0x11223344u)),
|
||||
ShortcutMutation.Remove(9),
|
||||
ShortcutMutation.Add(new ShortcutEntry(9, 0xB001u, 0x55667788u)),
|
||||
}, plan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidDrop_ReturnsNoMutation()
|
||||
{
|
||||
Assert.Empty(ShortcutDropPlanner.PlanDrop(
|
||||
Empty(), ShortcutDropSource.FreshItem, 0, -1,
|
||||
new ShortcutEntry(0, 0xA001u, 0u)));
|
||||
Assert.Empty(ShortcutDropPlanner.PlanDrop(
|
||||
Empty(), ShortcutDropSource.FreshItem, 0, 0,
|
||||
new ShortcutEntry(0, 0u, 0u)));
|
||||
}
|
||||
|
||||
private static ShortcutEntry?[] Empty() => new ShortcutEntry?[ShortcutStore.SlotCount];
|
||||
|
||||
private static ShortcutEntry?[] Full()
|
||||
{
|
||||
var slots = Empty();
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
slots[i] = new ShortcutEntry(i, (uint)(0xC000 + i), 0u);
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue