Item-effect descriptor table Confirmed
What does a Healing Leaf actually do, and why is a Door of Light greyed out in battle?
A small static table in SCUS_942.54 answers both: for each consumable it records the kind
of effect - heal HP, restore MP, cure status, revive, raise a stat, escape a dungeon - its target shape (one ally
or the whole party) and where it may be used (field menu, battle, both, neither). What it does not
hold is the number: "restores 200 HP" lives in a separate tier-indexed table.
At a glance
- In the game
- every item use from the field menu or the battle Item command; which items are greyed out where
- Magic / marker
- none - a fixed table in
SCUS_942.54 - Lives in
- SCUS VA
DAT_800752C0(file0x65AC0), 130 × 4-byte records, ending where the spell table begins; heal amounts at0x8007655C - Retail readers
- menu-usability gate, field list builder, apply handler (how we know)
- Parser
legaia_asset::item_effect::ItemEffectTable(from_scus,heal_amounts,restore_amount,stat_effect)- Confidence
- Confirmed - Ghidra-traced readers; disc-gated
item_effect_realpins the bytes and cross-checks each against its on-disc description
What it is and is not
This table holds class + tier + flags. "Healing Leaf restores 200 HP" is split in two: this table says (class = heal-HP, tier = 0), and the tier → 200 mapping is the separate, also-static heal-amount table. Stat-up and buff magnitudes (classes 5/6/7) are inline immediates in the apply handler.
It is the sibling of the item-name table and the spell table: the three are contiguous static data. The lookup is double-indirected - an item id resolves to a subtype byte in its name-table record, and the subtype picks a descriptor here - so many items share one descriptor, and "key items are unusable" is not a special case at all: a key item simply resolves to a descriptor with neither usability bit set.
item_name_table[id].byte(+1) - the subtypeitem_tableDAT_800752C0[subtype * 4] - class, tier, flags, passive indexitem_effectheal_amountsRecord layout (4 bytes, 130 records)
| Offset | Size | Field | Meaning |
|---|---|---|---|
+0 | 1 | class | effect class - the action-validator arm (table below) |
+1 | 1 | tier | per-class sub-case; heal-HP 0/1/2 = 200 / 800 / full, stat-up = which stat, book = level |
+2 | 1 | flags | populated / info-panel / all-party / battle-usable / field-usable bits (below) |
+3 | 1 | passive-effect index | 0x00..=0x3F for accessory / quest-item rows (the accessory-passive table); 0x41 = no-passive sentinel on consumable rows |
Flag byte (+2)
| Bit | Meaning |
|---|---|
0x80 | populated - set on every descriptor that has an effect |
0x40 | draws its own info-panel body instead of the passive block - exactly five subtypes: the Point Card and the two summon-flute pairs (the Point Card arm prints the live points bank; see shop) |
0x20 | whole-party target (all-targets validator) |
0x04 | usable from the battle item menu |
0x02 | usable from the field item menu |
Healers carry 0x04 | 0x02; permanent stat-ups and field-utility items carry 0x02 only; status-cures and revive carry 0x04 only. Key items resolve to a descriptor with neither usability bit (e.g. flag 0x89), which is how the menu greys them out. The class byte is meaningful only together with the usability flags - many key items funnel to class 0 with no usability bit, so "class 0" alone is not "an HP potion".
Class byte (+0)
Class labels are validated against the on-disc item description strings:
| Class | Effect | Example item |
|---|---|---|
0 / 1 | heal HP, one ally / whole party | Healing Leaf / Healing Bloom |
2 | restore MP | Magic Leaf |
3 / 8 | cure all status / cure single status | Medicine / Antidote |
4 | revive | Phoenix |
5 | extend action gauge (one battle) | Fury Boost |
6 | permanent stat-up (tier = which stat) | Miracle Water, the Water line |
7 | temporary stat buff (one battle, ×6/5) | Power Elixir |
11/12/13 | arts book (Fire/Wind/Thunder; tier = book level) | Fire Book I |
14 | Point Card strike - reachable code, no item on the disc reaches it | - |
126/127 | summon flute | Lippian Flute |
128 / 129 | field escape (dungeon) / field warp (city) | Door of Light / Door of Wind |
130 | reduce encounter rate | Incense |
Class 14: the Point Card strike nothing sells
The class-14 arm of the apply handler takes min(points_bank, 9999) off the Point Card bank and applies it as battle damage - a strike scaling with how much the player has shopped. Decoding all 256 item records finds no item carrying class 14, so retail never opens the arm; the LEGAIA_POINT_CARD_MAX probe knob reaches it only by forcing the bank. Unused content, not a missing item.
Heal-amount table
The literal restore amounts the apply handler reads: two contiguous u16[4] sub-tables indexed by the descriptor tier (base + tier*2); only tiers 0..=2 are read flat.
| VA | Sub-table | Tier 0 | Tier 1 | Tier 2 | Read by |
|---|---|---|---|---|---|
0x8007655C (file 0x66D5C) | HP restore cap | 200 | 800 | 9999 | classes 0 / 1 |
0x80076564 | MP restore cap | 50 | 200 | 20 | class 2 |
- Each restore is deficit-clamped (
applied = min(max - current, table[tier])), so tier 2's9999is an effective full restore. - Tier
3+HP heals do not read this table - they are character-relative, scaled off the per-character Seru-heal tables. - Revive (class
4) is not a flat amount: tier 0 restoresmax_hp×0.4 + rand()%(max_hp/8), higher tiers a full revive.
History: "the amounts are overlay-resident immediates"
An earlier reading placed the restore amounts in a switch of immediates inside an overlay-resident handler outside the dumped corpus. The HP / MP amounts are static tier tables read by the static apply handler; only the class 5/6/7 magnitudes are immediates. Recorded on settled threads.
Stat-up / buff items (classes 5/6/7)
These classes read no data table - their magnitudes are inline immediates in the apply handler's case 5/6/7, so the (class, tier) → effect mapping is pinned to the disassembly while each item's (class, tier) is parsed from the descriptor. In brief: the Water line adds a flat permanent increment to one stat (tier selects which), the Elixirs multiply a battle-actor stat by 6/5 for the rest of the battle, and Fury Boost widens the action gauge. Parser: stat_effect(id) → ActionGauge / Permanent / BuffOneBattle.
Full magnitude tables (Waters, Elixirs, Fury Boost)
Class 6 - permanent stat-up (field-use, flag 0x82)
Adds a flat increment to the character record, tier selecting the stat:
| Tier | Stat | Record offset | Delta | Cap | Item |
|---|---|---|---|---|---|
| 0 | Max HP | +0x11C | +16 | 9999 | Life Water (0x82) |
| 1 | ATK | +0x124 | +4 | 999 | Power Water (0x83) |
| 2 | DEF (both facets) | +0x126 / +0x128 | +4 | 999 | Guardian Water (0x84) |
| 3 | SPD | +0x12A | +4 | 999 | Swift Water (0x85) |
| 4 | INT | +0x12C | +4 | 999 | Wisdom Water (0x86) |
| 5 | Max MP | +0x11E | +8 | 999 | Magic Water (0x87) |
| 6 | all (AGL/HP/ATK/SPD/INT/MP/DEF) | - | +4 each | AGL 280, HP 9999, else 999 | Honey (0x65), Miracle Water (0x6D) |
Class 7 - one-battle buff (battle-use, flag 0x84)
Multiplies the battle-actor stat halfwords by 6/5 (the handler's uVar + uVar/5), clamped to 0xFFFF, for the rest of the battle:
| Tier | Stat(s) | Item |
|---|---|---|
| 1 | SPD (+0x164/+0x166) | Speed Elixir (0x8D) |
| 2 | DEF, both facets (+0x15C/+0x15E, +0x160/+0x162) | Shield Elixir (0x8C) |
| 3 | ATK (+0x158/+0x15A) | Power Elixir (0x8B) |
| 4 | all - SPD + DEF + ATK + AGL (+0x168/+0x16A) | Wonder Elixir (0x8E) |
Class 5 - Fury Boost (0x81) sets the actor +0x1F9 gauge flag; the action-SM gauge-build phase then sizes the gauge as gauge_stat * 7 / 5 + 8 (clamped to 0x120). See the buff-selector table on the battle-formulas page.
How we know
| Function | Address | What it proves | Dump |
|---|---|---|---|
| Menu-usability gate | FUN_8003043C | The subtype → descriptor index chain; the & 0x20 all-party test selecting the all-targets validator call; an item is offered only when at least one eligible target would benefit (walks the live party calling FUN_8003fb10(class, tier, target) per member). | funcs/8003043c.txt |
| Field list builder | FUN_80030628 | Reads the 0x02 / 0x04 usability bits - the grey-out logic. | funcs/80030628.txt |
| Apply handler | FUN_800402F4; HP arm 0x800404B8, MP arm 0x80040DC0, revive arm 0x80040F58 | The tier-table reads, deficit clamp, revive formula, and the class 5/6/7 immediates. One handler serves field menu and battle (branching on game_mode == 0x15). | funcs/800402f4.txt |
| Jump-table callers | table at 0x80014FA0 (132 entries); callers 0x801D84B4, 0x801D9280, 0x801E40DC | What indexes the handler depends on the caller: descriptor class (field item use), the item record's own kind byte, or the battle actor's +0x1E8. | overlay dumps |
| Target-panel preview | FUN_801D6A54 | Gates on kind == 2 and class == 6 before mapping tier to a preview mode - the sibling kind-byte selector. | funcs/overlay_menu_801d6a54.txt |
| Disc-gated test | item_effect_real | Pins Leaf 200, Flower 800, Berry full, Magic Leaf 50 MP, Magic Fruit 200 MP, Healing Shroom 200; cross-checks each class against its on-disc description string. | - |
Parser + engine consumption
ItemEffectTable::from_scus resolves both tables from a SCUS_942.54 image (PSX-EXE t_addr → file-offset map). The engine installs the flags over its curated catalog via ItemCatalog::apply_effect_flags (usability gates + is_all_party: Healing Bloom / Fruit skip target-select and fan across every living ally), seeds the Water line via apply_stat_items, the Elixirs via apply_buff_items, and Fury Boost via apply_action_gauge_items. The engine mirror of the usability gate (inventory_use::item_has_valid_target) greys a heal at full HP, a cure with no matching status, a revive with nobody fallen. Source of record: docs/formats/item-effect-table.md.