Inn
Pay the innkeeper, the screen fades, and the party wakes with full HP and MP. Retail Legaia has no inn system behind that: no dedicated code, no price table, no "this is an inn" flag. Every inn is an ordinary conversation in its town's script, and the price is a number typed into that script. That is what makes the stay portable and editable - and what makes finding a price a byte scan rather than a table lookup. Fully ported
At a glance
- Where
- Inside each scene's MAN (the scene's script-and-data bundle, asset type
0x03) - the innkeeper's own dialogue record. - Runs on
- The field / event script VM, field overlay PROT
0897. No inn overlay, no menu sub-screen. - Price
- A u16 literal in a gold-compare instruction, repeated as the magnitude of a debit a few bytes later.
- Restore
- One heal instruction per party slot (
4C 82 <slot>) - the only inn-specific opcode in the game. - Parser
legaia_asset::inn_costs(price scan); engine pathengine-core::inline_dialogueoverengine-vm::field.- Confidence
- Confirmed - disassembly + disc sweep + disc-gated engine test (how we know).
One stay, six script steps
Every step is a plain script instruction, so two inns in two towns differ only in the price literal. The No answer and the can't-afford skip both land on refusal lines inside the same record; a stay never leaves the script VM.
The trigger is the picker's jump
Nothing marks a dialogue as an inn. The hand-off from the offer to the restore is the option picker's per-option relative jump: the record is laid out so that answering Yes lands on the gold gate. Two details of that layout trip up a port - either alone makes every inn unreachable.
| Detail | What it is | What goes wrong if misread |
|---|---|---|
Menu open byte 0x2A | A 2-option picker that animates the box first and clamps (does not wrap) the cursor. Handled by the same arm as 0x27..0x29. | A decoder that stops at 0x29 finds no inn menu - and misses a large share of the game's other menus. |
A2 F8 <clip> / AC F8 08 / AD F8 08 | "Play a clip on the player, clear the end latch, spin until it re-latches." Bit 8 of the actor's clip-control word is the end flag; F8 retargets the player. | Treating the spin as "conversation over" stops one instruction into the Yes branch, before the gate. |
In the port, World::trigger_field_interact steps the innkeeper's record through the field VM; the gate reads the live purse, the debit is the record's own ADD_MONEY, and the restore is its own 4C 82 ops. The runner parks on the clip spin until the player's clip cursor latches the end by itself.
Record layout, as read off the retock innkeeper
1F <greeting> 00 1F <price line> 00 1F <offer question> 00
2A <rel16 yes> <rel16 no> 24 ; 2-option picker + post-page continue
1F <"yes" label> 00 1F <"no" label> 00
; yes-jump target:
A2 F8 <clip> AC F8 08 AD F8 08 ; play a player clip, clear its end latch, spin
... 26 <rel16> ; -> the gate
4E <pp> 30 <cost u16> <skip u16> ; if gold < cost, skip to the refusal
3A <sext24(-cost)> ; take the money
1F <thank-you> 00 1F <good night> 00
35 / 34 / 4A / 36 ; BGM release, fade, waits, cue
4C 82 00 4C 82 01 4C 82 02 ; the restore, one op per party slot
The full Yes gesture is the door-swing shape aimed at the player: un-hold, poke the clip, clear + spin, un-clamp, wait, re-clamp, clear + spin again, then hand the player back to the locomotion move. Every operand is an animation bit of the player's +0x62 word, not a local flag of the record.
Where the price lives
The price is a gold-gate + debit pair: op 0x4E sub-op 3 compares the party's gold against a u16 literal and skips when gold is short; op 0x3A (ADD_MONEY) then applies the negative of that literal. A 32-bit sibling (sub-op 10) exists for prices above 65,535 and is used only by the casino's gold-to-coin counters.
legaia_asset::inn_costs finds the pair by byte scan - a literal in a gold compare that reappears as a debit within a few instructions - which survives the picker jump tables that desync a linear walk. The engine runs that scan at scene load. Free rests (Rim Elm's bed, Biron) have no pair at all.
| Scene family | Charge | Form |
|---|---|---|
ropeway, balden, rayman2 inns | 200 G | sub-3, u16 |
retock inn | 240 G | sub-3, u16 |
station3 train ticket, paid tours | up to 3,000 G | sub-3, u16 |
koin* casino counters | 8,500..90,000 G | koin4 carries the only sub-10 u32 sites |
The engine's spare prompt
The port also carries InnSession (engine-core::inn): a menu-style cost window with InnConfirm / InnSleep sub-screens and its own commit kernel. Nothing on the playable path opens it - retail has no such screen, so wiring it in would replace the innkeeper's real dialogue with an invented panel. It remains a direct entry for tests and tooling; the DREAM warp some inns append after the restore is not reached by the port yet.
How we know
| Function / data | Address | What it proves | Dump |
|---|---|---|---|
Field VM op 0x4E inner jump table | 0x801CEE30 (12 entries) | Sub-3 arm at 0x801E0AEC loads party gold _DAT_8008459C; sub-9 at 0x801E0B34 loads the casino coin bank _DAT_800845A4. Read from the disassembly - the decompiled C collapses these arms. | overlay_0897_801de840.txt |
| Option-jump handler | FUN_80038050 | 0x27..0x2A share one arm: new_pc = O + 1 + index*2 + i16(entry[index]). | 80038050.txt |
| Picker cursor handler | 0x801D941C | Option count comes off the active state (0x18 = 4, 0x16 = 3, else 2); 0x2A enters state 0x11 and clamps. | field overlay dump |
| Actor tick | FUN_800204F8 | Latches bit 8 of actor+0x62 when a clip ends - the bit the AD F8 08 spin waits on. | 800204f8.txt |
| Cross-context id resolver | FUN_8003C83C | Id F8 short-circuits to the live player object _DAT_8007C364. | 8003c83c.txt |
| Disc sweep | crates/asset/tests/inn_costs_disc.rs | The gate + debit pair resolves in every inn / paid-lodging scene and nowhere a rest is free. | - |
| Engine oracle | crates/engine-core/tests/inn_stay_field_vm_disc.rs | Drives the real retock record from the interact call; checks the gold delta and party pools on Yes, No and can't-afford. | - |
History: the cost table that does not exist
A per-scene cost table in the menu overlay's data segment was the natural first guess; the disc has none. Three port defects hid the real path at once: 0x2A decoded as nothing, the gate read an empty purse, and the clip spin was read as the end of the conversation. See settled threads.
Full provenance: docs/subsystems/inn.md. Source: crates/engine-core/src/inn.rs, crates/engine-core/src/inline_dialogue.rs.