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_game_tables::item_effect::ItemEffectTable(from_scus,heal_amounts,restore_amount,stat_effect)- Confidence
- Confirmed - Ghidra-traced readers; the parsed bytes cross-check against each item's on-disc description string
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 id → subtype
item_name_table[id].byte(+1)(item table) - subtype → descriptor
DAT_800752C0[subtype * 4]- class, tier, flags, passive index - descriptor → amountHP / MP classes read the tier tables; stat classes use handler immediates
Record layout (4 bytes, 130 records)
| Field | Meaning |
|---|---|
| class | effect class - the action-validator arm (table below) |
| tier | per-class sub-case; heal-HP 0/1/2 = 200 / 800 / full, stat-up = which stat, arts book = the art id granted |
| flags | populated / info-panel / all-party / battle-usable / field-usable bits (below) |
| 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 | target side is the enemy party (clear = the ally party); paired with 0x20 it selects one of four target modes - set on exactly five subtypes, the Point Card and two pairs for the summon flutes. Three consumables select them - the Point Card, the Lippian Flute (subtype 0x27, 0xC4) and the Spikefish Flute (subtype 0x28, 0xE4); the 0xC6 / 0xE6 rows (subtypes 0x23, 0x24) are selected by no consumable (see shop) |
0x20 | all targets on the selected side (clear = one) |
0x04 | usable from the battle item menu |
0x02 | usable from the field item menu |
The 0x40/0x20 pair is read in exactly one place: the battle Item command in FUN_801D0748 (PROT 0898) loads the flag byte at 0x801D18D8 and forks on andi 0x40 / andi 0x20 at 0x801D18E0/0x801D18E8 into phase 0x5B (one enemy), 0x5D (all enemies), 0x64 (one ally) or 0x66 (all allies). The magic arm runs the identical ladder on the spell table’s +2 byte at 0x801D1C50, and the two tables’ low nibbles agree - an item’s 0xC4 is the spell table’s “one enemy” 0x44 with the 0x80 bit added. Sweeping every image for a +2 load followed by andi 0x40 finds those two sites and nothing else; the item-info panel FUN_801D0F1C contains no andi 0x40 at all, and its slti 0x40 tests the record’s +3 passive index against the no-passive sentinel, a different field.
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 = the art id granted) | Fire Book I |
14 | Point Card strike - reached by item 0xFE through an id test in the battle action state machine, not through a descriptor | Point Card |
126/127 | summon flute | Lippian Flute, Spikefish Flute |
128 / 129 | field escape (dungeon) / field warp (city) | Door of Light / Door of Wind |
130 | reduce encounter rate | Incense |
Arts books: the tier is an art id
Classes 11/12/13 write the tier byte verbatim into a per-character learned-art list (kept sorted by id), so the tier is the art the book grants, not a book level. The character comes from the class alone - class - 11 is the roster slot (0 / 1 / 2) - and the target the menu highlights is never read, so a Fire Book always lands on the same roster slot. The field VM's grant arm 4C C7 calls the same applier with the slot plus 0xB. On disc the nine books carry tiers Fire 3/2/1, Wind 5/4/1, Thunder 3/2/1 for I/II/III, all with flag 0x83 (field use only).
Class 14: the Point Card strike, reached by item id
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, but the arm is live: the battle action state machine tests for item 0xFE at 0x801E4080 and forces the selector to 0x0E at 0x801E40C0, so the Point Card itself runs it.
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.
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) |
The handler writes the base window (+0x11C..+0x12C) and nothing else. The live window follows because the menu's use routine (FUN_801D8308) calls the stat aggregator straight after it. The aggregator copies each base value over its live one, adds the equipment's percent boosts off the base (ability bit 0 a tenth of base Max HP, bit 1 a quarter, bits 2 and 3 the same two for Max MP), clamps to 9999 / 999, and lowers a current value that now exceeds its maximum. So a raised maximum is the base plus whatever boost is worn, and the gain is not refilled: current HP / MP stay where they were.
Port: World::apply_stat_up_item raises the base window and World::rebuild_record_live_hp_mp rebuilds the two maxima from it - the same rebuild World::refresh_party_ability_bits runs for every party member, so an equipment change moves the maxima with the accessory. The combat stats take the base raise and the same increment on the live value, which the battle seed re-derives from the base.
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 + INT (+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
The readers behind each field
| 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 |
| Arts-book arm | 0x80041FC0 (class - 0xB), 0x80042064 (sb tier at +0x74E) | Tier = art id; character = class-derived roster slot; picked target unused. | funcs/800402f4.txt |
In the port
legaia_game_tables::item_effect::ItemEffectTable::from_scus resolves both tables from a SCUS_942.54 image; engine-menus::items::ItemCatalog installs the usability and all-party flags and seeds the stat-up, buff and Fury Boost items from it, and engine-menus::inventory_use mirrors the retail menu-usability gate (a heal greys out when nobody would benefit). Source of record: docs/formats/item-effect-table.md.