Data are taken from the Ocean Fishing Spreadsheet managed by Tyo’to Tayuun. To report errors, please visit the Fisherman’s Horizon Discord or message Lulu Pillow@Adamantoise or Pillowfication#0538.

Bite times are periodically fetched from Teamcraft. Teamcraft defines the minimum and maximum bite times to be the whiskers of a typical boxplot. This means

  1. Compute quartiles using the type-7 algorithm detailed in R’s quantile method.
  2. Removing samples more than from the median.

The default bite times shown on the Ocean Fishing page are the bite time ranges over all baits, excluding Versatile Lure. While this works fairly well, it can still be inaccurate, and most prescribed strategies will use player-tested timings.

Bait percentages are also fetched from Teamcraft and are usually used as the suggested bait, unless the bait causes a drastic increase in bite times. Usually the best bait is the fish’s desynthesis bait, and nonstandard ocean fishing baits are avoided, unless the goal is Fisher’s Intuition. Specific cases were further detailed in the Discord.

All my data and the code I used are available on GitHub.


Ocean Fishing voyages follow a specific pattern best seen using Japan Standard Time (JST). Voyages leave every 2 hours on odd hours (at 1:00, 3:00, …, 23:00). The destination always cycles between the 4 destinations in the following order:

The 4 destinations will all be set to arrive at Day, then repeated to arrive at Sunset, then repeated to arrive at Night. There are 12 routes that I label as

and they follow the sequence

However, the first voyage of every day (at 1:00 JST) will skip a route. So there might be the schedule

Since there are 12 routes and 12 voyages a day, the route that is skipped will cycle through all 12 routes in 12 days. The full pattern of routes is 144 routes long.

const PATTERN = [
  'BD', 'TD', 'ND', 'RD', 'BS', 'TS', 'NS', 'RS', 'BN', 'TN', 'NN', 'RN',
  'TD', 'ND', 'RD', 'BS', 'TS', 'NS', 'RS', 'BN', 'TN', 'NN', 'RN', 'BD',
  'ND', 'RD', 'BS', 'TS', 'NS', 'RS', 'BN', 'TN', 'NN', 'RN', 'BD', 'TD',
  'RD', 'BS', 'TS', 'NS', 'RS', 'BN', 'TN', 'NN', 'RN', 'BD', 'TD', 'ND',
  'BS', 'TS', 'NS', 'RS', 'BN', 'TN', 'NN', 'RN', 'BD', 'TD', 'ND', 'RD',
  'TS', 'NS', 'RS', 'BN', 'TN', 'NN', 'RN', 'BD', 'TD', 'ND', 'RD', 'BS',
  'NS', 'RS', 'BN', 'TN', 'NN', 'RN', 'BD', 'TD', 'ND', 'RD', 'BS', 'TS',
  'RS', 'BN', 'TN', 'NN', 'RN', 'BD', 'TD', 'ND', 'RD', 'BS', 'TS', 'NS',
  'BN', 'TN', 'NN', 'RN', 'BD', 'TD', 'ND', 'RD', 'BS', 'TS', 'NS', 'RS',
  'TN', 'NN', 'RN', 'BD', 'TD', 'ND', 'RD', 'BS', 'TS', 'NS', 'RS', 'BN',
  'NN', 'RN', 'BD', 'TD', 'ND', 'RD', 'BS', 'TS', 'NS', 'RS', 'BN', 'TN',
  'RN', 'BD', 'TD', 'ND', 'RD', 'BS', 'TS', 'NS', 'RS', 'BN', 'TN', 'NN'

To figure out the route at a given time, we need to first establish some epoch as the first voyage and determine where in PATTERN that voyage lies. All other routes will be calculated relative to that epoch. Fortunately, JST is UTC+09:00, which means a voyage lands on the Unix epoch. As it turns out, this voyage is index 88 in PATTERN. Altogether,

const TWO_HOURS = 2 * 60 * 60 * 1000
const OFFSET = 88

 * Returns the route of the ongoing/most recent voyage.
function getRoute (date: Date) {
  // Get the number of voyages since 00:00:00 UTC, 1 January 1970
  const voyageNumber = Math.floor(date.getTime() / TWO_HOURS)

  // Get where it lies in the pattern
  const route = PATTERN[(OFFSET + voyageNumber) % PATTERN.length]

  return route