TL;DR
The hooked species is a table lookup: lure row × “band” column. The band roll only ever outputs 0–3; band 4 - the Spirit column - is written by a single store behind a strike-time gate: over 50 lifetime casts (a saved counter), the counter even at that instant, Normal Lure, the third rod, band 0 active, and then a 1-in-16 roll. The “Good!” splash reports a match against any of four reel-cadence templates, and the matched template's id becomes the band - so only one cadence (the one ending in a Square tap) selects the rare band, while the most natural rhythm selects the most common one.

The spawn tables

The hooked species is assigned in the pre-hook half of the hooked-fish handler FUN_801d26cc: species = spawn_table[lure*8 + band]. The equipped lure (_DAT_80084450) picks the row; the cast band (DAT_801d90e8, 0–4) picks the column. Decoded from the disc (rodata 0x801D8334), the species agree cell-for-cell with the published community tables:

Buma / lureband 3 (~75.0%)band 2 (~15.2%)band 1 (~4.9%)band 0 (~4.9%)band 4 (gated)
LightHigh ChargerHigh ChargerJackJackSpikefish†
NormalHigh ChargerJackHigh ChargerHigh ChargerSpirit
HeavyChargerFly PieLippianDevourerSpikefish†
Vidna / lureband 3band 2band 1band 0band 4 (gated)
LightJackJackFly PieJackSpikefish†
NormalHigh ChargerFly PieSpikefishFly PieSpikefish†
HeavyFly PieFly PieChargerOctobanBaraba

† Dead cells: each venue's band-4 gate hardwires the lure it fires on (Normal at Buma, Heavy at Vidna), so band 4 is only ever read on the row carrying the rare fish. The Spikefish entries in the other rows are unreachable in retail play - the only code path that could touch them is a developer shortcut (debug print flag + R1 held at the strike forces band 4 unconditionally), which retail can't enable.

The selection pipeline

Band assignment happens in two phases. While the lure is in the water, the band is re-derived every frame - a cadence match pins it for a ~64-frame hold window (and boosts the bite roll for the same window); otherwise the frame's random roll replaces it. At the instant a fish strikes, a venue-specific gate may upgrade band 0 to band 4; the species lookup then reads whatever band is current. Everything downstream of the strike is deterministic.

Band selection pipeline: cadence match or random roll sets band 0 to 3 while waiting; at the strike the gate may upgrade band 0 to band 4; the species lookup reads the result while waiting (line record > 500, nothing hooked) cadence match FUN_801d3db4 band = template id, held ~64 frames roll rand & 0xfff re-rolled per frame: 75 / 15 / 5 / 5 % cast band DAT_801d90e8 strike band-4 gate band 0 → 4 if all pass species = row[lure] col[band] conditions (Buma): casts > 50 and even · Normal Lure · third rod · band 0 · then 1/16 no gate pass → the band is used as-is; no other write reaches band 4
The two phases of band assignment. The gate is evaluated once, inside the strike branch of FUN_801d26cc, immediately before the species lookup.

The band roll

While the lure sits deep enough (line record over 500 units) and no fish is hooked, the band re-rolls on every frame no cadence holds it: rand() & 0xfff against three fixed cutoffs. The published 184/48/16/8-out-of-256 estimate has the right shape; the actual cutoffs sit slightly lower at the rare end. No outcome maps to band 4 - there is no fifth cutoff.

Band roll distribution: actual cutoffs from the disassembly versus the community estimate; no segment exists for band 4 actual roll - rand & 0xfff vs cutoffs 0xc00 / 0xe70 / 0xf38 (out of 4096) band 3 · 3073/4096 · 75.0% 15.2% 4.9% 4.9% published community estimate (out of 256) band 3 · 184/256 · 71.9% 18.8% 6.3% 3.1% band 4 has no segment in either bar: no roll outcome maps to it. band 0 (bright) is the rare band the gate requires - ~1 roll in 20.
Whatever band is active at the instant the fish strikes is the one the species lookup uses.

The reel-cadence recogniser

