Plumbs Parsed.ArmorLevels into the extras composer and ports the retail
armor-level trio + unenchantable legend for the player examination window's
extras list (0x10000335), closing gap G4 and the legend half of G8 from
docs/research/2026-08-25-campaign-as-ground-truth.md.
Decomp evidence (docs/research/named-retail/acclient_2013_pseudo_c.txt):
- CharExamineUI::SetAppraiseInfo @0x004B45F0: the armor-level trio
(@0x004B4FD1-@0x004B5410) gates on ANY of nine base_armor_* fields > 0,
emits one leading spacer, then three rows "Head/Chest/Groin" (Head,
Chest, Abdomen), "Bicep/Wrist/Hand" (UpperArm, LowerArm, Hand),
"Thigh/Shin/Foot" (UpperLeg, LowerLeg, Foot) formatted "AL: %s/%s/%s"
with each part "%d" below 0x270f (9999) or "*%d" with (value-9999) at/
above it (data_794344 vs data_7b110c). The trio precedes the ratings
block and has no trailing spacer of its own.
- The "* = Unenchantable" legend (@0x004B5D7D-@0x004B5DED) is added
UNCONDITIONALLY after the whole `if (InqCreature)` block closes —
confirming ruling R3's "unconditional" reading directly from the raw
decompile, not just the BN flattening theory.
- CreatureExamineUI::SetAppraiseInfo @0x004B3FF0 (monster path): reads the
same nine ratings properties with the same gating/spacer logic, but
never touches base_armor_* or the unenchantable literal. Confirmed the
monster (character:false) path gains neither the trio nor the legend —
CreatureAppraisalRows.BuildExtra is character-gated for both.
- Ruling R4 (spacer discipline): CharExamineUI's own ratings-block leading-
spacer flag (ebx_13) is a known BN-decompiler artifact loss (call-
argument mangling instead of a clean `= 1` assignment). Cross-checked
against CreatureExamineUI's clean version of the identical algorithm:
one leading spacer before the FIRST ratings-family row that fires, one
trailing spacer if ANY fired. The existing BuildExtra ratings logic
(per-row gates 307|313|314, 308|315|316, 350|351; single leading/
trailing spacer) already matched this exactly — no functional change to
the ratings section, only the signature/threading change to make room
for the trio and legend around it.
Changed:
- CreatureAppraisalRows.BuildExtra now takes (properties, armorLevels,
character) instead of (properties) alone. Character-gated trio + legend
wrap the unchanged ratings logic.
- AppraisalUiController.RebuildCreatureStats takes the character flag and
threads appraisal.ArmorLevels through; ApplyCreature passes its own
`character` parameter. No caching needed for the combat refresh to keep
the AL rows: AppraiseInfoParser always parses ArmorLevels into the fresh
Parsed value Apply receives, so a re-Apply of the refreshed response
renders the same rows for free.
- Test signature updates only (no behavior pins changed) plus new
coverage: ArmorLevelTrioUsesRetailGroupingLabelsAndFormatPrecedingRatings,
ArmorLevelPartRendersUnenchantableSentinelAtOrAbove9999 (theory: 9998/
9999/10123), ArmorLevelRowMixesStarredAndPlainPartsIndependently,
AllNineArmorLevelsZeroOrNegativeEmitsNoTrioAndNoSpacer,
ArmorLevelTrioAbsentWhenArmorLevelsIsNull, EachRatingRowGatesIndependently,
LegendIsAbsentOnMonsterPathEvenWithRatingsShown,
LegendIsAlwaysLastOnCharacterPathEvenWithNoOtherExtras (rows-level);
CharacterResponse_ArmorLevelTrioPopulatesExtraListThroughRealBinding,
CharacterResponse_CombatRefreshRetainsArmorLevelRows,
CreatureResponse_NeverGainsArmorLevelTrioOrLegend (controller-level,
through the real LayoutImporter/FixtureLoader binding seam).
No existing pin was corrected — the pre-AS3 ratings gating/spacer
behavior already matched the decomp; only the call signature changed.
Full hermetic suite: AcDream.App.Tests 6208/0 skips; full-solution
15,483/0 skips. Release build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>