Skip to content

chore: FFXIV Patch 5.5 "Trail to the Heavens" + LuminaLookup + status-resolver fixes + StatusNameEnglish#106

Merged
logicallysynced merged 7 commits into
mainfrom
sharlayan-9-rebuild
May 8, 2026
Merged

chore: FFXIV Patch 5.5 "Trail to the Heavens" + LuminaLookup + status-resolver fixes + StatusNameEnglish#106
logicallysynced merged 7 commits into
mainfrom
sharlayan-9-rebuild

Conversation

@logicallysynced
Copy link
Copy Markdown
Contributor

@logicallysynced logicallysynced commented Apr 28, 2026

Summary

Patch-day support for FFXIV 5.5 "Trail to the Heavens" plus a batch of API additions and resolver bug fixes that surfaced during the cross-language audit work.

Patch-day dependency bumps

Dep From To
FFXIVClientStructs 4ad7d0d72 6ea6cd2d1 (~50 commits — Update structs, Update UIModule, Update UIState, Update PlayerState, Update signatures, AddonLookingForGroup rename, CastInfo bitfield migration, Update EventFramework, etc.)
Lumina 7.3.0 7.4.0
Lumina.Excel 7.4.3 7.4.3 (latest published; per-sheet hash-mismatch tolerated by Reader.GameState.EnsureLuminaSheets)

Layout shifts auto-tracked at runtime; only test pins needed bumping

  • UIModule.RaptureLogModule 0x19E0 → 0x1AC0
  • UIModule.RaptureHotbarModule 0x57B80 → 0x57C60
  • PlayerState size 0x908 → 0x920 (harness pin)
  • Vector3 size 0xC → 0x10 (FCS gave it SIMD alignment; field offsets X/Y/Z @ 0/4/8 unchanged)

CastInfo bitfield migration absorbed

FCS migrated CastInfo.IsCasting and CastInfo.Interruptible from standalone byte fields to [BitField<bool>] accessors over a single Flags byte, AND made the struct non-marshalable. ActorItemMapper switched from Marshal.OffsetOf<CastInfo> to FieldOffsetReader.OffsetOf; IsCasting1 / IsCasting2 schema offsets now point at Flags; ActorItemResolver does the bit-0 mask. Downstream entry.IsCasting1 semantic preserved.

New public API: Sharlayan.LuminaLookup

Name → universal row id helpers across 10 sheets — inverse of Reader.Lumina's id-to-name helpers.

Method Resource Field
WeatherIdFromName Weather Name
ActionIdFromName Action Name
StatusIdFromName Status Name
PlaceNameIdFromName Zone / field Name
ContentFinderConditionIdFromName Duty / instance Name
ItemIdFromName Item Name
BNpcNameIdFromName Battle NPC Singular
ENpcResidentIdFromName Friendly NPC Singular
MountIdFromName Mount Singular
CompanionIdFromName Minion Singular
FindRowId<T>(config, name, selector) Generic escape hatch caller-provided

One process-wide Dictionary<string, uint> per sheet type, lazy-built across all 4 client languages and unioned with OrdinalIgnoreCase. Pass "Moon Dust", "快晴", or "Mondstaub" and get the same row id back. Per-language failures (hash mismatch during patch-day) silently degrade.

Cross-language consumer safety: StatusNameEnglish

New StatusItem.StatusNameEnglish property — always English regardless of Configuration.GameLanguage. Existing StatusName remains the localized display value. Consumers comparing against hardcoded English literals (statuses.Find(s => s.StatusNameEnglish == "Iron Will")) now stay correct on JP/DE/FR clients.

Audit confirmed StatusName was the only resolver field switching on GameLanguage. Actor names / target names / action names / chat are either locale-neutral (live memory) or display-only with no English-comparison use case.

The duplicated 5-case language switch in both resolvers became a single LocalizationHelper.SelectLocalized call.

Resolver bug fixes

DefaultStatusEffectOffset was reading garbage

ActorItemMapper left DefaultStatusEffectOffset at the default 0, so the resolver's BlockCopy was reading status entries from the start of the Character struct — pure garbage IDs and stale slots. Same bug in PartyMemberMapper (pointed at the StatusManager struct base instead of _status[0], off by 8 bytes for the Owner pointer prefix). Both now resolve StatusManager._status via FieldOffsetReader. Pinned via new integrity test.

Stacks gating

FCS Status.Param documents itself as "stack count for debuffs; food/potion id otherwise; meaningless for ordinary buffs". The mapper read it as Stacks for every status, surfacing arbitrary Param bytes as fake stack counts (e.g. Reduced Rates Stacks=30). Both resolvers now zero Stacks when statusInfo.MaxStacks <= 1, sourced from a new MaxStacks property on Models.XIVDatabase.StatusItem populated from Lumina's Status sheet.

