FA5 mechanism-faithfulness review of 7ed79eaf/bc29a1db/7e394cbf. Verdict APPROVE-WITH-FIXES. Every high-stakes claim re-derived from the PDB-paired 2013 decomp: CF-1's unconditional 0x001F post-world arm (00490d59 sits OUTSIDE the busy-count guard), the monarch/patron/self field sources (UpdatePlayerData/UpdateMonarchData/UpdatePatronData), the SF-7 per-relationship gate, swear=world-selection/no-SetSelectedObject, and the AD-86 ACE-zeroed-field citations all match retail. MANDATORY live-mount probe RAN and PASSED against the real installed DATs (1/1) — the scoped doubled-0x10000492 NotSame assertion and a full production Bind() with zero "not found" held. FA5 unit suite 36/36 green. One SHOULD-FIX (LOW): FA5 greys the offline vassal NAME (OfflineNameColor) — retail's UpdateVassalsData @004924c3 sets the name with no color; the offline cue is exclusively the authored 0x100004AA marker toggle. Either drop OfflineNameColor or honestly register it (the AD-82 addendum's "covered by the marker" framing understates it). Does not block the gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
347 lines
18 KiB
Markdown
347 lines
18 KiB
Markdown
# Campaign FA slice FA5 — mechanism-faithfulness review
|
|
|
|
**Reviewer lens:** mechanism-faithfulness (retail-fidelity), read-only.
|
|
**Date:** 2026-08-12. **Target commits:** `7ed79eaf` (code+tests),
|
|
`bc29a1db` (register/gate-script/ledger), `7e394cbf` (NUL-byte repair).
|
|
**Contract:** `docs/plans/2026-08-11-fellowship-allegiance-campaign.md`
|
|
§FA5 (CORRECTED row — `0x001F` subscription, NOT `0x027B`) + lane-C
|
|
allegiance-wire doc + `fa-panel-structure` §3.2/§10.
|
|
**Worktree HEAD at review:** `b6c4a4fa` (clean).
|
|
|
|
---
|
|
|
|
## VERDICT: APPROVE-WITH-FIXES (one SHOULD-FIX, LOW severity)
|
|
|
|
FA5 is mechanism-faithful. Every high-stakes claim was re-derived from
|
|
the PDB-paired 2013 decomp (not trusted from the implementer's summary):
|
|
CF-1's unconditional `0x001F` post-world arm, the monarch/patron/self
|
|
field sources, the per-relationship SF-7 gate, the swear/break/kick
|
|
targets, and the ACE-zeroed-field honesty all match retail. The
|
|
mandatory live-mount probe **PASSED against the real installed DATs**
|
|
(the anti-fixture-green safeguard the implementer could not run) —
|
|
including the doubled-`0x10000492` scoped dual-resolution that is exactly
|
|
where a live-only defect could have hidden.
|
|
|
|
The single SHOULD-FIX is a LOW-severity invented-visual: FA5 greys an
|
|
offline vassal's **name** (`OfflineNameColor`), which retail does not do
|
|
— retail's offline indication is exclusively the authored `0x100004AA`
|
|
marker toggle, with the name left at normal color. The AD-82 addendum
|
|
mentions `OfflineNameColor` but frames it as "already covered by the
|
|
retail-authored `0x100004AA` marker," which understates it: the
|
|
name-grey is an *additional* cue retail never paints. This does not block
|
|
the connected gate (data + the authored marker are correct); it needs
|
|
either removal or an honest register entry.
|
|
|
|
---
|
|
|
|
## Live-mount probe — actual output (MANDATORY, reported explicitly)
|
|
|
|
Run: `ACDREAM_PROBE_LIVE_MOUNT=1`,
|
|
`ACDREAM_DAT_DIR=%USERPROFILE%\Documents\Asheron's Call`,
|
|
`dotnet test tests/AcDream.App.Tests -c Release --no-build --filter
|
|
"FullyQualifiedName~SocialPanelLiveMountProbe"`. The App test project was
|
|
rebuilt first (with dependencies) since this fresh worktree's artifacts
|
|
predated FA5.
|
|
|
|
**Result: `Passed! - Failed: 0, Passed: 1, Skipped: 0` (exit 0).**
|
|
`SocialPanelLiveMountProbeTests.ProbeLiveMountShapes [84 ms]`.
|
|
|
|
The probe is NOT a no-op green — against live DATs it asserts (all held):
|
|
|
|
- `Assert.Equal(2, passupCount)` — `0x10000492` occurs exactly twice
|
|
under the allegiance page.
|
|
- The **scoped dual-resolution**: `monarchScopedPassup` found under
|
|
`0x10000490` (under monarch field `0x10000255`), `patronScopedPassup`
|
|
found under patron field `0x1000025A`, both non-null AND
|
|
`Assert.NotSame` — proving the two `FindDescendant` sites resolve to
|
|
DIFFERENT real instances. A flat/unscoped lookup would silently pick
|
|
one for both roles; this catches it.
|
|
- Vassal ListBox `0x10000260` template resolves via the production
|
|
`RowTemplateResolver`; row fields `0x10000268`/`0x10000269` (UiText),
|
|
`0x100004AA` (offline marker) all resolve.
|
|
- Checkbox `0x10000262` (UiButton) + label
|
|
`ID_PlayerOption_IgnoreAllegianceRequests` resolves non-empty.
|
|
- All five allegiance strings resolve non-empty:
|
|
`ID_Allegiance_MonarchLabel`, `PatronSlashMonarchLabel`,
|
|
`SwearConfirmation`, `BreakConfirmation`, `KickConfirmation`.
|
|
- A full production `SocialAllegiancePageController.Bind()` against the
|
|
live layout emits **zero "not found"** console warnings.
|
|
|
|
The raw dump confirms `0x10000490` is authored under BOTH the monarch
|
|
field (`0x10000255`, at `(210,0 90x36)`) and the patron field
|
|
(`0x1000025A`, at `(220,0 80x36)`); FA5's monarch-experience lookup
|
|
scopes to the monarch instance, and its patron-experience lookup recurses
|
|
from the patron field (finding the patron instance) — matching retail's
|
|
own `GetChildRecursiveTemplate(sub-block, …)` vs
|
|
`GetChildRecursive(m_pPatronField, …)` split (see item 2).
|
|
|
|
FA5 unit suite (targeted, this build):
|
|
`SocialPanelControllerTests` + `GameplayConfirmationControllerTests` =
|
|
**36 passed / 0 failed**.
|
|
|
|
---
|
|
|
|
## Per-item verification (re-derived from primary source)
|
|
|
|
### 1. CF-1 — the `0x001F` subscription (highest stakes) — VERIFIED
|
|
|
|
**Unconditional post-world arm (the core claim).** `pseudo-c:157175`,
|
|
`gmAllegianceUI::RecvNotice_PlayerDescReceived @0x00490D40`:
|
|
|
|
```
|
|
if (m_bAwaitingUpdate == 0) { m_bAwaitingUpdate = 1; IncrementBusyCount(); }
|
|
CM_Allegiance::Event_UpdateRequest(1); // 00490d59 — OUTSIDE the if
|
|
```
|
|
|
|
The `Event_UpdateRequest(1)` at `00490d59` sits OUTSIDE the `if` guard
|
|
(which only wraps the busy-count increment), so retail sends `0x001F(1)`
|
|
**unconditionally**, exactly as the implementer claims.
|
|
`SocialAllegiancePageController.RedeclareAfterWorldEntry()` mirrors this:
|
|
it calls `SetUpdateSubscription(true)` with no latch comparison, and
|
|
advances the latch only on `Accepted` (so a dropped send retries).
|
|
|
|
**Three arming points, all confirmed against the decomp** (every
|
|
`Event_UpdateRequest` site enumerated):
|
|
`PostInit @0x004911C6 → 1` (Bind's `SetPageVisible(true)` attempt,
|
|
pre-world no-op in production), `OnVisibilityChanged` visible
|
|
`@0x004912DD → 1` / hidden `@0x00491311 → 0` (edge-triggered
|
|
`SetPageVisible`), `PlayerDescReceived @0x00490D59 → 1`
|
|
(`RedeclareAfterWorldEntry`, unconditional). A fourth retail arm exists —
|
|
`OnQualityChanged @0x00490DC9 → 1` (Patron/Monarch instance-id change,
|
|
`fa-panel-structure` §6.3) — NOT modeled by FA5; covered instead by
|
|
acdream's `0x0020`-seed + `Revision` polling. Given the subscription
|
|
model (data streams while subscribed), this is a benign
|
|
data-refresh-mechanism substitution, not a functional gap.
|
|
|
|
**Genuinely avoids MF-3-REOPEN.** Reconnect seams traced end-to-end:
|
|
pre-world `LiveSessionResetBindings.SessionDialogs → ResetSessionTransientUi
|
|
→ SocialPanelController.ResetSessionDeclaration →
|
|
_allegiance.ResetPageVisibleLatch()` (clears latch, **no send**);
|
|
post-world `EnteredWorld.RestoreLayout →
|
|
RedeclareSocialPanelAfterWorldEntry →
|
|
SocialPanelController.RedeclareAfterWorldEntry →
|
|
_allegiance.RedeclareAfterWorldEntry()` (unconditional send). So a
|
|
reconnect while the panel is CLOSED still re-subscribes.
|
|
|
|
**The test genuinely exercises the world gate.**
|
|
`Reconnect_ReDeclaresSubscription_AfterWorldEntry_EvenWhilePanelClosed`
|
|
binds with the window never shown (`_visible=false`), clears the latch,
|
|
then asserts `RedeclareAfterWorldEntry` records `subscription:True`. This
|
|
only passes because the path is unconditional — a conjunction-gated shape
|
|
(`UpdateAllegiancePageVisibility`) would compute `false && … = false`,
|
|
and with the latch already `false` would send nothing, failing the
|
|
assertion. Confirmed it would fail against an edge-triggered shape.
|
|
|
|
**`0x027B` is NOT wired for data.** FA5's sole subscription surface is
|
|
`SetUpdateSubscription` (`0x001F`); no info-request send exists in the
|
|
controller. Class doc and gate-script both state `0x027B`/`0x027C` are
|
|
text-only chat (FA2 MF-2). Confirmed.
|
|
|
|
### 2. Monarch/patron/self blocks (SF-7 rewrite) — VERIFIED (all field sources)
|
|
|
|
Re-decompiled `UpdatePlayerData @0x00491330`, `UpdateMonarchData
|
|
@0x00491B40`, `UpdatePatronData @0x004917C0` fresh:
|
|
|
|
- **`0x10000251` = the ALLEGIANCE name, not the viewer's.** `00491397`:
|
|
`m_pAllegianceName ← _allegiance.m_AllegianceName` via
|
|
`AddVariable_String`. FA5 `RefreshSelfBlock` uses
|
|
`snapshot.AllegianceName`. ✓
|
|
- **Self followers `0x10000252` = `_total_vassals`.** `00491506`:
|
|
`AddVariable_Int(_total_vassals) → m_pPlayerFollowers`. FA5
|
|
`snapshot.TotalVassals`. ✓
|
|
- **Self rank `0x10000253` = `InqInt(qualities, 0x1e)` (AllegianceRank)
|
|
+ `GetTitle`.** `00491578`: `CBaseQualities::InqInt(&var_1f8[0xe],
|
|
0x1e, …)` then `AllegianceData::GetTitle`. FA5 substitutes
|
|
`snapshot.Rank` (documented data-source substitution). ✓
|
|
- **Monarch gate = hide when `monarch==viewer || monarch==0`.**
|
|
`00491c0f`. FA5 `hasMonarch = monarch is {} m && m.CharacterId !=
|
|
selfGuid` (null when no monarch). Matches under all cases. ✓
|
|
- **Hidden branch blanks name AND followers.** `00491f8c`
|
|
(`m_pMonarchName ← ""`) and `00491fcd` (`m_pMonarchFollowers ← ""`).
|
|
FA5 blanks both. ✓
|
|
- **Shown followers = `_total_members - 1`.** `00491eac`:
|
|
`ecx_43 = ebx_3->_total_members - 1`. FA5 `TotalMembers - 1` (guarded).
|
|
✓ (test asserts "2" for `TotalMembers=3`).
|
|
- **patron==monarch → `PatronSlashMonarchLabel` + reveal `0x10000490`
|
|
sub-block + `0x10000492` = viewer's CpTithed.** `00491c60` (else of
|
|
`GetPatron != monarch`): sets label, `GetChildRecursive(0x10000490)
|
|
->SetVisible(1)` `00491cbc`, `GetChildRecursiveTemplate(sub-block,
|
|
0x10000492)` with `GetData(viewer).cp_tithed`. FA5 maps
|
|
`patronIsMonarch → PatronSlashMonarchLabel`, `subBlock.Visible=true`,
|
|
`monarchExperiencePassedUp ← Member(self).CpTithed`. ✓ patron!=monarch
|
|
→ `MonarchLabel` + hide sub-block (`00491dc5`). ✓
|
|
- **Enabled ← IsLoggedIn** (`SetState(1)` vs `SetState(0xd)` `00491f0c`).
|
|
FA5 `_monarchField.Enabled = monarchData.IsLoggedIn`. ✓
|
|
- **Patron gate = hide when `patron==0 || patron==monarch`.**
|
|
`004918b1`. FA5 `hasPatron = patron is {} p && (monarch is not {} m ||
|
|
p.CharacterId != m.CharacterId)`; negation matches retail in all four
|
|
cases (verified case-by-case). ✓ Shown: name = `GetFullName`
|
|
(`004918e0`); Enabled ← IsLoggedIn (`0049197d`); experience
|
|
`GetChildRecursive(m_pPatronField, 0x10000492)` = viewer's own tithed
|
|
(`004919b9` `GetData(viewer)`). FA5 matches (`patronExperiencePassedUp
|
|
← Member(self).CpTithed`). ✓
|
|
|
|
The doubled-`0x10000492` scoping matches retail exactly: monarch's under
|
|
the sub-block, patron's recursively under the patron field. FA5's two
|
|
`FindDescendant` sites (`Bind`, lines 315-319) reproduce this precisely.
|
|
|
|
### 3. Vassal list — VERIFIED
|
|
|
|
`RuntimeAllegianceState.AllegianceView.GetVassals` returns
|
|
`AllegianceProfileLookups.FindVassals` order — the FA1-assembled
|
|
REVERSE-wire order (each record prepended, doc comment cites lane C
|
|
§4.4). FA5 `RefreshRoster` materializes `new List<>(Vassals(self))`
|
|
and iterates with **no re-sort**; `RebuildRoster` uses
|
|
`FlushPreservingScroll()` (NOT `Flush()`), so the FA3 scroll-reset
|
|
carry-forward is NOT reintroduced. The allocating `GetVassals` call runs
|
|
only inside the `snapshot.Revision != _lastRosterRevision` gate — not
|
|
per-frame (honoring FA2's blast SF-7 caution). ✓
|
|
|
|
### 4. Swear/break/kick targets + type-1 confirmation — VERIFIED
|
|
|
|
- **Swear = WORLD selection.** `UpdateSwearButton @0x004908E0` gates on
|
|
`GetPatron(self)==0 && selectedID!=0 && selectedID!=self &&
|
|
IsPlayer(selectedID) && GetData(selectedID)==0`, where
|
|
`ACCWeenieObject::selectedID` is the world selection. FA5 reads
|
|
`Selection.SelectedObjectId` at click, resolves via
|
|
`ResolveWorldObjectName` (= `Objects.Get(guid)?.GetAppropriateName()`,
|
|
the same `ClientObjectTable` resolver `ToolbarRuntimeBindings.ResolveName`
|
|
uses). ✓ The one omission — `IsPlayer()` — is registered AD-84 (same
|
|
class as AD-83). ✓
|
|
- **No SetSelectedObject for Allegiance's list** (fa-panel-structure
|
|
§6.2): Allegiance's `ListenToElementMessage` idMessage-4 reads the row's
|
|
`0x10000001` into `m_iidSelectedVassal` only — no
|
|
`ACCWeenieObject::SetSelectedObject` (Fellowship's DOES call it). FA5's
|
|
panel-local `_selectedVassalGuid` with no world-sync is faithful, and
|
|
the FA4 world→panel sync is NOT reintroduced for Allegiance. ✓
|
|
- **Break = current patron** (`UpdateBreakButton @0x004909D0`:
|
|
`GetPatron!=0`; `MakeBreakConfirmationDialog @0x00492BF0`). FA5
|
|
`OnBreakClick → Patron(self) → Break(patron.CharacterId)`. ✓
|
|
- **Kick = panel-local selected vassal** (`MakeKickConfirmationDialog
|
|
@0x00492E10`, `m_iidSelectedVassal`). FA5 `OnKickClick →
|
|
_selectedVassalGuid → Kick`. ✓
|
|
- **Type-1 confirmation reaches the generic controller.**
|
|
`RetailUiRuntime.HandleConfirmationRequest` forwards every request
|
|
straight to `GameplayConfirmationController` (no intercept — FA4's
|
|
type-4 removal never touched type-1). New test
|
|
`AllegianceSwearRequest_Type1_OpensDialog_MessageVerbatim_AndSendsAcceptOnClose`
|
|
verifies a `ConfirmationType 1` opens a verbatim dialog and sends
|
|
accept on close. ✓
|
|
|
|
### 5. ACE-zeroed fields (AD-86) — VERIFIED (citations accurate)
|
|
|
|
`references/ACE` is not checked out in this worktree, so lane C §5.1 is
|
|
the authority. AD-86's cited lines match it field-for-field:
|
|
`AllegianceHierarchy.cs` officers `:74-75`, officerTitles `:78-83`,
|
|
motd/motdSetBy `:86-89`, nameLastSetTime/isLocked/approvedVassal
|
|
`:62-64`/`:153-155`, broadcast counters `:53-56`; `AllegianceData.cs`
|
|
timeOnline/allegianceAge `:59-60,86-89,111-112`. Retail's own
|
|
`gmAllegianceUI` has no widget for any of them (fa-panel-structure §3.3),
|
|
so dropping them past the parse layer matches retail's presentation. The
|
|
register row, blast column, and lane-C pointer are all accurate.
|
|
|
|
### 6. §6 DISCIPLINE compliance — VERIFIED
|
|
|
|
- **Resolver on every Build.** Main panel Build passes `resolver.Resolve`
|
|
(`RetailUiRuntime.cs:2715`); the vassal row `RowTemplateResolver`
|
|
builds each template with `strings.Resolve` (`:2765`). ✓
|
|
- **Doubled `0x10000492` scoped under each parent** (both sites found):
|
|
monarch under `monarchIsPatronSubBlock` (`0x10000490`), patron under
|
|
`patronField` — probe-confirmed `NotSame`. ✓
|
|
- **No lazy `0x0`.** All `FindDescendant` failures null-check + log; no
|
|
magenta-placeholder id path. ✓
|
|
- **`BlankSentinel` is compare-only and never displayed.** Used solely in
|
|
`SetProvider`'s `lastValue == sentinelValue` / `lastValue =
|
|
sentinelValue`; the rendered text comes from `BlankLineProvider`
|
|
(`[new UiText.Line(" ", …)]`, a literal single space). The NUL-repair
|
|
commit `7e394cbf` restored the two spaces; **byte-swept all 11
|
|
FA5-touched files (src/tests/docs): NUL=0, control-char=0.** ✓
|
|
|
|
### 7. Retail anchors + gate-script §FA5 — VERIFIED
|
|
|
|
Five+ anchors spot-checked and confirmed: `Event_UpdateRequest` site
|
|
inventory (`157175/157212/157414/157455/157495`); `UpdateSwearButton`
|
|
enable rule; `UpdateBreakButton`; `UpdateVassalsData @0x00492340`
|
|
(offline marker `SetVisible(1)` when `!IsLoggedIn` `@004925b1`; row
|
|
identity via `SetAttribute_InstanceID(0x10000001)`); the `Update()`
|
|
ordering (self→monarch→patron→vassals→swear→break→kick,
|
|
`pseudo-c:158677+`) matches FA5's `Tick`.
|
|
|
|
The gate-script §FA5 **correctly avoids the false-route the reviewer
|
|
flagged**: it explicitly states the panel populates via `0x001F`→`0x0020`
|
|
on tab-show, and says "**Do not use `@allegiance info` as a trigger for
|
|
anything in this section.**" The FA3 section (already corrected in FA3's
|
|
fix round) is consistent — it tells the user FA3 does NOT send `0x001F`
|
|
and `@allegiance info` won't reveal the panel. Step 4 correctly describes
|
|
the unconditional post-world resend as the MF-3-REOPEN analogue; steps
|
|
5-8 pre-empt the AD-85 "just a number" and SF-7 per-relationship false
|
|
reports. No wrong step.
|
|
|
|
---
|
|
|
|
## SHOULD-FIX (numbered)
|
|
|
|
**SF-1 (LOW) — offline vassal name-grey is an invented visual not in
|
|
retail; register or remove it.** `UpdateVassalsData @0x004924c3` sets the
|
|
vassal name (`0x10000268`) via `GetFullName` with **no color argument**;
|
|
retail's *only* offline indication is the dedicated `0x100004AA` marker
|
|
toggle (`@004925b1`: `SetVisible(1)` offline / `(0)` online).
|
|
`SocialAllegiancePageController.UpdateRow` (`SocialAllegiancePageController.cs:708`)
|
|
additionally renders the offline name in `OfflineNameColor
|
|
(0.6,0.6,0.6,1)` — a grey retail never paints. The AD-82 FA5 addendum
|
|
mentions `OfflineNameColor` but frames it as "already covered by the
|
|
retail-authored `0x100004AA` marker," which understates it: the marker is
|
|
faithful; the name-grey is a *separate additional* cue on top of it.
|
|
**Fix:** either (a) render vassal names in `TextColor` unconditionally
|
|
(drop `OfflineNameColor`), letting the authored `0x100004AA` marker carry
|
|
the offline state exactly as retail does — the more faithful option — OR
|
|
(b) correct the AD-82 addendum to honestly register the offline
|
|
name-grey as an adaptive cue rather than claiming no invented tints are
|
|
used. Does not block the connected gate (data + marker are correct).
|
|
|
|
---
|
|
|
|
## Minor observations (NOT fixes — no action required)
|
|
|
|
- **`OnQualityChanged`/quality-handler re-subscription arms** (Patron
|
|
`0x19`/Monarch `0x1A` instance-id change → `Event_UpdateRequest(1)`,
|
|
`00490dc9`, fa-panel-structure §6.3) are not modeled. Benign: acdream's
|
|
subscription-plus-`0x0020`-revision-polling refreshes the data through
|
|
a different mechanism. Not a functional gap.
|
|
- **`SetTalkFocusEnabled(4/5/6, …)`** (patron/monarch/vassal chat-focus
|
|
toggles, tail of each Update* function) are not modeled — a
|
|
chat-integration nicety outside the panel-display scope.
|
|
- **Monarch label `LinesProvider` is reassigned unconditionally each
|
|
`Tick`** in the shown branch (`RefreshMonarchBlock:555`) — a per-frame
|
|
closure/list allocation, unlike the guarded `SetLine` used for
|
|
name/followers. Blast-lens perf nit (rarely changes; could be guarded);
|
|
not a mechanism concern.
|
|
- **NIT:** AD-86's prose says "seven fields" but the parenthetical lists
|
|
nine (7 `AllegianceProfile` + 2 `AllegianceData`). Cosmetic; the field
|
|
list and citations are complete/correct.
|
|
|
|
---
|
|
|
|
## What verified clean (explicit)
|
|
|
|
- **Live-mount probe: PASSED against real DATs** (1/1), including the
|
|
scoped doubled-`0x10000492` `NotSame` assertion and full production
|
|
`Bind()` with zero "not found".
|
|
- CF-1's unconditional post-world `0x001F` arm is byte-faithful to
|
|
`RecvNotice_PlayerDescReceived` and genuinely closes MF-3-REOPEN; the
|
|
decisive reconnect test exercises the world-closed gate.
|
|
- All monarch/patron/self field sources match a fresh decompile of
|
|
`UpdatePlayerData`/`UpdateMonarchData`/`UpdatePatronData`.
|
|
- Vassal roster: FA1 reverse order preserved, no re-sort,
|
|
`FlushPreservingScroll` (no scroll-reset), revision-gated (not
|
|
per-frame).
|
|
- Swear=world-selection / Break=patron / Kick=panel-local; no
|
|
`SetSelectedObject` for Allegiance; type-1 confirmation flows to the
|
|
generic controller.
|
|
- AD-84/AD-85/AD-86 + AD-82 addendum are accurate (AD-86's ACE citations
|
|
match lane C §5.1 line-for-line).
|
|
- §6: resolver on every Build; doubled ids scoped; no lazy `0x0`;
|
|
`BlankSentinel` compare-only; **zero NUL/control-char corruption across
|
|
all 11 FA5 files** after `7e394cbf`.
|
|
- Gate-script §FA5 states the true `0x001F`-not-`@allegiance-info`
|
|
expectation — no false-route.
|