/** * BEACON NIGHT (coverage slice 10, track T105, 2026-10-14; levels 8-14): after dark the Old Watchtower's signal basin is lit for ten * minutes at one of five places round the tower (three on the open lawn north of the Dreadwoods wall, two inside the woods' northern * edge, where other players can attack and nobody heals), and the gang's night raiders come for it. Defenders keep the flame alive: they * RELIGHT the basin (a click, a short cool-down) and cut down the raiders that crowd it. Raiders at the basin drain the flame; if it * goes out the night is lost, if it still burns when the ten minutes end the night is held. A podium is named by contribution. * * Everything here is pure: the clock, the places, the flame, the scoring, the prizes, the chat lines and the saved state, so the * server, a test and the login line agree. The clock is READ off the wall clock, never rolled or stored (the Passing Bay's pattern): * four starts a day, at 01:50, 05:40, 19:50 and 23:40 UTC, picked so that no ring fight, lair awakening, Heartwood Hunt, Fishing Derby * round or Redcap patrol window comes within ten minutes of one (the test checks 300 days of every clock). A restart, a deploy or a * second server agree on the slot and the place without saving anything. * * The scoring is the Heartwood Hunt's collusion-safe one (heartwoodHunt.ts), built against farming (abuse-first): * - only OWN actions score: a relight that really raised the flame, and a kill of this night's own raiders. A player kill, a death or a * trade scores nothing, so an ambush at the basin takes points from nobody and a friend cannot be fed points; * - each side is capped (12 relights, 10 raider kills); past the caps an action still helps the flame, which is the shared goal; * - one place per ACCOUNT, a minimum score to be paid at all, a podium only when three different accounts qualified AND the beacon was * held, and at most three paid nights per account per UTC day; * - ties go to whoever reached the score first, then the character key, so the order is total; * - prizes are Explorer XP, torches and arrows: no coins, nothing that outclasses the gated 41+ gear. * * Amounts are PLACEHOLDERS for the balance pass, done last with James. The raiders are the Old Watchtower gang's own kind (hijackers). */ export declare const BEACON_NAME = "Beacon Night"; /** The four starts, in minutes after UTC midnight: 01:50, 05:40, 19:50 and 23:40 (night hours; the test checks them against every other clock). */ export declare const BEACON_START_MINUTES: readonly number[]; export declare const BEACON_PER_DAY: number; /** How long the basin burns and is defended. */ export declare const BEACON_ROUND_MS: number; /** How long the result stays up after the round. */ export declare const BEACON_RESULTS_MS: number; /** The warning before the round starts, and the warning before it ends. */ export declare const BEACON_SOON_MS: number; export declare const BEACON_LAST_MS: number; /** idle (nothing announced), soon, open, last (the final minute), results (the board is up). */ export type BeaconStage = 'idle' | 'soon' | 'open' | 'last' | 'results'; export declare const BEACON_STAGES: readonly BeaconStage[]; export declare function isBeaconStage(s: unknown): s is BeaconStage; /** The start of slot `slot` (slot = day * 4 + the start's index; any integer, negative included). */ export declare function beaconStartOf(slot: number): number; export declare function beaconEndOf(slot: number): number; export type BeaconClock = { /** The night this stage is about: the open or just-finished one, else the next to start. */ slot: number; stage: BeaconStage; /** Time to this night's start (0 once it has begun). */ msToStart: number; /** Time to this night's end (0 once it is over). */ msToEnd: number; }; export declare function beaconClockAt(now: number): BeaconClock; /** True while the basin burns and relights and raider kills count. */ export declare function beaconIsOpen(stage: BeaconStage): boolean; /** The next night to start strictly after `now` (for the login line). */ export declare function beaconNextRound(now: number): { slot: number; startsAt: number; }; /** The UTC day a time falls in (the daily paid-nights cap resets at midnight UTC). */ export declare function beaconDayOf(now: number): number; /** * Where the basin can be lit (world tiles; the backend smoke pins each one standable with a standable ring round it, outside the Old * Watchtower gang's camp (x 374-406, z 226-250) by at least twelve tiles). `woods` marks a place inside the Dreadwoods (PvP, no healing). */ export declare const BEACON_SITES: readonly { id: string; x: number; z: number; label: string; woods: boolean; }[]; /** The place of a night. Consecutive nights never share one (the step is 2 inside a block of five and 3 across a block edge). */ export declare function beaconSiteFor(slot: number): (typeof BEACON_SITES)[number]; /** The basin: a quest prop (the old `beacon` model) the server places while a night is on and takes away after. */ export declare const BEACON_ID = "bn-beacon"; export declare const BEACON_PROP = "beacon"; export declare const BEACON_STYLE = "beacon:night"; export declare const BEACON_ENTITY_NAME = "The Watchtower's Signal Basin"; export declare const BEACON_IDLE_LINE = "A great iron basin of pitch and driftwood. Lit on the right nights, it tells the road the Watchtower is held."; export declare const BEACON_FLAME: { readonly max: 100; readonly start: 60; /** What one relight adds. */ readonly relight: 6; /** Flame lost per second for each raider standing within `atBasin` tiles of the basin. */ readonly drainPerRaiderPerSec: 1.2; /** The wind: flame lost per second with no raider at all. */ readonly decayPerSec: 0.1; readonly atBasin: 2; /** One relight per player this often (a click held down adds nothing). */ readonly relightCooldownMs: 3000; /** A relight needs the player this close to the basin (the server's reach rules apply too). */ readonly reach: 3; }; /** The flame after `dtMs` with `raidersAtBasin` raiders on it, never below 0. Pure. */ export declare function beaconFlameAfter(flame: number, dtMs: number, raidersAtBasin: number): number; /** The flame after one relight (capped). */ export declare function beaconFlameRelit(flame: number): number; /** True when a player may relight now: the cool-down has run and the flame is not already full. */ export declare function beaconMayRelight(lastAtMs: number | undefined, now: number, flame: number): boolean; /** * The night raiders: the Old Watchtower gang's own kind, sent in waves while the basin burns. Levels follow the defenders' average * Combat, clamped; the count follows how many are at the basin. They aggro defenders in reach, and advance on the basin when nobody is * in reach. PLACEHOLDERS. */ export declare const BEACON_RAIDERS: { readonly kind: "hijacker"; readonly idPrefix: "bn-"; readonly name: "Night Raider"; readonly firstWave: 3; readonly waveEveryMs: 30000; readonly perExtraDefender: 1; readonly waveMax: 7; readonly aliveCap: 12; /** Raiders come out of the dark this far from the basin. */ readonly spawnMin: 10; readonly spawnMax: 13; readonly aggroRadius: 8; readonly leashRadius: 20; readonly minLevel: 10; readonly maxLevel: 14; /** A defender is "at the basin" this close (a wave grows with them). */ readonly nearBasin: 14; /** A player under this Combat is not locked on to (they may still relight, which is the safe job). */ readonly aggroCombat: 8; readonly aggroText: "A night raider charges out of the dark at you!"; }; /** How many raiders a wave brings: the first wave is fixed; later ones grow with the defenders at the basin. */ export declare function beaconWaveSize(wave: number, defenders: number): number; /** The raiders' level for a party's average Combat. */ export declare function beaconRaiderLevel(avgCombat: number): number; export declare const BEACON_LIGHT_POINTS = 2; export declare const BEACON_KILL_POINTS = 3; /** Past these a relight or a kill still helps (the flame still burns) but scores nothing more. */ export declare const BEACON_LIGHT_CAP = 12; export declare const BEACON_KILL_CAP = 10; /** The least score that counts as having taken part (3 relights, or 2 raiders). Below it: no prize, no mark. */ export declare const BEACON_MIN_SCORE = 6; /** Podium prizes need at least this many different accounts above the minimum, and the beacon held. */ export declare const BEACON_MIN_DEFENDERS = 3; /** Paid nights per account per UTC day (alts and shared accounts cannot farm every night of the day). */ export declare const BEACON_DAILY_PAID_NIGHTS = 3; /** A raider kill counts only for a player this close to the basin (a raider kited across the lawn scores nothing). */ export declare const BEACON_KILL_REACH = 24; export declare function beaconScore(lights: number, kills: number): number; export type BeaconEntry = { /** 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; /** Relights that really raised the flame, and raider kills this night (uncapped; the score caps them). */ lights: number; kills: number; /** When the score last rose (ties go to whoever reached the score first). */ at: number; }; export declare function beaconEntryScore(e: Pick): number; type Who = { key: string; account: string; name: string; }; /** One relight by this character. Returns a new entry (the old is untouched). */ export declare function beaconWithLight(entry: BeaconEntry | undefined, who: Who, at: number): BeaconEntry; /** One raider kill by this character. */ export declare function beaconWithKill(entry: BeaconEntry | undefined, who: Who, at: number): BeaconEntry; export declare function beaconQualifies(e: BeaconEntry): 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 beaconRanking(entries: readonly BeaconEntry[]): BeaconEntry[]; /** The 1-based place of a character in the ranking (ranked entries only), or 0. */ export declare function beaconPlaceOf(entries: readonly BeaconEntry[], key: string): number; export type BeaconRibbon = 'red' | 'blue' | 'bronze'; export declare const BEACON_RIBBON_LABEL: Readonly>; /** PLACEHOLDER prizes by place (held night only): Explorer XP and birch arrows on top of the participation prize. */ export declare const BEACON_PODIUM_PRIZES: readonly { explorerXp: number; arrows: number; }[]; /** Everyone paid who qualified. A beacon that went out halves the XP and gives no torches. */ export declare const BEACON_PARTICIPATION: { readonly explorerXp: 25; readonly torches: 2; }; export declare const BEACON_TORCH_KIND = "unlitbirchtorch"; export declare const BEACON_ARROW_KIND = "bircharrow"; /** Ledger marks (types explorerLedger.ts `beacon` page) a result can write. */ export declare const BEACON_MARK_ENTERED = "beacon:entered"; export declare const BEACON_MARK_HELD = "beacon:held"; export declare const BEACON_MARK_LIGHT = "beacon:lit"; export declare const BEACON_MARK_GUARD = "beacon:guard"; export declare const BEACON_MARK_RIBBON: Readonly>; export type BeaconItem = { kind: string; qty: number; }; /** What one character is owed for a night. */ export type BeaconPrize = { explorerXp: number; items: BeaconItem[]; /** Ledger marks to write. */ marks: string[]; /** The chat line they are told. */ text: string; }; /** The paid-nights record: per account, the day and how many nights were paid that day. */ export type BeaconPaid = Record; /** Paid nights this account has had on this UTC day. */ export declare function beaconPaidToday(paid: BeaconPaid, account: string, day: number): number; /** The record after one more paid night for these accounts (old days dropped). Returns a new object. */ export declare function beaconWithPaid(paid: BeaconPaid, accounts: readonly string[], day: number): BeaconPaid; export type BeaconPrizeInput = { held: boolean; paid: BeaconPaid; day: number; }; /** * Everything a finished night pays, by character key. One prize per account (the ranking lists an account once). An account that has * had its paid nights today is skipped (the podium moves up to the next account). Pure, so a restart that finalises a night late pays * exactly what the clock-time finalise would have. */ export declare function beaconPrizes(entries: readonly BeaconEntry[], input: BeaconPrizeInput): Map; /** The world chat line for a finished night. */ export declare function beaconResultLine(entries: readonly BeaconEntry[], held: boolean): string; /** The world chat line for a stage (idle and results have none: the results line is built from the standings). */ export declare function beaconChatLine(stage: BeaconStage, slot: number): string | null; /** The login line's piece about Beacon Night. */ export declare function beaconTodayText(now: number): string; /** What /beacon tells a player. */ export declare function beaconStatusLines(clock: BeaconClock, entries: readonly BeaconEntry[], myKey: string | null, flame: number, now: number): string[]; export type BeaconOutcome = 'none' | 'held' | 'out'; export type BeaconSave = { v: 1; /** The night the entries belong to (-1 = none). */ slot: number; /** The stage last announced for `announced.slot`. */ announced: { slot: number; stage: BeaconStage; } | null; /** True once `slot`'s results have been worked out and paid or queued. */ finalized: boolean; /** The flame now (while the night is open), and how the night ended. */ flame: number; outcome: BeaconOutcome; /** Raider waves sent this night. */ waves: number; entries: BeaconEntry[]; /** Prizes for characters who were offline when the night ended, by characterId. */ pending: Record; /** Paid nights per account per UTC day. */ paid: BeaconPaid; }; export declare function emptyBeaconSave(): BeaconSave; /** At most this many prizes wait for one character, and entries are never more than this many. */ export declare const BEACON_PENDING_MAX = 5; export declare const BEACON_MAX_ENTRIES = 1000; /** A saved state, checked field by field (it comes from the database); anything wrong gives the empty state. */ export declare function parseBeaconSave(raw: unknown): BeaconSave; export {}; //# sourceMappingURL=beaconNight.d.ts.map