fix(ui): OP8 round-2 residuals — inert-row conflict exclusion, DAT-default display, injectivity pin
R1 (code half): store-only rows (MappedAction null) are excluded from the conflict universe — they never reach the InputDispatcher, so a chord they display cannot collide; counting them made the ten Camera Alternate arrow-key defaults trip a false N-way confirm on any arrow rebind. Mapped cross-context sharing (retail's ConflictingMaps — the combat cluster) remains deferred as ISSUES #373 with the OP8 gate script now carrying the explicit do-not-file warning. SHOULD: unmapped rows with no persisted chords display their DAT defaults (retail shows the arrow keys; blank read as 'unbound') — display-only, the store is untouched until the row itself is edited; the independence test updated to pin the new display semantics while keeping its storage-isolation asserts. Injectivity of RetailActionIdentityTable is now test-enforced (load-bearing for both M1's per-row activation capture and M2's de-alias). R2: AP-203 addendum names the ten same-verb-sibling-live rows and the conflict exclusion. Full Release suite in this worktree: 13,155 passed / 4 skips / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b1968ce980
commit
f1d502072e
4 changed files with 66 additions and 6 deletions
|
|
@ -557,12 +557,19 @@ public sealed class KeyboardConfigControllerTests
|
|||
Assert.Equal(InputAction.CameraRotateLeft, ctx5.MappedAction);
|
||||
Assert.Null(ctx6.MappedAction); // unmapped — no live dual-binding infrastructure (M2)
|
||||
|
||||
// Rebinding ctx5's row must not touch ctx6's storage, and vice versa.
|
||||
// Round-2 SHOULD-FIX: an unmapped row with no persisted chords now
|
||||
// DISPLAYS its DAT defaults (retail shows the arrow keys; blank read
|
||||
// as "unbound"). Display-only — storage stays untouched until the
|
||||
// user edits THIS row.
|
||||
Assert.NotEmpty(ctx6.Model.Current);
|
||||
var ctx6InitialDisplay = ctx6.Model.Current.ToArray();
|
||||
|
||||
// Rebinding ctx5's row must not touch ctx6's display or storage.
|
||||
ctx5.KeyButtons[0].OnClick!.Invoke();
|
||||
fake.Capture(ChordW);
|
||||
Assert.Contains(ChordW, ctx5.Model.Current);
|
||||
Assert.Empty(ctx6.Model.Current); // ctx6 never seeded/written by ctx5's edit
|
||||
Assert.Empty(fake.Unmapped); // ctx6 untouched — only ctx5's mapped write happened
|
||||
Assert.Equal(ctx6InitialDisplay, ctx6.Model.Current); // unchanged by ctx5's edit
|
||||
Assert.Empty(fake.Unmapped); // ctx6's STORE untouched — display seeding writes nothing
|
||||
|
||||
ctx6.KeyButtons[0].OnClick!.Invoke();
|
||||
fake.Capture(ChordA);
|
||||
|
|
@ -584,4 +591,23 @@ public sealed class KeyboardConfigControllerTests
|
|||
|
||||
Assert.Null(controller);
|
||||
}
|
||||
|
||||
// ── OP8 re-review round 2: identity-map injectivity is load-bearing for
|
||||
// BOTH M1 (per-row activation capture) and M2 (no dual rows editing one
|
||||
// action) — pin it so a future mapping addition cannot silently alias. ──
|
||||
|
||||
[Fact]
|
||||
public void IdentityMap_IsInjective_NoTwoRowsShareOneInputAction()
|
||||
{
|
||||
var seen = new Dictionary<InputAction, (uint MapId, uint ActionId)>();
|
||||
foreach (((uint mapId, uint actionId), InputAction action) in RetailActionIdentityTable.Map)
|
||||
{
|
||||
Assert.False(
|
||||
seen.TryGetValue(action, out (uint MapId, uint ActionId) prior),
|
||||
$"InputAction.{action} is mapped by BOTH (0x{prior.MapId:X}, 0x{prior.ActionId:X}) "
|
||||
+ $"and (0x{mapId:X}, 0x{actionId:X}) — aliasing reintroduces the M2 twin-row clobber.");
|
||||
seen[action] = (mapId, actionId);
|
||||
}
|
||||
Assert.True(seen.Count > 100, $"sanity: only {seen.Count} mapped actions seen");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue