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 path engine-core::inline_dialogue over engine-vm::field.
Confidence
Confirmed - disassembly + disc sweep + disc-gated engine test (how we know).

One stay, six script steps

1Talkthe innkeeper greets, quotes the price, asks the questiontext pages 2Yes / Noa 2-option picker; the Yes entry jumps straight to the gold gateop 0x2A 3Gateif gold < price, skip to the can't-afford lineop 0x4E sub-3 4Debitsubtract the price from the purseop 0x3A 5Sleeprelease the music, fade, waitops 0x35 / 0x34 / 0x4A 6Restorerefill HP and MP, one op per party slot; some inns then warp to a dream scene4C 82 <slot>

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.

DetailWhat it isWhat goes wrong if misread
Menu open byte 0x2AA 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 familyChargeForm
ropeway, balden, rayman2 inns200 Gsub-3, u16
retock inn240 Gsub-3, u16
station3 train ticket, paid toursup to 3,000 Gsub-3, u16
koin* casino counters8,500..90,000 Gkoin4 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 / dataAddressWhat it provesDump
Field VM op 0x4E inner jump table0x801CEE30 (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 handlerFUN_800380500x27..0x2A share one arm: new_pc = O + 1 + index*2 + i16(entry[index]).80038050.txt
Picker cursor handler0x801D941COption count comes off the active state (0x18 = 4, 0x16 = 3, else 2); 0x2A enters state 0x11 and clamps.field overlay dump
Actor tickFUN_800204F8Latches bit 8 of actor+0x62 when a clip ends - the bit the AD F8 08 spin waits on.800204f8.txt
Cross-context id resolverFUN_8003C83CId F8 short-circuits to the live player object _DAT_8007C364.8003c83c.txt
Disc sweepcrates/asset/tests/inn_costs_disc.rsThe gate + debit pair resolves in every inn / paid-lodging scene and nowhere a rest is free.-
Engine oraclecrates/engine-core/tests/inn_stay_field_vm_disc.rsDrives 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.

See also