Arts command gauge - weapon-specialty arm width
When a character's turn opens the Arts command input, the battle UI draws an action gauge: a fixed AP pool the player spends by inputting directional commands (High / Low / the two arm swings). Each command costs AP, and the cost of the arm command depends on the equipped weapon's class versus the character's favored class. Equip off-class and the arm command costs more - the "longer arm input" players see. The Astral Sword costs the most of all.
Not a flat double
The mechanic is usually described as "an off-class weapon doubles the arm command." Byte-for-byte it is a base cost plus an escalating class penalty, not a flat ×2. Reading the arm command's cost for the same character with different weapons isolates it exactly:
| Equip | Class vs character | Arm (0x0C) cost |
|---|---|---|
| Gala + Ra-Seru Club | favored (club user, club weapon) | 0x1E (30) |
| Gala + Nail Glove | off-class (club user, claw weapon) | 0x2A (42) |
| Vahn + Astral Sword | always-double exception | 0x36 (54) |
The other commands (0x0D/0x0E/0x0F) hold a constant 0x1E. So: base 0x1E, +0x0C for an off-class weapon, +0x18 for the Astral Sword. The Astral penalty is twice the off-class penalty - the source of the "double" shorthand - but the off-class case itself is +0x0C over base (×1.4), not ×2.
Where the cost lives
The per-command cost is a runtime field, not a static table row:
DAT_801C9360- per-character command-data pointer table (one pointer per active party member), in battle bss. Each entry points into the loaded player battle data (thebattle_datablock, extraction 863..866).DAT_801C9360[char]- that character's array of per-command struct pointers, indexed by command code (cmd * 4).…[cmd] + 0x74- the arm width / AP cost byte. Full access:*(u8*)( *(u32*)( *(u32*)(DAT_801C9360 + char*4) + cmd*4 ) + 0x74 ).
The default 4-command display uses command codes DAT_801F4B8C = [0x0C 0x0F 0x0E 0x0D] (overlay 0898 rodata) with an icon-base sibling DAT_801F4B94 = [0x0D 0x10 0x11 0x0C]. Command 0x0C is the arm command whose +0x74 tracks the weapon; the others stay constant.
How the gauge consumes it
The gauge is assembled by FUN_801D388C (the battle action/animation event handler, driven by the battle main dispatcher FUN_801D0748). Its case 9/0x2C reads the cost; case 0xB spends it against the remaining AP at ctx + 0x6DC:
// case 9 / 0x2C (gauge build)
bVar3 = *(u8*)( *(u32*)(DAT_801C9360[char][cmd]) + 0x74 ); // arm width / AP cost
ctx[slot + 0x14] = bVar3; // per-slot AP cost
gauge_slot.icon_pos = bVar3 - 6; // visual width on the bar
// case 0xB (spend)
if (ctx[0x6DC] < ctx[slot + 0x14]) return; // not enough AP
ctx[0x6DC] -= ctx[slot + 0x14]; // consume the command
A higher +0x74 widens the gauge slot (bVar3 - 6) and drains more of the AP pool, so an off-class arm both looks wider and lets fewer total commands fit.
Weapon classes and favored mapping
"Off-class" is the equipped weapon's class versus the character's favored class. The class is legible from the static item-property records (DAT_80074368 + id*12): the description pointer (+8) is shared per class, and the description carries a Best:<character> token. Universal weapons (equip-mask 0b111) partition cleanly:
| Class | "Best" character | Example universal weapons |
|---|---|---|
| knife / sword | Vahn | Survival Knife, Battle Knife, Short Sword |
| claw | Noa | Nail Glove, Crimson Nails, Fighter Claw, Bloody Claw |
| club / axe | Gala | Survival Club, Red Club, Survival Axe, Battle Axe |
Character-specific weapons (Ra-Seru Blade / Fangs / Club, etc.) are locked to one owner by the equip-character mask (equip-record +6) and are always favored for that owner. The Astral Sword (0xBA) has its own description pointer, matches no character, and takes the maximum penalty. Favored mapping: knife/sword → Vahn, claw → Noa, club/axe → Gala.
Execution path
Once a combo is committed it is replayed by the Arms execution resolver FUN_801EC3E4 (overlay 0898), called from SCUS_942.54 at 0x800478A0 (jal 0x801EC3E4) - the execution driver is the static side, which is why the resolver has no caller inside the overlay. It advances the input cursor (actor + 0x1F4) one step per recorded command and dispatches per-command sub-handlers through PTR_801CF4B4[(actor + 0x1D9) - 0xC]. Those sub-handlers read the equipped weapon again (e.g. 0x801ECC00: weapon id → item subtype DAT_80074369 → equip record DAT_80074F68) to fold it into the damage / effect calculation - a read distinct from the gauge-build cost above.
Who writes the cost
The cost is not computed by a runtime favored-class comparison. It is written once at battle load (the game_mode 0x14 → 0x15 transition) as a verbatim copy out of the assembled battle-character buffer:
- The writer is
FUN_800557B8(the per-command-struct copy routine inSCUS_942.54): a fixed 43-word block copy from sourcea1to the runtime structa0(lw v0,(a1)→sw v0,(a0); the cost word at struct+0x74lands inside that block). There is no arithmetic on the cost value between load and store - confirmed by a live write-watch on the field through a field→battle transition, where the only write fires here atpc = 0x80055810. - It is called from the battle character-assembly chain (
FUN_80052770… the call site at0x80053330), which splices the equipped item's section into the per-character battle buffer.
So the arm cost originates in the equipped weapon's section of the per-character player battle file (extraction 863..866) and is carried verbatim into the runtime struct. The "off-class penalty" is therefore per-(character, weapon) data baked into those files - favored-class weapons carry a low arm cost in that character's file and off-class weapons a higher one - not a class comparison the engine performs. The same weapon yields different costs in different characters' files (a claw is cheap in Noa's file, expensive in Gala's).
Disc location
Inside the player battle file, the cost sits in the weapon's section, reached through the section's swing-action record:
section (decoded)
+0x04 u32 swing_rec_a ; offset (within the section) to the swing/arm command record
...
swing_rec_a + 0x74 ; u8 arm cost ← the weapon-specialty byte
The descriptor table keys sections by equippable item id, so each weapon has its own section and swing record. Decoding the three player files (asset battle-data-pack <file> --out) and reading section[+0x04] + 0x74 per weapon gives a byte-exact picture - favored-class weapons carry 0x1E (30), off-class weapons higher costs that scale with class distance:
| character (file) | favored → 0x1E | off-class → 0x2A | far off-class → 0x36 |
|---|---|---|---|
| Vahn (863) | blade / knife / sword / fist | claw, axe | - |
| Noa (864) | claw / feral / fang (+ knife) | sword / blade | club / axe |
| Gala (865) | club / axe / mace | claw, knife | - |
Cross-checked against live RAM: Gala + Nail Glove reads 0x2A, Gala + Ra-Seru Club reads 0x1E - matching that file's 0x28 and 0x21 sections. The cost lives inside the section's LZS-compressed stream, so an editor decompresses the section, rewrites the byte at swing_rec_a + 0x74, recompresses, and writes back within the slot footprint.
Confidence
- Confirmed (live-pinned + byte-validated against the disc) the cost field
DAT_801C9360[char][0x0C] + 0x74, its values, the case-9read / case-0xBspend inFUN_801D388C, the resolver call site, the writer (FUN_800557B8, verbatim copy from the LZS-decoded equipment section at battle load), and the disc location (section[+0x04]swing record+0x74in the player battle files, tabulated above). - Inferred that command
0x0Cis "the arm" (it is the only command whose cost tracks the weapon).
The mechanic is therefore a fully editable data table: rewrite a character's favored-class arm costs up / another class's down to reassign their specialty - which is exactly what the randomizer's --weapon-specialty does, permuting the three favored families among the characters by rewriting these bytes in place.