chore: FFXIV Patch 5.5 "Trail to the Heavens" + LuminaLookup + status-resolver fixes + StatusNameEnglish#106
Merged
Merged
Conversation
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>
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
4ad7d0d726ea6cd2d1(~50 commits —Update structs,Update UIModule,Update UIState,Update PlayerState,Update signatures, AddonLookingForGroup rename, CastInfo bitfield migration,Update EventFramework, etc.)Reader.GameState.EnsureLuminaSheets)Layout shifts auto-tracked at runtime; only test pins needed bumping
UIModule.RaptureLogModule0x19E0 → 0x1AC0UIModule.RaptureHotbarModule0x57B80 → 0x57C60PlayerStatesize0x908 → 0x920(harness pin)Vector3size0xC → 0x10(FCS gave it SIMD alignment; field offsets X/Y/Z @ 0/4/8 unchanged)CastInfo bitfield migration absorbed
FCS migrated
CastInfo.IsCastingandCastInfo.Interruptiblefrom standalone byte fields to[BitField<bool>]accessors over a singleFlagsbyte, AND made the struct non-marshalable.ActorItemMapperswitched fromMarshal.OffsetOf<CastInfo>toFieldOffsetReader.OffsetOf;IsCasting1/IsCasting2schema offsets now point atFlags;ActorItemResolverdoes the bit-0 mask. Downstreamentry.IsCasting1semantic preserved.New public API:
Sharlayan.LuminaLookupName → universal row id helpers across 10 sheets — inverse of
Reader.Lumina's id-to-name helpers.WeatherIdFromNameNameActionIdFromNameNameStatusIdFromNameNamePlaceNameIdFromNameNameContentFinderConditionIdFromNameNameItemIdFromNameNameBNpcNameIdFromNameSingularENpcResidentIdFromNameSingularMountIdFromNameSingularCompanionIdFromNameSingularFindRowId<T>(config, name, selector)One process-wide
Dictionary<string, uint>per sheet type, lazy-built across all 4 client languages and unioned withOrdinalIgnoreCase. 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:
StatusNameEnglishNew
StatusItem.StatusNameEnglishproperty — always English regardless ofConfiguration.GameLanguage. ExistingStatusNameremains 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
StatusNamewas the only resolver field switching onGameLanguage. 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.SelectLocalizedcall.Resolver bug fixes
DefaultStatusEffectOffsetwas reading garbageActorItemMapperleftDefaultStatusEffectOffsetat the default0, so the resolver'sBlockCopywas reading status entries from the start of the Character struct — pure garbage IDs and stale slots. Same bug inPartyMemberMapper(pointed at theStatusManagerstruct base instead of_status[0], off by 8 bytes for theOwnerpointer prefix). Both now resolveStatusManager._statusviaFieldOffsetReader. Pinned via new integrity test.StacksgatingFCS
Status.Paramdocuments itself as "stack count for debuffs; food/potion id otherwise; meaningless for ordinary buffs". The mapper read it asStacksfor every status, surfacing arbitrary Param bytes as fake stack counts (e.g.Reduced Rates Stacks=30). Both resolvers now zeroStackswhenstatusInfo.MaxStacks <= 1, sourced from a newMaxStacksproperty onModels.XIVDatabase.StatusItempopulated from Lumina's Status sheet.Resilience
Reader.GameState.EnsureLuminaSheetsresolvesWeather/BGM/BGMFadeType/BGMFadein independent try/catches — a hash mismatch on one sheet no longer voids the others.[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)LuminaLookupsmoke test (harness[5]) — Weather / Action / Status / PlaceName / ContentFinderCondition / Item / BNpcName / ENpcResident / Mount / Companion lookups working with English + Japanese inputs🤖 Generated with Claude Code