Before each frame's band roll, the game consults a reel-cadence recogniser (FUN_801d3db4): a 16-slot ring buffer of {button, held-frames} reel history, walked backwards against four rodata templates (0x801D87D4), each hold matched with ±10 frames of tolerance. A match does three things: it fires the splash rendered on a successful bait (“Good!”), it stores the matched template's id as the band, and it arms the check countdown to 0x40 - for the next ~64 frames the band holds instead of re-rolling, and because that same countdown feeds the strike credit (credit = countdown + 2), the bite roll runs at roughly twenty times its idle rate for the duration. A strike can only land on a frame where a reel button is held, so the final press of the cadence doubles as the bite window.

Performing the band-0 cadence

Setup: cast deep - the cadence check and the band itself only run once the line record exceeds 500, i.e. a hard cast that sinks well - then press either reel button briefly and release, so the first rest below starts from a measurable point. (When repeating the loop, the previous hold already serves as that marker.) Then, matching the numbered steps in the figure:

  1. Hands off - no buttons - for about two-thirds of a second (0.52–0.82 s accepted).
  2. Hold ✕ for about 0.4 s (0.27–0.57 s), then release.
  3. Hands off again for about two-thirds of a second.
  4. Press and hold □. The “Good!” splash fires about a tenth of a second into the hold: band 0 is set. Keep holding - the boosted bite lands only while a reel button is down, and it is most likely in the first second after the splash. If nothing bites, release and repeat from step 1.
Timeline of the band-0 cadence in seconds: rest two-thirds of a second, hold Cross 0.4 seconds, rest two-thirds of a second, then press and hold Square; the splash fires a tenth of a second into the Square hold any brief press first 1 hands off 2 hold ✕ 3 hands off 4 hold □ rest ≈ ⅔ s ✕ ≈ 0.4 s rest ≈ ⅔ s □ hold… accepted 0.52 – 0.82 s accepted 0.27 – 0.57 s accepted 0.52 – 0.82 s splash ≈ 0.1 s in “Good!” · band 0 set · bite boosted ~1 s 0 s 0.5 s 1.0 s 1.5 s 2.0 s Times assume 60 Hz. Each hold is matched with ±10 frames (≈ ⅛ s) of slack - rhythm, not frame-precision. No bite can land during the rests (a strike requires a reel button held), so the quiet phases cost nothing.
The band-0 cadence on a real-time axis. The whole figure spans ~2 seconds; the final □ press is held past the splash so the boosted bite window - which requires a reel button down - stays open.

The hold window is the load-bearing part. An unmatched band is re-rolled every frame, so a rolled band 0 exists only on the frames the dice happen to produce it; a cadence-set band is pinned for the full window, and the boosted strike usually lands inside it. The four templates compared:

The four reel-cadence templates as timelines; template 0 ends in a Square tap and selects band 0, template 3 is the natural Cross rhythm and selects the abundant band 3 template 0 → band 0 rest 40f ✕ 25f rest 40f □ 15f selects the rare band template 1 → band 1 □ 15f ✕ 25f rel template 2 → band 2 □ 15f rest 40f □ 15f template 3 → band 3 ✕ 25f rest 40f ✕ 25f matches the natural reel rhythm Durations in frame-steps (≈ frames at 60 Hz), each matched ±10. ✕ = reel A (Cross, pad bit 0x40) · □ = reel B (Square, 0x80). All four templates fire the same “Good!” splash - the splash reports a match, not which band was set.
Matching template 0 sets band 0 deterministically, replacing the ~4.9% roll. Rhythmic ✕ reeling matches template 3 and sets the most common band - with the same splash. Template 0 is the only cadence ending in a Square tap, which accounts for the reported association between Square presses and Spirit catches.

The band-4 gate at the strike

When a strike lands (reel held, per-frame strike roll passed, nothing hooked yet), one branch in FUN_801d26cc - executed immediately before the species lookup - decides whether an active band 0 upgrades to band 4. Every condition must hold simultaneously:

