/** * A STEERED RIDE (2026-10-19, first used by Bracken Heath's Pup Track and Heath Dash, brackenHeath.ts): a cart the SERVER moves along a course * while the rider picks one of three lanes. Hask's Sled Run (sledRun.ts) is the pattern: the server sets the rider's `path`, the world's * ordinary stepping carries them along it at the ride's pace, and the client only draws it. What is new here is the lanes: a course is a * centreline of 8-connected tiles, and each lane is that line pushed sideways, so a lane change is two diagonal steps across the turf * stripe between two lanes. * * Everything in this file is pure data and pure maths, shared by the server (which moves the cart, checks hits and pays) and the client * (which draws the course's hazards and crossers and the rider's readout). Nothing here touches health: a hazard slows the cart and knocks * coins off the pay, and that is all it can do. * * Coordinates are world tiles (x east, z south). A direction is a step (dx, dz) with each part -1, 0 or 1. The lane offsets are along the * rider's RIGHT-hand normal (-dz, dx): lane 0 is the left lane, 1 the middle, 2 the right. */ export type RideLane = 0 | 1 | 2; export declare const RIDE_LANES: readonly RideLane[]; /** Tiles sideways from the centreline (along the right-hand normal) for lanes 0, 1 and 2. The tiles between them (offset -1 and 1) are the turf stripes. */ export declare const RIDE_LANE_OFFSETS: readonly [number, number, number]; export declare const RIDE_LANE_NAMES: readonly [string, string, string]; export declare function isRideLane(v: unknown): v is RideLane; /** One tile of a course's centreline, with the direction of the segment it is on. */ export type CentreTile = { readonly x: number; readonly z: number; readonly dx: number; readonly dz: number; }; /** One tile a rider can stand on: where it is, the centreline index it belongs to (`p`, the rider's progress) and its sideways offset. */ export type RideTile = { readonly x: number; readonly z: number; readonly p: number; readonly o: number; }; /** A fixed hazard on one lane: the rider who drives over it is knocked (slowed, coins off). Drawn as a course prop. */ export type RideHazardKind = 'hay' | 'gorse' | 'barrel'; export type RideHazard = { readonly id: string; readonly p: number; readonly lane: RideLane; readonly kind: RideHazardKind; }; /** A straw boost mat on one lane: driven over, the cart goes faster for a stretch. */ export type RideBoost = { readonly id: string; readonly p: number; readonly lane: RideLane; }; /** * Something that runs to and fro across the lanes on the server's clock (a loose sheep, a heath hare): an NPC of an existing harmless kind, * moved every tick by the server (never by the generic wander) and never fought. A rider whose tile it is on, or next to, is knocked. */ export type RideCrosser = { readonly id: string; readonly kind: 'sheep' | 'brownhare'; readonly name: string; /** The centreline index it crosses at. */ readonly p: number; /** How far either side of the centreline it runs (it turns at +-reach). */ readonly reach: number; /** Milliseconds per tile it moves. */ readonly msPerTile: number; /** Shifts its clock so two crossers are not in step. */ readonly phaseMs: number; }; export type RideCourse = { readonly id: string; readonly name: string; /** Who lends the cart and where you stand to ask (Chebyshev reach). */ readonly keeper: string; readonly keeperReach: number; /** The centreline's corners: each leg is straight or an exact diagonal. The ride runs from the first to the last. */ readonly waypoints: ReadonlyArray; /** How many times round (the Pup Track is two laps of one loop; the loop must close on its first waypoint). */ readonly laps: number; /** The lane a ride starts in. */ readonly startLane: RideLane; /** Combat level needed (0 = any level). */ readonly combat: number; /** Walking-pace multipliers: the cart's own pace, on a boost, and after a knock. */ readonly speed: number; readonly boostSpeed: number; readonly slowSpeed: number; /** How many centreline tiles a boost or a knock lasts. */ readonly boostTiles: number; readonly slowTiles: number; /** The pay for a finished run, what each knock takes off it and the least it can fall to (PLACEHOLDERS). */ readonly pay: number; readonly knockCoins: number; readonly minPay: number; /** The centreline indexes of the checkpoint flags (numbered 1..n in order). */ readonly checkpoints: readonly number[]; readonly hazards: readonly RideHazard[]; readonly boosts: readonly RideBoost[]; readonly crossers: readonly RideCrosser[]; /** A run that has not finished by now is called off. */ readonly maxRunMs: number; }; /** The hold at the line between being counted in and the cart rolling. */ export declare const RIDE_READY_MS = 3000; /** After any ending, the next ride waits this long. */ export declare const RIDE_RETRY_MS = 4000; /** A finished run pays at most once in this long per rider (another run is always free to ride, and counts for the board). */ export declare const RIDE_PAY_WINDOW_MS: number; /** A crosser knocks a rider whose tile is within this (Chebyshev) of its own, and whose progress is within this of its crossing. */ export declare const RIDE_CROSSER_HIT = 1; /** The centreline: every tile from the first waypoint to the last, each with its leg's direction. Throws on a leg that is not straight or diagonal. */ export declare function rideCentreline(course: Pick): CentreTile[]; /** The right-hand normal of a direction. */ export declare function rideNormal(dx: number, dz: number): { nx: number; nz: number; }; /** * One lane (or the centreline at offset 0) as a walkable chain of tiles: every centreline tile pushed `offset` along its normal, with * the gaps a corner opens filled by straight steps and the cusps a corner folds dropped, so each step is to a neighbouring tile and no tile * is visited twice. Each tile keeps the centreline index it came from (`p`, never decreasing). */ export declare function rideLaneTiles(centre: readonly CentreTile[], offset: number): RideTile[]; /** A course built once: its centreline and its three lanes. */ export type BuiltCourse = { readonly course: RideCourse; readonly centre: readonly CentreTile[]; readonly lanes: readonly [readonly RideTile[], readonly RideTile[], readonly RideTile[]]; /** The index of the finish in centreline terms. */ readonly finishP: number; /** Every tile of the course's ground (the lanes, the stripes and a verge either side): a lane change never leaves it. */ readonly ground: ReadonlySet; /** The ground's tiles with their sideways offsets (rideGroundTiles). */ readonly groundOffsets: ReadonlyMap; }; export declare function buildRideCourse(course: RideCourse): BuiltCourse; /** The first tile of a lane at or past centreline index p (the last tile when p is past the end). */ export declare function rideLaneIndexAt(lane: readonly RideTile[], p: number): number; /** The tile a hazard or boost mat stands on: its lane's first tile at its index. */ export declare function rideMarkTile(b: BuiltCourse, mark: { p: number; lane: RideLane; }): RideTile; /** * Every tile of a course's ground: a solid band round the centreline, each tile with its sideways offset (rounded toward the centre: -2, 0 and 2 * are the lanes, -1 and 1 the turf stripes, -3 and 3 the verges). A tile belongs to its nearest centreline tile, so a diagonal leg is as solid as a * straight one (no checkerboard of gaps between its lanes for a stone to stand in). */ export declare function rideGroundTiles(b: BuiltCourse): { x: number; z: number; o: number; }[]; /** * A lane change: the steps from the rider's tile across to the new lane, landing two centreline tiles ahead, then on along it. `from` is the * rider's tile; returns the steps (each to a neighbouring tile) and the index in the new lane where the rider lands, or null when the course * is too near its end to change. */ export declare function rideLaneChange(b: BuiltCourse, from: RideTile, to: RideLane): { steps: RideTile[]; landIndex: number; } | null; /** The lanes a tile counts as for hits: its own, or both lanes either side of a stripe. */ export declare function rideTileLanes(t: Pick): RideLane[]; /** The lane nearest a world tile at the rider's progress (a tap on the course): the lane whose tile a few steps ahead is nearest it. */ export declare function rideLaneNearest(b: BuiltCourse, p: number, x: number, z: number): RideLane; /** A crosser's sideways offset at a moment: it runs from -reach to +reach and back, one tile per msPerTile. */ export declare function rideCrosserOffset(c: RideCrosser, nowMs: number): number; /** A crosser's tile at a moment (the centreline tile at its index, pushed sideways by its offset). */ export declare function rideCrosserTile(b: BuiltCourse, c: RideCrosser, nowMs: number): { x: number; z: number; o: number; }; /** Is a rider on this tile knocked by this crosser now? */ export declare function rideCrosserHits(b: BuiltCourse, c: RideCrosser, rider: RideTile, nowMs: number): boolean; /** The cart's pace at a progress index: slowed after a knock, boosted after a mat, else its own. */ export declare function rideSpeedAt(course: Pick, p: number, boostUntil: number, slowUntil: number): number; /** What a finished run pays: the course's pay less the knocks, never below its least. */ export declare function ridePay(course: Pick, knocks: number): number; /** Seconds with one decimal: 61840 -> "61.8 s". */ export declare function formatRideTime(ms: number): string; export type RideBoardRow = { name: string; ms: number; at: number; }; export type RideBoardSave = { week: string; courses: Record; }; export declare const RIDE_BOARD_SIZE = 10; /** The ISO week a moment falls in (Monday starts a week), the board's key: "2026-W43". */ export declare function rideWeekKey(nowMs: number): string; export declare function emptyRideBoard(nowMs: number): RideBoardSave; /** The board as saved, or a fresh one: last week's board, a broken row, an impossible time or an unknown course is dropped. */ export declare function parseRideBoard(raw: unknown, nowMs: number, plausible: (courseId: string, ms: number) => boolean): RideBoardSave; /** A finished run on the board: one row a name (only a quicker run replaces it); a new week starts afresh. */ export declare function recordRideTime(save: RideBoardSave, courseId: string, name: string, ms: number, nowMs: number): { save: RideBoardSave; changed: boolean; place: number; }; export declare function ridePlaceOf(rows: readonly RideBoardRow[], name: string): number; /** The board as chat lines, quickest first. */ export declare function rideBoardLines(save: RideBoardSave, course: Pick): string[]; /** ServerMessage 'cart' phases. */ export type RidePhase = 'ready' | 'go' | 'lane' | 'knock' | 'boost' | 'checkpoint' | 'done' | 'fail'; /** Messages a rider may still send: nothing that moves, acts, trades, fights, eats or sits. Server and client keep the same list. */ export declare const RIDE_ALLOWED_MESSAGES: ReadonlySet; /** The walking pace the server's multipliers scale (backend config.ts MOVE_SPEED_TILES_PER_SEC; the backend smoke checks they agree). */ export declare const RIDE_WALK_TILES_PER_SEC = 3.9; /** What a step costs the world's stepping (a diagonal is root two, as backend map.ts stepCost). */ export declare function rideStepCost(a: { x: number; z: number; }, b: { x: number; z: number; }): number; /** * How much faster a lane's cart rolls than the centreline's, so that every lane takes the same time end to end (an outside lane is longer * round a corner): the lane's length over the centreline's. The skill is in the hazards, not in hugging the inside. */ export declare function rideLaneScale(b: BuiltCourse, lane: RideLane): number; /** * The time a clean run takes (no knock), riding at the centreline's pace: with `boosts` every mat taken, without them none. The quickest a run * can be is about the first; a careful run with no mats about the second. Used to set the paint times and the board's plausible range. */ export declare function rideCleanTimeMs(b: BuiltCourse, boosts: boolean): number; /** `ride:go:` asks for a ride, `ride:board:` reads that course's Fastest Times, `ride:mine` your own times this week. */ export declare const RIDE_ACTION_PREFIX = "ride:"; export declare const rideGoAction: (courseId: string) => string; export declare const rideBoardAction: (courseId: string) => string; export declare const RIDE_MINE_ACTION = "ride:mine"; export declare function isRideAction(action: string): boolean; /** What an action asks for, or null when it is not a ride action. */ export declare function parseRideAction(action: string): { kind: 'go' | 'board'; course: string; } | { kind: 'mine'; } | null; //# sourceMappingURL=steeredRide.d.ts.map