Resilience

  • Reader.GameState.EnsureLuminaSheets resolves Weather / BGM / BGMFadeType / BGMFade in independent try/catches — a hash mismatch on one sheet no longer voids the others.
  • Harness [4] block probes each typed sheet independently and prints ✓ <RowCount> or ✗ <ExceptionType>: <Message> per sheet so the exact sheet(s) blocked by a schema-version gap are visible.

Test plan

  • dotnet test — 179/179 passing (~+43 new tests vs main)
  • Debug build + deploy to Chromatics Build Dependencies
  • Live verify against patched FFXIV client — 22 scanner keys resolve; per-scene BGM, status names (English + localized), and per-status Stacks all correct
  • LuminaLookup smoke test (harness [5]) — Weather / Action / Status / PlaceName / ContentFinderCondition / Item / BNpcName / ENpcResident / Mount / Companion lookups working with English + Japanese inputs

🤖 Generated with Claude Code

Bumps the FFXIVClientStructs submodule from 4ad7d0d72 → 036e201a2 to
absorb the post-patch struct changes ("Update UIModule", "Update UIState",
"Update signatures" + 20 other commits). Field offsets are reflected at
runtime via FieldOffsetReader so scanner keys auto-track; only manual
upkeep was the integrity-test pins and a couple of harness fixes.

Pin updates (caught by FCSDependencyIntegrityTests):
  - UIModule.RaptureLogModule    : 0x19E0 → 0x1AC0  (+0xE0)
  - UIModule.RaptureHotbarModule : 0x57B80 → 0x57C60 (+0xE0)

Harness fixes:
  - Vector3 size pin 0xC → 0x10 (FCS gave Vector3 SIMD alignment;
    field offsets X/Y/Z @ 0/4/8 unchanged so no read impact).
  - [4] Lumina block now probes each typed sheet independently and
    reports MismatchedColumnHashException per-sheet, so we can tell
    exactly which schema(s) Lumina.Excel hasn't caught up on yet.

Reader.GameState.EnsureLuminaSheets now resolves Weather / BGM /
BGMFadeType / BGMFade in their own try/catch so a hash mismatch on
one sheet doesn't void the others.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@logicallysynced logicallysynced self-assigned this Apr 28, 2026
Pulls 14 additional FCS commits on top of the Patch 5.5 baseline:
"Update structs", AgentLobby / ItemFinderModule / AddonConfigDataSet
updates, ChangeBait fix, AtkValue IDisposable removal, several enum
tweaks. No further integrity-test pin drift — all 136 tests pass
unchanged, scanner keys + chain offsets resolve clean.

Lumina / Lumina.Excel: no new package releases (still 7.3.0 / 7.4.3),
so the schema-version gap from Patch 5.5 persists for whichever sheet(s)
the harness [4] block flags. EnsureLuminaSheets already tolerates this.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dependency updates:
  - FFXIVClientStructs: eb346431f → e2cebc4f4 (10 commits — "Update structs",
    AgentLobby / ItemFinderModule / EventFramework updates, enum tweaks).
  - Lumina: 7.3.0 → 7.4.0.
  - Lumina.Excel: still 7.4.3 (no new release published yet).

CastInfo: FCS migrated CastInfo.IsCasting and CastInfo.Interruptible from
standalone byte fields to [BitField<bool>] accessors over a single Flags
byte at offset 0 (bit 0 = IsCasting, bit 1 = Interruptible). Three follow-on
changes to keep ActorItem casting reads correct:

  1. ActorItemMapper switches Marshal.OffsetOf<CastInfo>(...) calls to
     FieldOffsetReader.OffsetOf — FCS made CastInfo non-marshalable
     (managed-pointer-bearing field added) so the marshalling reflection
     throws ArgumentException on every member.
  2. IsCasting1 / IsCasting2 schema offsets now both point at the Flags
     byte; the resolver does the per-bit mask.
  3. ActorItemResolver reads byte at IsCasting1's offset and tests bit 0
     instead of using TryToBoolean. The "any in-progress cast → true"
     semantic is preserved for downstream callers.

All 136 integrity / mapper tests pass with the new FCS and Lumina.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pulls 15 more FCS commits — "Update structs", new AddonLookingForGroup
hierarchy (renamed from LookingForGroupCondition), QueueFlags / Language
enum tweaks, removal of footgun functions, isInherited swap. No further
integrity-test pin drift; all 136 tests pass unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inverse of Reader.Lumina's id-to-name helpers: given a localised resource
name from any client language, return the universal numeric row id
(Weather "Moon Dust" → 148, Action "リフルジェントアロー" → 98). Powered by
the shared LuminaGameDataCache.

Public surface (Sharlayan.LuminaLookup):
  - WeatherIdFromName / ActionIdFromName / StatusIdFromName
  - PlaceNameIdFromName / ContentFinderConditionIdFromName / ItemIdFromName
  - BNpcNameIdFromName / ENpcResidentIdFromName
  - MountIdFromName / CompanionIdFromName
  - FindRowId<T>(config, name, selector) — generic escape hatch
  - ClearCache() — for rare sqpack-changes scenarios

