Indexing (Ghidra-traced)

(FUN_80xxxxxx / DAT_80xxxxxx are Ghidra names - the disassembler's labels for traced functions and fixed RAM addresses.) From the per-frame stat aggregator FUN_80042558 (static SCUS_942.54), which walks each active party member's eight equipment-slot bytes at char record +0x196:

kind = item_table[id].byte(+0)                  ; DAT_80074368[id*0xC]
if kind == 1: index = equip_bonus[sub].byte(+5) ; DAT_80074F6D[sub*8]
if kind == 2: index = descriptor[sub].byte(+3)  ; DAT_800752C3[sub*4]
if index < 0x40:
    char[+0xF4 + (index>>5)*4] |= 1 << (index & 0x1F)
  • char +0xF4..+0x103 is the per-character 4×u32 ability bitfield - the word the MP-cost ability bits 0x10/0x20 (indices 0x04/0x05) live in.
  • The aggregator then ORs all three members' bitfields into the global 4×u32 mask at DAT_80074358 (bit-tested by FUN_800431D0) - how party-wide passives are consumed.
  • Index ≥ 0x40 grants nothing. Retail data: every equip-bonus row carries +5 = 0x40 (no equipment grants a passive; the kind == 1 arm is latent) and every consumable descriptor carries +3 = 0x41. Only accessory and quest-item descriptor rows carry live indices.

The same gate (descriptor +3, < 0x40) is read by the menu overlay's Goods detail panel FUN_801D0F1C and the static description resolver FUN_80034250 to fetch the display text.

The battle damage finisher FUN_801DDB30 consumes the bitfield's first two words (+0xF4/+0xF8) directly as the party defender's elemental-resist / spirit words: its per-element ladder tests bit 0x1D + attacker_element (the contiguous elemental-guard band, so elements 0..=2 land in +0xF4 bits 29..31 and elements 3..=6 in +0xF8 bits 0..3), +0xF8 & 0x10 is the All-Guard (0x24) 3/4-scale gate, and +0xF8 & 0x100/0x200 (AP Boost 1/2, indices 0x28/0x29) accelerate the defender's spirit-gauge fill. Engine mirror: engine-vm::battle_formulas::DefenderResist.

Passive name/description table (0x8007625C)

A static 64-record table gives each index its menu name, effect description, and scope:

FieldValue
Record base0x8007625C (file offset 0x66A5C)
Stride0xC bytes
Record count0x40 (indices 0x00..=0x3F)
OffsetTypeField
+0u32scope: 1 = party-wide (one wearer benefits the whole party), 0 = wearer-only
+4u32pointer to the effect name string (e.g. "HP Boost 1")
+8u32pointer to the effect description string (0x7C '|' is the line break)

Party-wide scope is set on exactly indices 0x30..=0x37 and 0x3B..=0x3F (the battle-end / encounter / escape modifiers).

The 64-index space (bands)

Names/descriptions are the on-disc strings; the assignments are byte-verified against the curated gamedata accessory table (accessory_passives_vs_disc):

IndicesBandExamples
0x00..=0x0Cpercent stat boosts (HP/MP 10%/25%, ATK/DEF/SPD/INT/AGL 20%)Life Ring, Magic Armband, Power Ring
0x0D..=0x15combat behaviours (Attack ×2, Guard/Steal/Counter Attack, turn-order chains)War God Icon, Evil God Icon, Speed Chain
0x16..=0x1Cstatus-nullify guardsCure Amulet, Wonder Amulet
0x1D..=0x24elemental guards (Earth/Water/Fire/Wind/Thunder/Light/Dark + All)Earth Jewel, Rainbow Jewel
0x25..=0x2Fper-turn regen, AP modifiers, arts/magic/EXP boostsLife Grail, Mettle Ring, Crimson Book
0x30..=0x37party-wide battle-end / initiative / escape modifiersGolden Book, Chicken King
0x38..=0x3Awalk regen (HP/MP/AP)Life Source
0x3B..=0x3Fencounter modifiers + two display-text-only card slotsGood Luck Bell; Point/Platinum Card special-cased by id

Quest items share indices with their purchasable twins (Mei's Pendant = Life Ring, each Egg / Talisman = the matching Jewel). Indices 0x3E/0x3F have populated description records, but the Point Card / Platinum Card descriptor rows carry the 0x41 sentinel; the cards are special-cased by item id in the Goods panel.

Stat-boost magnitudes + Talisman spell grants

The percent boosts (0x00..=0x0C) are applied inline by the aggregator - there is no magnitude table. Per index: +10% = base/10, +25% = base>>2, +20% = base/5, added to the copied base, then capped (HP 9999, AGL 280, others 999). The remaining indices are point-of-use flags: each consumer tests the bitfield bit where the mechanic lives (MP cost at cast time, the steal table for Steal Attack, guards in the battle damage path, encounter rate in the field step roll, loot in the battle-end reward resolver FUN_8004E568). The two escape bits are consumer-pinned in the run roll FUN_801E791C: 0x34 Escape Boost (Chicken Heart) scales the party's escape roll x1.5 and 0x37 Great Escape (Chicken King) forces the roll compare to a tie - assured escape, still blocked by the scripted no-escape flag, hence "non-boss"; both fold only from living wearers (see battle-action).

A separate arm of FUN_80042558 watches equip slots for specific item ids (not passive indices) and grants/revokes battle spells: the five Talismans 0x72..=0x76 grant summon spells 0x9A/0x9B/0x9C/0x9D/0x99, and the top-tier Ra-Seru weapons 0x09/0x11/0x19 grant 0x9E/0x9F/0xA0. A Talisman is therefore "guard passive by index + summon by id".

Parser + engine consumption

legaia_asset::accessory_passive::AccessoryPassiveTable::from_scus resolves the property / descriptor / equip-bonus index bytes and the 0x8007625C records from a SCUS_942.54 image; stat_boosts(index) mirrors the aggregator arithmetic and bit_location(index) the bitfield placement. CLI: asset accessory-passive <SCUS> [--json]. The disc-gated accessory_passive_real test pins the per-item indices, table text, scope flags, and the retail equip +5 sentinel invariant. The engine consumes it via engine-core::accessory_passives + World::refresh_party_ability_bits (the aggregator port) and compute_battle_stats_with_passives (percent-of-base boosts with the retail clamp block).

Superseded readings

The descriptor +3 byte was previously documented as a "constant 0x41 ('A') marker" and the equip record's +5 byte as "constant 0x40". Both are in fact this index space's no-passive sentinels: 0x41 on every consumable row, 0x40 on every retail equipment row - only accessory / quest-item rows carry live indices.

See also