import { type ResponseCurve, type UtilityBrainSnapshot, type UtilityCandidate, type UtilityOption } from "../ai/utilityBrain.js"; import { type QueueWait, type WaitQueue, type WaitQueueSnapshot } from "./queue.js"; import type { NeedSet } from "./needs.js"; /** Where a ride is in its cycle. */ export type RidePhase = "idle" | "loading" | "riding" | "unloading" | "broken"; /** What a ride is worth to a guest. All three come from the ride's own data. */ export interface RideRatings { /** How good the ride is; a higher rating draws more guests. */ readonly excitement: number; /** Physical force; a guest past their comfort loses interest. */ readonly intensity: number; /** Motion sickness; a guest past their comfort loses interest. */ readonly nausea: number; } /** Breakdown curve and money for one ride. All data, so a park tunes its own economy. */ export interface RideBreakdown { /** Chance one completed cycle breaks the ride at zero wear. */ readonly baseChance: number; /** Added chance per cycle since the last service — the wear curve. */ readonly wearChance: number; /** Seconds the ride is out while it is repaired. */ readonly repairSeconds: number; /** Money charged when a breakdown is repaired. */ readonly repairCost: number; /** Money charged when a mechanic services the ride before it breaks. */ readonly serviceCost: number; } /** Everything a ride is: its ratings, its cycle and its wear. Content, not code. */ export interface RideDefinition { readonly id: string; readonly ratings: RideRatings; /** Guests one cycle carries; the line keeps the rest. */ readonly capacity: number; readonly loadSeconds: number; readonly rideSeconds: number; readonly unloadSeconds: number; /** Omit for a ride that never breaks down. */ readonly breakdown?: RideBreakdown; } /** A ride's line as a game or the devtools overlay reads it. */ export interface RideQueueView { readonly size: number; /** Wait facing a guest joining now, from the queue's own throughput. */ readonly wait: QueueWait; } /** One ride's live state. */ export interface RideView { readonly id: string; readonly ratings: RideRatings; readonly capacity: number; readonly phase: RidePhase; /** Seconds left in the current phase; `0` while idle. */ readonly phaseSeconds: number; /** Guests on board right now. */ readonly riders: number; readonly queue: RideQueueView; readonly cycles: number; /** Completed cycles since the ride was last serviced — what the wear curve reads. */ readonly wear: number; readonly breakdowns: number; /** Money this ride has cost in repairs and services. */ readonly spend: number; } /** Counters a devtools probe or test reads. */ export interface RideStats { rides: number; broken: number; riders: number; queued: number; boarded: number; cycles: number; breakdowns: number; /** Money spent on repairs and services since creation. */ spend: number; ticks: number; /** Slowest single `tick` seen so far, in milliseconds. */ slowestTickMs: number; } /** Where a ride's boardings, alightings and breakdowns are announced. Every hook is optional. */ export interface RideTickSink { onBoard?(rideId: string, guestId: string, at: number): void; onAlight?(rideId: string, guestId: string, at: number): void; onBreak?(rideId: string, at: number): void; onRepaired?(rideId: string, cost: number, at: number): void; } /** In-play dials a ride accepts from {@link RideSystem.retune}. */ export interface RideRetune { readonly ratings?: Partial; readonly capacity?: number; readonly loadSeconds?: number; readonly rideSeconds?: number; readonly unloadSeconds?: number; /** Merge into the breakdown curve, or `null` to make the ride reliable. */ readonly breakdown?: Partial | null; } /** What a service cost the caller. */ export interface RideServiceResult { readonly id: string; /** Money to debit; `0` when the ride had no wear to clear. */ readonly cost: number; /** Completed cycles the service cleared. */ readonly wear: number; } /** Serializable park state: the clock, each ride's cycle position, wear and line. */ export interface RideSystemSnapshot { elapsed: number; rides: { definition: RideDefinition; phase: RidePhase; timer: number; riders: string[]; cycles: number; wear: number; breakdowns: number; repairSpend: number; serviceSpend: number; boarded: number; queue: WaitQueueSnapshot; }[]; } /** Everything a ride system needs. */ export interface RideSystemConfig { readonly rides: readonly RideDefinition[]; /** `[0,1)` source for breakdown rolls; pass `ctx.rng`. Only read when a ride has a breakdown curve. */ readonly rng?: () => number; /** Seed seconds per guest for a line that has not boarded yet. Default 8. */ readonly queueSeedServiceSeconds?: number; /** Register a devtools probe under this name so the perf overlay shows the park. */ readonly devtoolsName?: string; } /** * The park's rides: each runs its own load → ride → unload cycle at its own capacity, takes guests * off its own line, wears out against its own breakdown curve, and reports its own repairs and * services. The clock is internal and only `tick` moves it, so a replay, a save and a test read the * same cycle. * * Cost per tick is bounded by the ride count plus the guests one cycle boards. */ export interface RideSystem { /** Seconds this system has ticked. */ now(): number; /** Bumped by `retune` and `restore`, so a chooser can rebuild stale options. */ revision(): number; rideIds(): readonly string[]; /** Live definitions, including any retuning. */ definitions(): readonly RideDefinition[]; ride(id: string): RideView | null; /** Every ride's live state. Allocates — debug and decision paths. */ rides(): readonly RideView[]; queue(id: string): WaitQueue | null; /** Put a guest at the back of a ride's line; false on an unknown ride or a guest already waiting. */ join(id: string, guestId: string): boolean; leave(id: string, guestId: string): boolean; /** Seconds a guest joining now would wait; `0` on an unknown ride. */ waitSeconds(id: string): number; /** Advance every ride's cycle by `dt` seconds. */ tick(dt: number, sink?: RideTickSink): void; /** Pay a ride's maintenance cost and clear its wear; nothing to pay when it has none. */ service(id: string): RideServiceResult | null; retune(id: string, patch: RideRetune): boolean; stats(): RideStats; snapshot(): RideSystemSnapshot; restore(next: RideSystemSnapshot): void; /** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */ dispose(): void; } /** A ride's breakdown curve a park starts from when it has none and retunes only part of one. */ export declare const DEFAULT_RIDE_BREAKDOWN: RideBreakdown; /** * Create the rides a park runs. Each ride cycles on its own data, boards from its own line up to its * own capacity, wears against its own breakdown curve and reports its repairs through the sink, so * the caller keeps the wallet, the bodies and the visuals. * * @capability ride-sim rides with capacity, cycles, queues, ratings, breakdowns and maintenance */ export declare function createRideSystem(config: RideSystemConfig): RideSystem; /** The rating scale a park's taste curves are normalized against. */ export interface RideRatingRange { readonly excitementMax: number; readonly intensityMax: number; readonly nauseaMax: number; } /** A 0–10 rating scale, the usual shape of a ride catalog. Override it when a park rates differently. */ export declare const DEFAULT_RIDE_RATING_RANGE: RideRatingRange; /** How empty a bored guest's fun need has to be before a ride starts to pull, by default. */ export declare const DEFAULT_RIDE_FUN_CURVE: ResponseCurve; /** * What one guest wants from a ride: the intensity and nausea they are comfortable with, and how long * they will stand in line. All three are the guest's own data. */ export interface RideTaste { /** Intensity rating this guest is happy with; a harder ride loses appeal past it. */ readonly intensityComfort: number; /** Nausea rating this guest tolerates. */ readonly nauseaComfort: number; /** Seconds of queue this guest accepts; at that wait a ride's appeal is spent. */ readonly queueTolerance: number; } /** Policy a park might vary when it turns taste and ride data into utility options. */ export interface RideChoicePolicy { /** Rating scale the taste curves normalize against. Default {@link DEFAULT_RIDE_RATING_RANGE}. */ readonly range?: RideRatingRange; /** Declared need whose emptiness makes every ride more attractive — a bored guest. */ readonly funNeed?: string; /** Curve sampling that need. Default {@link DEFAULT_RIDE_FUN_CURVE}. */ readonly funCurve?: ResponseCurve; /** Weight on the fun term. Default `1`. */ readonly funWeight?: number; } /** * One ride's standing appeal to one guest, before queue cost. Excitement draws, force and nausea * push back — what a guest's own card or thought bubble can show. * * @capability ride-appeal how much one guest likes one ride, from its ratings and their taste */ export declare function rideAppeal(ratings: RideRatings, taste: RideTaste, range?: RideRatingRange): number; /** * Compile a guest's taste and the park's ride data into utility options, so the guest's pick comes * from `createUtilityBrain` like every other decision it makes. Ride choice is one more set of * options a guest can score, not a separate scoring function. * * @capability ride-choice guests pick rides by taste and queue tolerance through the utility brain */ export declare function rideChoiceOptions(rides: readonly RideDefinition[], taste: RideTaste, policy?: RideChoicePolicy): UtilityOption[]; /** The live queue a ride-choice candidate is costed against. {@link RideSystem} satisfies this. */ export interface RideWaitSource { rideIds(): readonly string[]; waitSeconds(id: string): number; } /** * Cost each ride by the wait its own line reports, so a guest weighs a ride by seconds in line * rather than by a fixed order. `out` is reused for the array; the candidate records are not. * * @capability ride-choice guests pick rides by taste and queue tolerance through the utility brain */ export declare function rideChoiceCandidates(source: RideWaitSource, out?: UtilityCandidate[]): UtilityCandidate[]; /** * Score points per second of queue wait: a guest's own tolerance is the denominator. A guest who * merges rides into a brain that also scores food and exits uses this as its `costWeight`. * * @capability ride-choice guests pick rides by taste and queue tolerance through the utility brain */ export declare function rideQueueCostWeight(taste: RideTaste): number; /** Serializable chooser state: the guest's taste and the brain's held pick. */ export interface RideChooserSnapshot { taste: RideTaste; brain: UtilityBrainSnapshot; } /** Everything a ride chooser needs: the park, the guest's taste, and the guest's needs. */ export interface RideChooserConfig { readonly system: RideSystem; readonly taste: RideTaste; readonly needs: NeedSet; readonly policy?: RideChoicePolicy; /** Score margin a rival ride must clear before the guest changes their mind. Default `0.05`. */ readonly switchMargin?: number; /** Seconds a pick is held before a rival may replace it. Default `0`. */ readonly minHoldSeconds?: number; readonly rng?: () => number; readonly devtoolsName?: string; } /** * One guest's ride choice over one park: taste becomes utility options, each ride is costed by its * own line's wait, and the pick comes from the shared utility brain — so the same brain that picks * food picks rides, and a guest's queue tolerance is a weight rather than a rule. */ export interface RideChooser { /** Pick a ride for this guest now, or `null` when the park has no rides. */ choose(): string | null; /** Need-driven appeal of one ride for this guest, queue excluded. Throws on an unknown ride. */ scoreOf(rideId: string): number; /** Decay needs and age the hold timer; call once per tick before `choose`. */ advance(dt: number): void; /** Replace this guest's taste, keeping the held pick when that ride still exists. */ setTaste(taste: RideTaste): void; snapshot(): RideChooserSnapshot; restore(next: RideChooserSnapshot): void; /** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */ dispose(): void; } /** * Create one guest's ride choice over a park. Options are rebuilt when the park is retuned, so a * coaster whose track changes its ratings is re-scored without rebuilding the guest. * * @capability ride-choice guests pick rides by taste and queue tolerance through the utility brain */ export declare function createRideChooser(config: RideChooserConfig): RideChooser;