All return uint?. Caching: one process-wide Dictionary<string, uint> per
sheet type, lazy-built on first call by walking all four client languages
(English / Japanese / German / French) and unioning every row's display
name with OrdinalIgnoreCase. Subsequent calls hit the cached dict.
Per-language GetSheet failures (e.g. MismatchedColumnHashException during
a patch-day window) silently degrade — partial coverage beats none.

Tests (Sharlayan.Tests/LuminaLookupTests.cs, +26 cases):
  - 10 graceful-degradation tests, one per helper.
  - 4 input-validation tests (null / empty / null-config / null-selector).
  - 2 ClearCache cycle tests.
  - 10 surface-contract Theory cases pinning each helper's signature.

Harness [5] section probes every helper with known fixtures (English +
Japanese variants for Weather and Action) so the live sqpack path is
covered. Total tests: 136 → 162.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three related fixes to the status-resolver pipeline that surfaced together
during cross-language audit work:

1. StatusItem.StatusNameEnglish (cross-language consumer safety):
   New property on Sharlayan.Core.StatusItem / IStatusItem populated
   unconditionally with statusInfo.Name.English regardless of
   Configuration.GameLanguage. Existing StatusName remains the localized
   display value. Consumers comparing against hardcoded English literals
   (e.g. statuses.Find(s => s.StatusNameEnglish == "Iron Will")) now stay
   correct on JP/DE/FR clients. Audit confirmed StatusName was the only
   field switching on GameLanguage — actor names, action names, chat etc.
   are either locale-neutral (live memory) or display-only.

   The duplicated 5-case language switch in ActorItemResolver and
   PartyMemberResolver is now a single LocalizationHelper.SelectLocalized
   call, with the English value pulled directly from statusInfo.Name.

2. DefaultStatusEffectOffset wiring:
   ActorItemMapper left this at the default 0 (the wiring was never
   plumbed), so the resolver's BlockCopy was reading status entries from
   the START of the Character struct — pure garbage IDs and stale slots.
   Now points at BattleChara.StatusManager + StatusManager._status (i.e.
   the first real Status slot). Same fix in PartyMemberMapper, which had
   pointed at the StatusManager struct base instead of _status[0] (off
   by 8 bytes for the Owner pointer prefix).

3. Stacks gating:
   FCS Status.Param documents itself as "stack count for debuffs; food/
   potion id otherwise; meaningless for ordinary buffs". The mapper read
   it as Stacks for every status, surfacing arbitrary Param bytes as
   fake stack counts (e.g. "Reduced Rates Stacks=30"). Both resolvers
   now zero Stacks when statusInfo.MaxStacks <= 1, sourced from a new
   MaxStacks property on Models.XIVDatabase.StatusItem populated from
   Lumina's Status sheet.

Tests (179 total, +17 new):
  - LocalizationHelperTests covers SelectLocalized for all 6 languages,
    English fallback, null safety, schema pins on StatusName /
    StatusNameEnglish, plus a resolver-pattern Theory proving
    StatusNameEnglish stays English under Japanese/German/French/
    Chinese/Korean.
  - PartyMemberMapperTests updated to require DefaultStatusEffectOffset
    point at _status[0], not the StatusManager base.
  - FCSDependencyIntegrityTests pin StatusManager._status @ +0x8.

Harness [3c.1] block prints both StatusName and StatusNameEnglish for
the local player's currently-active statuses (filters expired Duration=0
slots and ??? names). Section header shows the configured GameLanguage
so the cross-language guarantee is visible per tick.

Also bundled in this PR: Sharlayan.LuminaLookup public class — name →
universal row id helpers across 10 sheets (Weather, Action, Status,
PlaceName, ContentFinderCondition, Item, BNpcName, ENpcResident, Mount,
Companion). One process-wide cached dict per sheet type, lazy-built
across all 4 client languages with case-insensitive matching. Per-
language failures (hash mismatch) silently degrade. 26 unit tests +
harness [5] smoke section.

Version: 9.0.26 → 9.0.29

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@logicallysynced logicallysynced changed the title chore: FFXIV Patch 5.5 "Trail to the Heavens" support chore: FFXIV Patch 5.5 "Trail to the Heavens" + LuminaLookup + status-resolver fixes + StatusNameEnglish May 4, 2026
Pulls 15 more FCS commits — "Update structs", AgentGoldSaucer.EditDeckIndex
additions, BonePhysicsModule / BoneSimulator expansion, several return-type
fixes (FormatUtf8String, RefreshDeckEdit), Haselnussbomber callbacks, and
formatting cleanups. All 179 integrity / mapper / lookup tests pass
unchanged — no further offset pin drift.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@logicallysynced logicallysynced merged commit 65386b1 into main May 8, 2026
3 checks passed
@logicallysynced logicallysynced deleted the sharlayan-9-rebuild branch May 8, 2026 09:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant