At a glance

Overlay
PROT 0972 at base 0x801CE818; venue scene bundle other1
Mode driver
FUN_801CF3BC, state word DAT_801D926C, jump table 0x801CEBE0
Lure / bite tick
FUN_801D26CC: band roll, strike roll, species pick, line-break and reel-in exits
Fish AI + tension
FUN_801D4004, gauge DAT_801D9168, 0..0x1000
Cadence recogniser
FUN_801D3DB4 over the templates at DAT_801D87D4
Tables
Species 0x801D81A4 (10 records, stride 0x28); spawn 0x801D8334 / 0x801D8434; prizes 0x801D8088 / 0x801D80D0
Saved state
0x8008444C..0x8008446C: points, best catch, lure, rod, cast counter, one-time prize bits
Port
Rules crate engine-fishing (PondSession); draws in engine-ui::fishing

Getting there

The door is the mode-24 minigame warp: field-VM op 0x3E with op0 = 100 (sub-id 0) sets game mode 0x18 and loads the overlay. It is the ordinary scene-backup, overlay-load, return-to-field sequence, so the game returns to the exact field state it suspended. The mechanism is on the field / event VM page.

A disc-wide walk finds the door at exactly two sites, both signboards on the overworld. Which one the party came from picks the venue: state 1 compares the departure scene’s id against two immediates and selects Buma or Vidna, which in turn selects the prize page and the spawn page. Both pond areas are in the bundle’s one map.

The warp is the last op of the signboard’s own talk record, which the action button runs like any town conversation. Before it, the board refuses on a frozen lake (Karisto only, system flag 0x378 clear), on no fishing rod and on no lure; then it asks whether to fish, and a yes fades the screen to white and waits 48 frames before the warp. The record is laid out on the world-map internals page.

Fishing loads no music of its own; it keeps whatever the departure scene was playing. The driver has no direct caller either - it is the tick word of an actor template the init spawns.

State machine

0x64.. Hub menu rod, lure, prizes 0xC8 Exit fade 0x0A / 0x0B init, fade in 0x0C Idle shore 0x0D Wind-up about 12 frames 0x14 Power meter bounces to 0x1000 0x15 In flight cast Triangle 0x19 Lure in water band roll, strike, fight 0x1E Rod recover fish on 0x20..0x22 Landing result plate, points accepted reeled in, no fish 0x28 Line break one lure consumed banner done
State values are sparse and many states simply add one to advance. The mode driver is half the machine: the lure tick runs from the actor side of the loop and writes the same state word, which is how the in-water state ends.

Reel input

InputRetail meaning
Crossreel A; also casts and locks the power meter
Squarereel B; also casts and locks the power meter
R2reel-direction mirror, read alongside the reel buttons
Circle, L2the abandon edge - not a cast
Triangle, Selectopen the hub menu from the idle shore

Holding both reel buttons resolves to reel A. The cadence recogniser keeps a 16-entry ring of {button, held frames} and matches a window of that history against the rhythm templates decoded from the disc. A match selects a cast band directly; with no match the band is the current frame’s random roll.

The port’s cast input differs from retail by choice: Circle casts and locks the meter, Cross and Square only reel.

Which fish bites

species = spawn_table[lure * 8 + band]     lure 0..2, band 0..4
Roll (12 bits)BandShare
r <= 0xC003about 75.0%
0xC00 < r <= 0xE702about 15.2%
0xE70 < r <= 0xF381about 4.9%
0xF38 < r0about 4.9%

No roll and no template maps to band 4. The rarest band has its own gate, which is what the Spirit Fish write-up follows. A matched template holds its band for the next 64 frame-steps.

The fight

0 0x1000 tension DAT_801D9168 reel held released reel A (Cross): + pull * step / (rod * 9 + 0x23) reel B (Square): + pull * step / (rod * 6 + 0x19) released: - (rod * 0x40 + 0x4A) * step at the ceiling: rand % 10 == 0 breaks the line
A better rod softens both directions: it raises the divisors and the release rate. Reel B pulls harder than reel A for the same fish.

While hooked, a small behaviour machine (run, dart left, dart right, dive) moves the fish and modulates its pull, re-rolling from the species record’s cutoffs when each behaviour’s timer runs out.

ConditionEffect
the line’s screen position leaves the play arealine-break latch
hooked and the fish has run far outtension rises by step * 300 on its own
tension at the ceilingone-in-ten roll per frame to break
latch set while the lure is in the waterline breaks: one lure is consumed
reeled all the way in with a fish onthe catch

Scoring and the prize counter

points = fish_base_value * (strength + 0x9C0) / 0x32000

The points are added to the saved counter, clamped to 999999, and a catch that beats the saved best replaces it. A per-catch latch scores a fish once.

The prize counter spends the pool on six rows per venue. A row is a 12-byte record with a limit - 1 for a one-time prize latched in a saved bitfield, 99 for a repeatable one. Row 0 is hidden until strictly affordable, so each venue’s top prize only appears once the pool exceeds its price.

See also

The species table column by column, the cadence templates, the fish-weight sum, the venue spawn tables, the line, rod and shore actors and the whole HUD are in docs/subsystems/minigame-fishing.md.