if (venue == Buma) {                          // DAT_801d90d0 == 0
    if (0x32 < casts                          // > 50 lifetime casts  (_DAT_80084460)
        && (casts & 1) == 0                   // cast counter EVEN at this instant
        && lure == 1                          // Normal Lure          (_DAT_80084450)
        && rod  == 2                          // third rod            (_DAT_80084454)
        && band == 0)                         // rare band active     (DAT_801d90e8)
        if ((rand() & 0xf) == 0)              // ... then 1 in 16
            band = 4;                         // -> Spirit
} else {                                      // Vidna
    if ((casts & 1) == 0 && lure == 2 && rod == 2 && band == 0)
        if ((rand() & 3) == 0)                // 1 in 4, no cast-count threshold
            band = 4;                         // -> Baraba
}

Three of these conditions are not externally observable, which is why no published table carries them. The cast counter (_DAT_80084460) lives in the save block: it increments once per cast at the moment the flown lure lands, persists across sessions, and is saved with the game - band 4 at Buma is unreachable inside the first 51 lifetime casts. The counter's parity makes alternating casts ineligible regardless of play. The rod condition means that with either other rod equipped, the gate short-circuits and band 4 cannot occur. The parity has exactly one visible correlate: it selects the lure's drift direction on wall contact.

Effective probabilities

With the gate's structure known, the per-catch Spirit probability at Buma follows directly. All rows assume the venue's other preconditions (deep cast, strike landed):

SetupPer-catch probabilityDerivation
Third rod, Normal Lure, 50+ lifetime casts, template-0 cadence held at the strike≈ 1/321/16 gate roll × ½ counter parity; the cadence pins band 0 and boosts the bite roll ~20× for its ~1 s hold window, so the strike normally lands inside it
Third rod, Normal Lure, 50+ lifetime casts, no cadence≈ 1/660199/4096 band-0 roll × ½ parity × 1/16 gate roll
Either other rod, or under 51 lifetime casts0The gate's conjunction fails before the random roll

These figures reconcile the published model with the observed rates. Under a flat 1/32 per catch, 250 catches without a Spirit has probability ~0.04%; under the gated model without cadence input, the same drought has probability ~68% - long empty sessions are the expected outcome, not an anomaly. The catches that did occur correspond to sessions in which every condition held, including a reel rhythm ending in a Square tap. Baraba at Vidna carries no cast-count threshold and a 1-in-4 gate roll, consistent with its far higher observed rate.

Passing the gate selects the species; landing it is a separate problem. The Spirit record carries the species table's largest pull factor - the strongest tension fight in the minigame - so a gated strike can still be lost in the reel.

Player reports, mapped to mechanisms

Long-standing player observations about Spirit fishing each correspond to a specific mechanism above:

Reported behaviourMechanism
Square presses associated with Spirit catchesTemplate 0 - the only cadence that sets band 0 - is the only template ending in a Square tap
Pond position affects resultsCells flagged 0x4000 in the floor grid raise the strike rate, increasing how often the gate is evaluated (the species table is unaffected)
Cast distance mattersBelow 200 line units no strike occurs; below 500 the band never re-rolls and the cadence check never runs
“Good!” improves the oddsAny match boosts the bite rate ~20× for ~1 s, but only template 0 sets band 0; the other three set bands 1–3 and fire the same splash
1/32 with a “Good!” on Normal LureMatches the effective rate when the rod, cast-counter, and cadence conditions all hold; the additional conditions are not externally observable
Long droughts despite correct lureExpected under the gated model - and certain with either other rod or under 51 lifetime casts

Provenance

Every mechanic above is read from the MIPS disassembly of the fishing overlay (PROT entry 0972, load base 0x801CE818) and verified against a retail USA disc image: the strike branch and band-4 gate in FUN_801d26cc, the cadence recogniser FUN_801d3db4, the reel-button decode FUN_801d7450, the species table at 0x801D81A4, the venue spawn tables at 0x801D8334 / 0x801D8434, and the gesture templates at 0x801D87D4. The full subsystem reference - state machine, tension fight, scoring, prize exchange - is minigame-fishing.md; the spawn-table and species parsers live in legaia_asset::fishing_species. No Sony-owned bytes appear here - only addresses, offsets, and decoded parameters.

See also