/** * THE TIMBER CONVOY (coverage slice 11, track T123, 2026-10-15; levels 21-30, a Dreadwoods woods feature): six times a day, in daylight, the * forester's crew rolls a loaded timber wagon out of the camp (a PvP-off pocket at the Rotwood Grove's southern edge) and walks it down the * woods to the Gallowmere road head. It takes eight minutes. The wagon is an NPC, not a prize: nobody can rob it. Players may ESCORT it, and * the woods' wolves come for it as it goes, so an escort that stays close and cuts the wolves down is what gets it there whole. * * Everything here is pure: the clock, the route, the wagon's tile and health, the scoring, the prizes, the chat lines and the saved state, so * the server, a test and the login line agree. It is the Beacon Night pattern (beaconNight.ts) and the Heartwood Hunt's collusion-safe scoring * (heartwoodHunt.ts), with the Redcap patrol's clock-read timing (tollRoad.ts): * - the clock is READ off the wall clock, never rolled or stored: six starts a day at 04:17, 07:17, 10:17, 13:17, 16:17 and 19:17 UTC, picked so * that no ring fight, lair awakening, Heartwood Hunt, Fishing Derby round, Redcap patrol or Beacon Night comes close (the test checks 300 * days of every clock). The wagon's tile is a pure function of the clock along the route, so a restart, a deploy or a second server agree * on where it is without saving anything; * - only OWN actions score: time spent within reach of the wagon (small, capped, and on its own never enough to be paid) and kills of this * convoy's own wolves (the `tc-` ids) made near the wagon. A player kill, a death or a trade scores nothing, so ambushing an escort in the * PvP woods takes points from nobody and a friend cannot be fed points; * - one place per ACCOUNT, a minimum score to be paid at all, a podium only when the wagon ARRIVED and three different accounts qualified, * and at most four paid convoys per account per UTC day; * - ties go to whoever reached the score first, then the character key, so the order is total; * - prizes are Woodwork and Explorer XP and alder and ash logs: no coins, nothing that outclasses the gated 41+ gear. Escorts are paid at * arrival (a wagon that is wrecked pays half the XP and no logs; a void convoy pays nothing). * * Amounts are PLACEHOLDERS for the balance pass, done last with James. The wolves are the Heartwood brood's own kind (mosswolf). */ export declare const CONVOY_NAME = "Timber Convoy"; /** The six starts, in minutes after UTC midnight: 04:17, 07:17, 10:17, 13:17, 16:17 and 19:17 (daylight; the test checks them against every other clock). */ export declare const CONVOY_START_MINUTES: readonly number[]; export declare const CONVOY_PER_DAY: number; /** How long the wagon takes from the camp to the road head. */ export declare const CONVOY_TRAVEL_MS: number; /** How long the unloading stays up after the wagon arrives (the wagon stands at the road head and the standings are up). */ export declare const CONVOY_RESULTS_MS: number; /** The loading time before the start, and the warning before the end. */ export declare const CONVOY_SOON_MS: number; export declare const CONVOY_LAST_MS: number; /** idle (nothing announced), soon (the wagon is being loaded at the camp), open (on the road), last (the final minute), results. */ export type ConvoyStage = 'idle' | 'soon' | 'open' | 'last' | 'results'; export declare const CONVOY_STAGES: readonly ConvoyStage[]; export declare function isConvoyStage(s: unknown): s is ConvoyStage; /** The start of slot `slot` (slot = day * 6 + the start's index; any integer, negative included). */ export declare function convoyStartOf(slot: number): number; export declare function convoyEndOf(slot: number): number; export type ConvoyClock = { /** The convoy this stage is about: the open or just-finished one, else the next to start. */ slot: number; stage: ConvoyStage; /** Time to this convoy's start (0 once it has begun). */ msToStart: number; /** Time to this convoy's arrival (0 once it is over). */ msToEnd: number; }; export declare function convoyClockAt(now: number): ConvoyClock; /** True while the wagon is on the road and presence and wolf kills count. */ export declare function convoyIsOpen(stage: ConvoyStage): boolean; /** True from the loading (soon) until the unloading ends (results): the wagon is on the ground. */ export declare function convoyWagonOut(stage: ConvoyStage): boolean; /** The next convoy to start strictly after `now` (for the login line). */ export declare function convoyNextRound(now: number): { slot: number; startsAt: number; }; /** The UTC day a time falls in (the daily paid-convoys cap resets at midnight UTC). */ export declare function convoyDayOf(now: number): number; /** * The road, in world tiles: from the forester's camp (the PvP-off pocket x 324-336, z 404-412) down through the woods to the Gallowmere road * head. Surveyed 2026-10-15 with the backend's pathfinder: every tile of every straight run is standable and every step is a legal step (the * backend smoke walks it). 165 tiles, so the wagon plods at about one tile every three seconds. */ export declare const CONVOY_ROUTE: readonly { x: number; z: number; }[]; /** The route's length in tiles (Chebyshev, the walking metric). */ export declare const CONVOY_ROUTE_LENGTH: number; /** The tile `d` tiles along the route (clamped to both ends). The same rounding the backend smoke walks. */ export declare function convoyTileAtDistance(d: number): { x: number; z: number; }; /** How far along the road the wagon is for convoy `slot` at `now`, 0 to 1 (0 while it loads, 1 once it has arrived). Pure: no stored position. */ export declare function convoyProgress(slot: number, now: number): number; /** The wagon's tile for convoy `slot` at `now`. */ export declare function convoyWagonTile(slot: number, now: number): { x: number; z: number; }; export declare const CONVOY_START_PLACE = "the forester's camp"; export declare const CONVOY_END_PLACE = "the Gallowmere road head"; /** The wagon: a tame pony NPC (the existing `penpony` kind) named for the cargo; never a fight, never attackable. Upgrade row: its own cart model. */ export declare const CONVOY_WAGON: { readonly id: "tc-wagon"; readonly kind: "penpony"; readonly name: "Timber wagon"; readonly healthMax: 100; /** Health lost per second for each wolf standing within `atWagon` tiles of it. */ readonly drainPerWolfPerSec: 0.8; readonly atWagon: 2; /** The wagon arrived "whole" with at least this much health left (a Ledger line). */ readonly intact: 70; /** The server steps the wagon towards its clock tile; further behind than this, it is put on the tile (a blocked step never strands it). */ readonly snapBehind: 4; /** Missing from the world this long (removed by something else, nowhere to stand), the convoy is void. */ readonly missingVoidMs: 20000; }; /** The wagon's health after `dtMs` with `wolvesAtWagon` wolves on it, never below 0. Pure. */ export declare function convoyHealthAfter(health: number, dtMs: number, wolvesAtWagon: number): number; /** * The woods' wolves that come for the wagon: the Heartwood brood's kind, ids `tc-`, sent in waves while somebody is near the wagon. Levels * follow the escorts' average Combat, clamped; the count follows how many escorts are near. They go for an escort in reach, and for the wagon * when nobody is in reach. No drops (no farming a convoy for fur). PLACEHOLDERS. */ export declare const CONVOY_WOLVES: { readonly kind: "mosswolf"; readonly idPrefix: "tc-"; readonly name: "Ambush wolf"; readonly firstWave: 2; readonly firstWaveAfterMs: 25000; readonly waveEveryMs: 40000; readonly perExtraEscort: 1; readonly waveMax: 6; readonly aliveCap: 10; /** Wolves come out of the trees this far from the wagon. */ readonly spawnMin: 9; readonly spawnMax: 12; readonly aggroRadius: 8; readonly leashRadius: 24; readonly minLevel: 21; readonly maxLevel: 30; /** An escort is "near" this close to the wagon (a wave grows with them, and the first wave waits for one). */ readonly nearWagon: 22; /** A player under this Combat is not locked on to (they may still walk beside the wagon, which is the safe job). */ readonly aggroCombat: 18; readonly aggroText: "A wolf bursts out of the trees at you!"; }; /** How many wolves a wave brings: the first wave is fixed; later ones grow with the escorts near the wagon. */ export declare function convoyWaveSize(wave: number, escorts: number): number; /** The wolves' level for a party's average Combat. */ export declare function convoyWolfLevel(avgCombat: number): number; /** One presence point for each stretch this long spent within `CONVOY_ESCORT_REACH` of the wagon. */ export declare const CONVOY_PRESENCE_EVERY_MS = 20000; export declare const CONVOY_ESCORT_REACH = 8; export declare const CONVOY_PRESENCE_POINTS = 1; export declare const CONVOY_KILL_POINTS = 3; /** Past these a stretch or a kill still helps the wagon (a dead wolf is a dead wolf) but scores nothing more. Presence alone never reaches the minimum. */ export declare const CONVOY_PRESENCE_CAP = 5; export declare const CONVOY_KILL_CAP = 10; /** The least score that counts as having escorted (2 wolves, or 1 wolf and 3 stretches). Presence alone (at most 5) is never enough. */ export declare const CONVOY_MIN_SCORE = 6; /** Podium prizes need at least this many different accounts above the minimum, and the wagon arrived. */ export declare const CONVOY_MIN_ESCORTS = 3; /** Paid convoys per account per UTC day (alts and shared accounts cannot farm every wagon of the day). */ export declare const CONVOY_DAILY_PAID = 4; /** A wolf kill counts only for a player this close to the wagon (a wolf kited across the woods scores nothing). */ export declare const CONVOY_KILL_REACH = 20; export declare function convoyScore(presence: number, kills: number): number; export type ConvoyEntry = { /** 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; /** Stretches spent beside the wagon and wolf kills this convoy (uncapped; the score caps them). */ presence: number; kills: number; /** When the score last rose (ties go to whoever reached the score first). */ at: number; }; export declare function convoyEntryScore(e: Pick): number; type Who = { key: string; account: string; name: string; }; /** One stretch beside the wagon by this character. Returns a new entry (the old is untouched). */ export declare function convoyWithPresence(entry: ConvoyEntry | undefined, who: Who, at: number): ConvoyEntry; /** One wolf kill by this character. */ export declare function convoyWithKill(entry: ConvoyEntry | undefined, who: Who, at: number): ConvoyEntry; export declare function convoyQualifies(e: ConvoyEntry): boolean; /** * The ranking: higher score first, then whoever reached it first, then the key (a total order a test can pin). One place per ACCOUNT: * of an account's characters only the best-placed one is listed. Entries below the minimum score are not ranked. */ export declare function convoyRanking(entries: readonly ConvoyEntry[]): ConvoyEntry[]; /** The 1-based place of a character in the ranking (ranked entries only), or 0. */ export declare function convoyPlaceOf(entries: readonly ConvoyEntry[], key: string): number; export type ConvoyRibbon = 'red' | 'blue' | 'bronze'; export declare const CONVOY_RIBBON_LABEL: Readonly>; /** PLACEHOLDER podium prizes by place (an arrived wagon only), on top of the participation prize. */ export declare const CONVOY_PODIUM_PRIZES: readonly { woodworkXp: number; explorerXp: number; ash: number; }[]; /** Everyone paid who qualified. A wagon that was wrecked halves the XP and gives no logs. */ export declare const CONVOY_PARTICIPATION: { readonly woodworkXp: 100; readonly explorerXp: 20; readonly alder: 3; }; export declare const CONVOY_ALDER_KIND = "alderwood"; export declare const CONVOY_ASH_KIND = "ashwood"; /** Ledger marks (types explorerLedger.ts `convoy` page) a result can write. */ export declare const CONVOY_MARK_ENTERED = "convoy:entered"; export declare const CONVOY_MARK_ARRIVED = "convoy:arrived"; export declare const CONVOY_MARK_WHOLE = "convoy:whole"; export declare const CONVOY_MARK_GUARD = "convoy:guard"; export declare const CONVOY_MARK_RIBBON: Readonly>; export type ConvoyItem = { kind: string; qty: number; }; /** What one character is owed for a convoy. */ export type ConvoyPrize = { woodworkXp: number; explorerXp: number; items: ConvoyItem[]; /** Ledger marks to write. */ marks: string[]; /** The chat line they are told. */ text: string; }; /** The paid-convoys record: per account, the day and how many convoys were paid that day. */ export type ConvoyPaid = Record; /** Paid convoys this account has had on this UTC day. */ export declare function convoyPaidToday(paid: ConvoyPaid, account: string, day: number): number; /** The record after one more paid convoy for these accounts (old days dropped). Returns a new object. */ export declare function convoyWithPaid(paid: ConvoyPaid, accounts: readonly string[], day: number): ConvoyPaid; /** How a convoy ended: on the road ('none'), the wagon got there ('arrived'), the wolves broke it ('wrecked'), or nobody can say ('void'). */ export type ConvoyOutcome = 'none' | 'arrived' | 'wrecked' | 'void'; export type ConvoyPrizeInput = { outcome: ConvoyOutcome; health: number; paid: ConvoyPaid; day: number; }; /** * Everything a finished convoy pays, by character key. One prize per account (the ranking lists an account once). An account that has had its * paid convoys today is skipped (the podium moves up to the next account). A void convoy pays nothing. Pure, so a restart that finalises a * convoy late pays exactly what the clock-time finalise would have. */ export declare function convoyPrizes(entries: readonly ConvoyEntry[], input: ConvoyPrizeInput): Map; /** The world chat line for a finished convoy. */ export declare function convoyResultLine(entries: readonly ConvoyEntry[], outcome: ConvoyOutcome, health: number): string; /** The world chat line for a stage (idle and results have none: the results line is built from the standings). */ export declare function convoyChatLine(stage: ConvoyStage): string | null; /** The login line's piece about the Timber Convoy. */ export declare function convoyTodayText(now: number): string; /** What /convoy tells a player. */ export declare function convoyStatusLines(clock: ConvoyClock, entries: readonly ConvoyEntry[], myKey: string | null, health: number, now: number): string[]; export type ConvoySave = { v: 1; /** The convoy the entries belong to (-1 = none). */ slot: number; /** The stage last announced for `announced.slot`. */ announced: { slot: number; stage: ConvoyStage; } | null; /** True once `slot`'s results have been worked out and paid or queued. */ finalized: boolean; /** The wagon's health now (while it is on the road), and how the convoy ended. */ health: number; outcome: ConvoyOutcome; /** Wolf waves sent this convoy. */ waves: number; entries: ConvoyEntry[]; /** Prizes for characters who were offline when the convoy ended, by characterId. */ pending: Record; /** Paid convoys per account per UTC day. */ paid: ConvoyPaid; }; export declare function emptyConvoySave(): ConvoySave; /** At most this many prizes wait for one character, and entries are never more than this many. */ export declare const CONVOY_PENDING_MAX = 5; export declare const CONVOY_MAX_ENTRIES = 1000; /** A saved state, checked field by field (it comes from the database); anything wrong gives the empty state. */ export declare function parseConvoySave(raw: unknown): ConvoySave; export {}; //# sourceMappingURL=timberConvoy.d.ts.map