/** * The Thaw gorge (gorge) joined the rotation in the skills 31-40 pass (2026-10-17): river fish (Fishing 25, 33 and 45) off the Surveyors' bank, the * south bank and the Salmon Leap, so the Fishing 31-40 band has a round of its own. * pier, pond and lake are the first three waters (T24). cove (Gullhook Head's pier cove, Fishing 14 and 30) and jetty (Saltmere * Crescent's Shell Bay, Fishing 14) joined the rotation in T104 (COVERAGE_1_40 slice 5), so the middle bands have a round of their own. */ export type DerbyVenueId = 'pier' | 'pond' | 'lake' | 'cove' | 'jetty' | 'gorge'; /** A world-tile box (inclusive). */ export type DerbyBox = { minX: number; maxX: number; minZ: number; maxZ: number; }; export type DerbyFish = { /** Item kind of the catch (what lands in the bag). */ kind: string; label: string; /** The rolled weight lies between these (grams). */ minG: number; maxG: number; /** Only a spot of this kind lands it (a conger comes off a conger hole, never off a tide snapper spot). Absent: any of the venue's spots. */ spot?: string; }; export type DerbyVenue = { id: DerbyVenueId; name: string; /** The chat line says where it is. */ where: string; /** Fishing spot kinds (the server's FishSpotKind) whose catches count here. */ spots: readonly string[]; /** The catches that count: a frog or a snail at the pond is not a derby fish. */ fish: readonly DerbyFish[]; /** The best-catch winner also gets one of these for the aquarium book (an item kind). */ specimen: string; /** * Where the water is, for a venue whose spot kinds exist in many places (the tide snapper has eight): a catch counts only when * the SPOT fished lies inside this box. Absent = the spot kind alone decides (the pier, the pond and the lake, as before). */ near?: DerbyBox; }; /** Gullhook Head's pier cove (types gullhookHead.ts GULLHOOK_COVES pier-cove, its pier and the spots at the pier's end), with a tile of margin. */ export declare const DERBY_COVE_BOX: DerbyBox; /** Saltmere Crescent's Shell Bay (types saltmereCrescent.ts SALTMERE_FISH_SPOT_HINTS), with a margin for the water tile the spots snap to. */ export declare const DERBY_JETTY_BOX: DerbyBox; /** The Thaw gorge (types fishingReaches.ts thaw-bank and thaw-south, riverFishing.ts SALMON_LEAP): the river spots between the Fall and the south bank, with a margin. */ export declare const DERBY_GORGE_BOX: DerbyBox; export declare function derbyBoxHas(box: DerbyBox, x: number, z: number): boolean; export declare const DERBY_VENUES: readonly DerbyVenue[]; export declare function derbyVenue(id: unknown): DerbyVenue | null; /** Every derby fish, across the three waters (the kinds are unique, so a kind names its species). */ export declare function derbyAllFish(): readonly { venue: DerbyVenue; fish: DerbyFish; }[]; export declare function derbyFishByKind(kind: string): { venue: DerbyVenue; fish: DerbyFish; } | null; /** * LEVEL FAIRNESS (T31, 2026-10-14; T24 flagged it): the "best catch" board is judged per species, not by raw weight. A sea * bass (1.5-6.5 kg) would beat any smelt (80-350 g) every round, so the better rod always won the smelt anglers' board. A catch * scores its weight as a share of the biggest its kind can be, in thousandths (a record-sized smelt and a record-sized bass * both score 1000), so a lucky smelt beats an average bass. Chosen over a bracket by Fishing level because the weight roll is * already per species and a bracket would need a level on every entry and split small rounds into boards with one angler. * A venue with a single species (pond: rudd and roach are comparable anyway, lake: only pike) is unchanged in practice. */ export declare const DERBY_SCORE_SCALE = 1000; export declare function derbyScoreOf(kind: string, grams: number): number; /** Time from one round's start to the next. */ export declare const DERBY_PERIOD_MS: number; /** Round n starts this long after the n-th period mark (Ringstone starts on the 3-hour marks). */ export declare const DERBY_OFFSET_MS: number; /** How long the lines are in the water. */ export declare const DERBY_ROUND_MS: number; /** How long the result board stays up after the round. */ export declare const DERBY_RESULTS_MS: number; /** The first warning, and the second, before the round starts; and the warning before it ends. */ export declare const DERBY_SOON_MS: number; export declare const DERBY_CLOSE_MS: number; export declare const DERBY_LAST_MS: number; /** idle (nothing announced), soon, close, open, last (final minutes), results (the board is up). */ export type DerbyStage = 'idle' | 'soon' | 'close' | 'open' | 'last' | 'results'; export declare const DERBY_STAGES: readonly DerbyStage[]; export declare function isDerbyStage(s: unknown): s is DerbyStage; export declare function derbyStartOf(slot: number): number; export declare function derbyEndOf(slot: number): number; /** The venue of a round: the same on every server, and never the venue of the round before. */ export declare function derbyVenueForSlot(slot: number): DerbyVenue; export type DerbyClock = { /** The round this stage is about: the open or just-finished one, else the next to start. */ slot: number; stage: DerbyStage; venue: DerbyVenue; /** Time to this round's start (0 once it has begun). */ msToStart: number; /** Time to this round's end (0 once it is over). */ msToEnd: number; }; export declare function derbyClockAt(now: number): DerbyClock; /** True while catches count. */ export declare function derbyIsOpen(stage: DerbyStage): boolean; /** The next round to start strictly after `now` (for the login line): its slot, venue and start. */ export declare function derbyNextRound(now: number): { slot: number; venue: DerbyVenue; startsAt: number; }; /** The world chat line for a stage (idle and results have none: the results line is built from the standings). */ export declare function derbyChatLine(stage: DerbyStage, venue: DerbyVenue): string | null; /** The login line's piece about the derby, e.g. "the Fishing Derby is at the Long Pier in 35 minutes". */ export declare function derbyTodayText(now: number): string; /** The derby fish a catch is, or null when it does not count here (wrong spot kind, wrong fish kind). */ export declare function derbyFishFor(venue: DerbyVenue, spotKind: string, catchKind: string, at?: { x: number; z: number; }): DerbyFish | null; /** A fish's weight in whole grams; `roll` is uniform in [0, 1) so a test can walk the range. Lean to the small side. */ export declare function rollDerbyWeight(fish: DerbyFish, roll: number): number; export declare function formatDerbyWeight(grams: number): string; /** "1.50 kg Sea Bass": a weight with its species (just the weight when the kind is unknown). */ export declare function derbyCatchText(kind: string, grams: number): string; /** One angler's round. Keyed by character, so a logout and login carries on. */ export type DerbyEntry = { /** characterId. */ key: string; /** The account, so one account's characters cannot all take prizes. Empty when unknown (then the key stands in). */ account: string; name: string; count: number; /** When the latest fish landed (ties on count go to whoever got there first). */ countAt: number; bestG: number; bestKind: string; /** When the best catch landed (ties on score go to whoever landed it first). */ bestAt: number; }; /** Add one fish to an entry, returning the new entry (the old is untouched). */ export declare function derbyWithCatch(entry: DerbyEntry | undefined, who: { key: string; account: string; name: string; }, fish: DerbyFish, grams: number, at: number): DerbyEntry; export type DerbyBoard = 'heaviest' | 'most'; /** * The ranking of one board. Ties: the earlier landing wins, then the key (so the order is total and a test can pin it). * One place per ACCOUNT: of an account's characters only the best-placed one is listed. */ export declare function derbyRanking(entries: readonly DerbyEntry[], board: DerbyBoard): DerbyEntry[]; /** The 1-based place of a character on a board (ranked entries only), or 0. */ export declare function derbyPlaceOf(entries: readonly DerbyEntry[], board: DerbyBoard, key: string): number; /** Podium prizes need at least this many different accounts fishing, so a lone angler (or one account's alts) farms nothing. */ export declare const DERBY_MIN_ANGLERS = 2; export type DerbyRibbon = 'red' | 'blue' | 'bronze'; export declare const DERBY_RIBBON_LABEL: Readonly>; /** PLACEHOLDER prizes by place: Fishing XP, Explorer XP. */ export declare const DERBY_HEAVIEST_PRIZES: readonly { fishingXp: number; explorerXp: number; }[]; export declare const DERBY_MOST_PRIZES: readonly { fishingXp: number; explorerXp: number; }[]; /** Everyone ranked who landed at least one fish. */ export declare const DERBY_PARTICIPATION: { readonly fishingXp: 25; readonly maggots: 5; }; /** Ledger marks (types explorerLedger.ts `derby` page) a result can write. */ export declare const DERBY_MARK_ENTERED = "derby:entered"; export declare const DERBY_MARK_CUP = "derby:cup"; export declare const DERBY_MARK_RIBBON: Readonly>; export declare const DERBY_MARK_VENUE: Readonly>; export type DerbyItem = { kind: string; qty: number; }; /** What one character is owed for a round: all their placings merged into one hand-over. */ export type DerbyPrize = { fishingXp: number; explorerXp: number; items: DerbyItem[]; /** Ledger marks to write. */ marks: string[]; /** The chat line they are told. */ text: string; }; /** * Everything a finished round pays, by character key. One prize per account (the ranking lists an account once). Pure, so a * restart that finalises a round late pays exactly what the clock-time finalise would have. */ export declare function derbyPrizes(entries: readonly DerbyEntry[], venue: DerbyVenue): Map; /** The world chat line for a finished round. */ export declare function derbyResultLine(entries: readonly DerbyEntry[], venue: DerbyVenue): string; /** What /derby tells a player: the state, and (during a round or its results) the standings and their own place. */ export declare function derbyStatusLines(clock: DerbyClock, entries: readonly DerbyEntry[], myKey: string | null, now: number): string[]; /** The biggest fish of one kind ever weighed in at a real (clock) round. By fish kind, so the plaque lists each water's species. */ export type DerbyRecord = { grams: number; name: string; at: number; }; export type DerbyRecords = Record; /** * A catch against the records: the new records and whether this one broke (or set) its kind's record. Pure; the old records * are untouched. A tie does not take the record from the holder. */ export declare function derbyWithRecord(records: DerbyRecords, who: { name: string; }, fish: DerbyFish, grams: number, at: number): { records: DerbyRecords; broke: boolean; }; /** Server entity ids of the derby grounds beside the Long Pier's foot (final-frame world tiles; backend derbyGrounds.ts lays them). */ export declare const DERBY_BOARD_ID = "derby-board"; export declare const DERBY_RECORDS_ID = "derby-records"; export declare const DERBY_WEIGHIN_ID = "derby-weighin"; export declare const DERBY_PRIZES_ID = "derby-prizes"; export declare const DERBY_GROUNDS: { /** The notice board: a click reads the round, the standings and the last results. */ readonly board: { readonly x: 298; readonly z: 1002; }; /** The record plaque: the biggest of each kind. */ readonly records: { readonly x: 297; readonly z: 1004; }; /** Stand-in props (the Bramble Fair's striped stalls) until the Blender pieces: the scales and the prize table. */ readonly weighIn: { readonly x: 294; readonly z: 1000; }; readonly prizes: { readonly x: 290; readonly z: 1000; }; }; /** Within this many tiles of the board it shows the round over itself, like Ringstone's. */ export declare const DERBY_SIGN_READ_RANGE = 6; export type DerbyRow = { name: string; text: string; }; export type DerbyYou = { bestPlace: number; mostPlace: number; count: number; bestText: string; }; export type DerbyRecordRow = { venueId: DerbyVenueId; venueName: string; fish: string; text: string; holder: string; }; export type DerbyLast = { venueName: string; best: DerbyRow[]; most: DerbyRow[]; }; /** Message `derby`: everything the board, the plaque and the HUD show, built per player (`you`). Times are "from now". */ export type DerbyView = { stage: DerbyStage; venueId: DerbyVenueId; venueName: string; where: string; msToStart: number; msToEnd: number; nextVenueName: string; nextInMs: number; /** Anglers ranked this round. */ anglers: number; best: DerbyRow[]; most: DerbyRow[]; you: DerbyYou | null; /** The last finished round (while there is no live one): its top three on each board. */ last: DerbyLast | null; records: DerbyRecordRow[]; }; /** A short ticking time, "12:34". */ export declare function formatDerbyClock(ms: number): string; export declare function derbyRecordRows(records: DerbyRecords): DerbyRecordRow[]; export declare function derbyView(input: { clock: DerbyClock; now: number; /** The standings of the round `clock` is about (empty when it has not started). */ entries: readonly DerbyEntry[]; /** The round that finished before it, for the board's results (null: none kept). */ last: { venue: DerbyVenue; entries: readonly DerbyEntry[]; } | null; records: DerbyRecords; myKey: string | null; }): DerbyView; /** The line over the board or on the HUD: the round or the next one, `elapsedMs` since the view was received. */ export declare function derbyBoardText(view: DerbyView, elapsedMs?: number): string; /** What a click on the board prints: the round, the standings (or the last round's results) and a fairness note. */ export declare function derbyBoardLines(view: DerbyView, elapsedMs?: number): string[]; /** What a click on the record plaque prints: the biggest of each kind, by water. */ export declare function derbyPlaqueLines(view: DerbyView): string[]; /** The saved state (world timers `fishing-derby`): the round's standings, the stage last told, and prizes not yet handed over. */ export type DerbySave = { v: 1; /** The round the entries belong to (-1 = none). */ slot: number; /** The stage last announced for `announcedSlot`. */ announced: { slot: number; stage: DerbyStage; } | null; /** True once `slot`'s results have been worked out and paid or queued. */ finalized: boolean; entries: DerbyEntry[]; /** Prizes for characters who were offline when the round ended, by characterId. */ pending: Record; /** The biggest fish of each kind ever weighed in (the plaque); older saves lack it. */ records: DerbyRecords; }; export declare function emptyDerbySave(): DerbySave; /** At most this many prizes wait for one character (a character away for days cannot grow the save without bound). */ export declare const DERBY_PENDING_MAX = 5; /** A saved state, checked field by field (it comes from the database); anything wrong gives the empty state. */ export declare function parseDerbySave(raw: unknown): DerbySave; //# sourceMappingURL=fishingDerby.d.ts.map