minimojs 1.0.0-alpha.20 → 1.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,16 @@ import { DrawSprite, Game } from "./minimo.js";
2
2
  /**
3
3
  * Visual source used by the arcade racer module.
4
4
  *
5
- * Sources can be either emoji-based or backed by a preloaded MinimoJS image key.
5
+ * A source is backed either by a preloaded MinimoJS image key, which is the
6
+ * first option to reach for, or by an emoji glyph.
6
7
  */
7
- export type ArcadeRacerVisualSource = ArcadeRacerEmojiSource | ArcadeRacerImageSource;
8
+ export type ArcadeRacerVisualSource = ArcadeRacerImageSource | ArcadeRacerEmojiSource;
8
9
  /**
9
10
  * Emoji-backed visual source.
11
+ *
12
+ * An emoji is rasterized with the player's own system emoji font, so it renders
13
+ * differently across platforms. Prefer {@link ArcadeRacerImageSource} when the
14
+ * look matters; this remains the quickest way to get a vehicle on the road.
10
15
  */
11
16
  export interface ArcadeRacerEmojiSource {
12
17
  type: "emoji";
@@ -58,12 +63,26 @@ export interface ArcadeRacerPerformanceOptions {
58
63
  sixtyToZeroSeconds: number;
59
64
  /** Target top speed used by mph helpers. Default: `145`. */
60
65
  topSpeedMph: number;
61
- /** Steering multiplier when the car is nearly stopped. Default: `0.18`. */
66
+ /**
67
+ * Steering authority when the car is nearly stopped.
68
+ * Higher values make low-speed lane changes build more quickly.
69
+ * Default: `0.9`.
70
+ */
62
71
  steeringLowSpeedScale: number;
63
- /** Steering multiplier near top speed. Default: `0.78`. */
72
+ /**
73
+ * Steering authority near top speed.
74
+ * Lower values make fast lane changes slower and heavier.
75
+ * Default: `0.34`.
76
+ */
64
77
  steeringHighSpeedScale: number;
65
- /** Curve shaping used for steering interpolation. Default: `1.5`. */
78
+ /** Curve shaping used for steering authority interpolation. Default: `1.8`. */
66
79
  steeringSpeedCurvePower: number;
80
+ /**
81
+ * Damping applied to lateral velocity.
82
+ * Higher values make the car settle more quickly after steering input.
83
+ * Default: `6.8`.
84
+ */
85
+ lateralDamping: number;
67
86
  }
68
87
  /**
69
88
  * Horizon layer settings for heading-based background cards.
@@ -170,6 +189,26 @@ export interface ArcadeRacerGroundFillOptions {
170
189
  */
171
190
  color?: string | null;
172
191
  }
192
+ /**
193
+ * Optional grouping of zero-based lane indices by travel direction.
194
+ *
195
+ * Lane indices are counted from left to right across the rendered road. For
196
+ * example, with `laneCount: 4`, the lanes are indexed as `0, 1, 2, 3`.
197
+ *
198
+ * If only one direction is provided, the other direction automatically uses the
199
+ * remaining lanes. When omitted entirely, direction-aware helpers fall back to
200
+ * all lanes.
201
+ */
202
+ export interface ArcadeRacerLaneDirectionsOptions {
203
+ /**
204
+ * Lanes reserved for vehicles moving in the same direction as the player.
205
+ */
206
+ same?: number[];
207
+ /**
208
+ * Lanes reserved for vehicles moving toward the player.
209
+ */
210
+ oncoming?: number[];
211
+ }
173
212
  /**
174
213
  * Optional elevation settings for path-based track primitives.
175
214
  */
@@ -241,6 +280,341 @@ export interface ArcadeRacerDebugOptions {
241
280
  /** Debug stroke width in screen pixels. Default: `2`. */
242
281
  lineWidth: number;
243
282
  }
283
+ /**
284
+ * Projected road point used by road drawers.
285
+ */
286
+ export interface ArcadeRacerRoadProjection {
287
+ /** Horizontal screen-space center of the projected road slice. */
288
+ x: number;
289
+ /** Vertical screen-space anchor of the projected road slice. */
290
+ y: number;
291
+ /** Projected road width in screen pixels at this depth. */
292
+ roadWidth: number;
293
+ /** Perspective scale relative to the near road width. */
294
+ scale: number;
295
+ /** Normalized depth factor used internally by the projection in range `[0, 1]`. */
296
+ t: number;
297
+ }
298
+ /**
299
+ * Exact projected road and shoulder bounds at a specific world distance.
300
+ */
301
+ export interface ArcadeRacerRoadSurfaceProjection {
302
+ /** Exact wrapped track distance used for this projection. */
303
+ trackDistance: number;
304
+ /** Exact unwrapped world distance used for this projection. */
305
+ absoluteDistance: number;
306
+ /** Projected road center and width at this distance. */
307
+ projection: ArcadeRacerRoadProjection;
308
+ /** Shoulder width in screen pixels at this distance. */
309
+ shoulderWidth: number;
310
+ /** Left paved-road boundary at this distance. */
311
+ leftRoadX: number;
312
+ /** Right paved-road boundary at this distance. */
313
+ rightRoadX: number;
314
+ /** Left outer shoulder boundary at this distance. */
315
+ leftOuterX: number;
316
+ /** Right outer shoulder boundary at this distance. */
317
+ rightOuterX: number;
318
+ }
319
+ /**
320
+ * Track sample data exposed to road drawers.
321
+ */
322
+ export interface ArcadeRacerRoadDrawSample {
323
+ /** Lateral road offset in track space at this sample. */
324
+ lateralOffset: number;
325
+ /** Elevation value used by hills and dips at this sample. */
326
+ elevation: number;
327
+ /** Instantaneous curvature value at this sample. */
328
+ curve: number;
329
+ /** Instantaneous hill gradient helper value at this sample. */
330
+ hill: number;
331
+ /** World-space X value for path-based tracks. */
332
+ worldX: number;
333
+ /** World-space Y / forward travel value for path-based tracks. */
334
+ worldY: number;
335
+ /** Heading in radians of the road center path at this sample. */
336
+ heading: number;
337
+ }
338
+ /**
339
+ * Single projected lane-marker slice inside a road strip.
340
+ */
341
+ export interface ArcadeRacerRoadLaneMarkerStrip {
342
+ /** Marker center X at the far edge of the strip. */
343
+ farX: number;
344
+ /** Marker center X at the near edge of the strip. */
345
+ nearX: number;
346
+ /** Marker width at the far edge of the strip. */
347
+ farWidth: number;
348
+ /** Marker width at the near edge of the strip. */
349
+ nearWidth: number;
350
+ }
351
+ /**
352
+ * Single projected road strip ready to be painted by a road drawer.
353
+ */
354
+ export interface ArcadeRacerRoadDrawStrip {
355
+ /** Zero-based visible strip index within the current frame. */
356
+ index: number;
357
+ /** Stable stripe counter used for banding and continuity across frames. */
358
+ stripeIndex: number;
359
+ /** Absolute unwrapped distance represented by the middle of this strip. */
360
+ absoluteDistance: number;
361
+ /** Wrapped track distance represented by the middle of this strip. */
362
+ trackDistance: number;
363
+ /** Absolute distance at the far edge of the strip. */
364
+ farDistance: number;
365
+ /** Absolute distance at the near edge of the strip. */
366
+ nearDistance: number;
367
+ /** Projected road state at the far edge of the strip. */
368
+ farProjection: ArcadeRacerRoadProjection;
369
+ /** Projected road state at the near edge of the strip. */
370
+ nearProjection: ArcadeRacerRoadProjection;
371
+ /** Track sample used at the far edge of the strip. */
372
+ farSample: ArcadeRacerRoadDrawSample;
373
+ /** Track sample used at the near edge of the strip. */
374
+ nearSample: ArcadeRacerRoadDrawSample;
375
+ /** Top scanline of the strip in screen space. */
376
+ farY: number;
377
+ /** Bottom scanline of the strip in screen space. */
378
+ nearY: number;
379
+ /** Alternating band index used by the default grass drawer. */
380
+ grassBandIndex: number;
381
+ /** Alternating band index used by the default shoulder drawer. */
382
+ shoulderBandIndex: number;
383
+ /** Alternating band index used by the default road drawer. */
384
+ roadBandIndex: number;
385
+ /** Shoulder width at the far edge of the strip. */
386
+ shoulderFar: number;
387
+ /** Shoulder width at the near edge of the strip. */
388
+ shoulderNear: number;
389
+ /** Far-left outer shoulder boundary. */
390
+ leftOuterFar: number;
391
+ /** Far-right outer shoulder boundary. */
392
+ rightOuterFar: number;
393
+ /** Near-left outer shoulder boundary. */
394
+ leftOuterNear: number;
395
+ /** Near-right outer shoulder boundary. */
396
+ rightOuterNear: number;
397
+ /** Far-left paved-road boundary. */
398
+ leftRoadFar: number;
399
+ /** Far-right paved-road boundary. */
400
+ rightRoadFar: number;
401
+ /** Near-left paved-road boundary. */
402
+ leftRoadNear: number;
403
+ /** Near-right paved-road boundary. */
404
+ rightRoadNear: number;
405
+ /** Precomputed lane-marker quads available for this strip. */
406
+ laneMarkers: ArcadeRacerRoadLaneMarkerStrip[];
407
+ }
408
+ /**
409
+ * Frame-level road drawing data resolved by the engine.
410
+ */
411
+ export interface ArcadeRacerRoadDrawFrame {
412
+ /** Render surface width in pixels. */
413
+ width: number;
414
+ /** Render surface height in pixels. */
415
+ height: number;
416
+ /** Screen-space horizon line used by the road renderer. */
417
+ horizonY: number;
418
+ /** Current absolute player distance. */
419
+ distanceState: number;
420
+ /** Forward draw distance used to build the visible strips. */
421
+ drawDistance: number;
422
+ /** Configured number of visible strips in the frame. */
423
+ stripCount: number;
424
+ /** Number of lane bands visible on the road. */
425
+ laneCount: number;
426
+ /** Track sample at the player's current position. */
427
+ currentSample: ArcadeRacerRoadDrawSample;
428
+ /** Active theme at draw time. */
429
+ theme: ArcadeRacerTheme;
430
+ /** Ordered visible road strips for this frame. */
431
+ strips: readonly ArcadeRacerRoadDrawStrip[];
432
+ /**
433
+ * Projects exact road and shoulder bounds for an arbitrary absolute distance.
434
+ *
435
+ * Use this when a custom drawer needs stable world-anchored props that
436
+ * should not depend on which visible strip they happen to fall inside.
437
+ */
438
+ projectDistance: (absoluteDistance: number) => ArcadeRacerRoadSurfaceProjection;
439
+ }
440
+ /**
441
+ * Strategy used by the racer to paint projected road strips.
442
+ */
443
+ export interface ArcadeRacerRoadDrawer {
444
+ /**
445
+ * Paints a single projected strip of road geometry.
446
+ *
447
+ * The engine resolves strip geometry before calling this method.
448
+ */
449
+ drawStrip(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
450
+ /**
451
+ * Optional pass that runs after all strips have been painted.
452
+ *
453
+ * The default drawer uses this for the lower-screen vignette.
454
+ */
455
+ drawOverlay?(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame): void;
456
+ }
457
+ /**
458
+ * Distance range that swaps the active road drawer for matching strips.
459
+ *
460
+ * Ranges are evaluated in wrapped track distance. When multiple ranges match
461
+ * the same strip, the most recently added range wins.
462
+ */
463
+ export interface ArcadeRacerRoadDrawerRange {
464
+ /** Inclusive wrapped start distance of the styled span. */
465
+ from: number;
466
+ /** Exclusive wrapped end distance of the styled span. */
467
+ to: number;
468
+ /** Drawer used for strips whose wrapped distance falls inside the span. */
469
+ drawer: ArcadeRacerRoadDrawer;
470
+ }
471
+ /**
472
+ * Side of the off-road grass area.
473
+ */
474
+ export type ArcadeRacerRoadSide = "left" | "right";
475
+ /**
476
+ * Construction options for {@link CropRoadDrawer}.
477
+ *
478
+ * Use tighter `columnSpacing` values for dense crops and larger values for
479
+ * sparser layouts. `columnSpacingJitter` and height ratios add small natural
480
+ * variation without changing the overall planting pattern.
481
+ */
482
+ export interface ArcadeRacerCropRoadDrawerOptions {
483
+ /** Game instance used to resolve image textures registered through MinimoJS. */
484
+ game: Game;
485
+ /** Visual used to paint each crop instance. */
486
+ visual: ArcadeRacerVisualSource;
487
+ /** Roadside grass bands that should receive crop instances. Default: both sides. */
488
+ sides?: readonly ArcadeRacerRoadSide[];
489
+ /**
490
+ * Whether the drawer should repaint the grass band under the crops.
491
+ *
492
+ * Enable this when the crop section needs a custom soil color instead of the
493
+ * road theme's default grass colors.
494
+ * Default: `false`.
495
+ */
496
+ paintGround?: boolean;
497
+ /** Fill color used for the crop soil when `paintGround` is enabled. Default: `#7b5a32`. */
498
+ groundColor?: string;
499
+ /** Distance between crop rows along the road, in world units. Default: `82`. */
500
+ rowSpacing?: number;
501
+ /**
502
+ * Base spacing between crop columns across the grass, in world units.
503
+ *
504
+ * Lower values create dense plantings; higher values create sparse ones.
505
+ * Default: `34`.
506
+ */
507
+ columnSpacing?: number;
508
+ /**
509
+ * Additional random variation applied to spacing between neighboring plants,
510
+ * in world units. Default: `0`.
511
+ */
512
+ columnSpacingJitter?: number;
513
+ /** Starting offset from the road shoulder before the first plant is placed. Default: `42`. */
514
+ columnInset?: number;
515
+ /**
516
+ * Alternate-row lateral shift, in world units.
517
+ *
518
+ * Defaults to half of `columnSpacing`.
519
+ */
520
+ rowShift?: number;
521
+ /** Minimum random height multiplier applied to crop instances. Default: `0.85`. */
522
+ minHeightRatio?: number;
523
+ /** Maximum random height multiplier applied to crop instances. Default: `1.35`. */
524
+ maxHeightRatio?: number;
525
+ /**
526
+ * Base crop height relative to projected road width.
527
+ *
528
+ * The final height is `roadWidth * baseHeightScale * randomHeightRatio`.
529
+ * Default: `0.12`.
530
+ */
531
+ baseHeightScale?: number;
532
+ /** Minimum on-screen draw height in pixels. Default: `16`. */
533
+ minDrawHeight?: number;
534
+ /** Alpha applied to each crop sprite. Default: `0.9`. */
535
+ alpha?: number;
536
+ /** Whether a dark base shadow should be painted under each crop. Default: `true`. */
537
+ drawBaseShadow?: boolean;
538
+ /** Fill color used by crop base shadows. Default: `rgba(68, 38, 18, 1)`. */
539
+ baseShadowColor?: string;
540
+ /** Alpha applied to crop base shadows. Default: `0.28`. */
541
+ baseShadowAlpha?: number;
542
+ /** Horizontal shadow radius relative to crop width. Default: `0.3`. */
543
+ baseShadowRadiusScale?: number;
544
+ /** Vertical shadow radius relative to horizontal shadow radius. Default: `0.42`. */
545
+ baseShadowHeightScale?: number;
546
+ }
547
+ /**
548
+ * Default road drawer used by {@link ArcadeRacerEngine}.
549
+ *
550
+ * Extend this class when you want to keep the built-in pseudo-3D road geometry
551
+ * and only customize colors, lane markings, textures, or strip-level effects.
552
+ */
553
+ export declare class DefaultArcadeRacerRoadDrawer implements ArcadeRacerRoadDrawer {
554
+ /** Paints the full default strip in four stages: ground, shoulders, road, and lane markers. */
555
+ drawStrip(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
556
+ /** Draws the default post-road vignette overlay. */
557
+ drawOverlay(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame): void;
558
+ /** Draws the off-road ground area for one strip. */
559
+ protected drawGround(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
560
+ /** Draws the shoulder band for one strip. */
561
+ protected drawShoulders(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
562
+ /** Draws the paved road surface for one strip. */
563
+ protected drawRoadSurface(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
564
+ /** Draws all lane markers for one strip when enabled. */
565
+ protected drawLaneMarkers(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
566
+ /** Returns the fill color used for the ground area of a strip. */
567
+ protected getGrassColor(frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): string;
568
+ /** Returns the fill color used for the shoulder area of a strip. */
569
+ protected getShoulderColor(frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): string;
570
+ /** Returns the fill color used for the paved road of a strip. */
571
+ protected getRoadColor(frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): string;
572
+ /** Returns the color used for lane-marker quads. */
573
+ protected getLaneMarkerColor(frame: ArcadeRacerRoadDrawFrame, _strip: ArcadeRacerRoadDrawStrip): string;
574
+ /** Controls whether lane markers should be painted for the strip. */
575
+ protected shouldDrawLaneMarkers(_frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): boolean;
576
+ /** Helper that fills a four-point polygon, useful for custom subclasses. */
577
+ protected drawQuad(ctx: CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: string): void;
578
+ }
579
+ /**
580
+ * Road drawer that forwards each paint call to multiple child drawers.
581
+ *
582
+ * Drawers are executed in array order, which makes this useful for layering a
583
+ * base road style with extra passes such as crops, palm fields, rocks, or
584
+ * tunnel accents.
585
+ */
586
+ export declare class CompositeRoadDrawer implements ArcadeRacerRoadDrawer {
587
+ /** Ordered child drawers that will be invoked for every strip and overlay pass. */
588
+ readonly drawers: ArcadeRacerRoadDrawer[];
589
+ /**
590
+ * Creates a composite drawer from the provided children.
591
+ *
592
+ * The order matters: earlier drawers paint first and later drawers paint on top.
593
+ */
594
+ constructor(drawers?: ArcadeRacerRoadDrawer[]);
595
+ /** Paints the strip by forwarding the call to each child drawer in order. */
596
+ drawStrip(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
597
+ /** Forwards the optional overlay pass to each child drawer that implements it. */
598
+ drawOverlay(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame): void;
599
+ /** Appends another child drawer to the composite. */
600
+ addDrawer(drawer: ArcadeRacerRoadDrawer): void;
601
+ /** Removes all child drawers from the composite. */
602
+ clearDrawers(): void;
603
+ }
604
+ /**
605
+ * Configurable roadside crop drawer.
606
+ *
607
+ * This drawer paints repeated crop sprites across the left and/or right grass
608
+ * bands. Use it inside {@link CompositeRoadDrawer} together with
609
+ * {@link DefaultArcadeRacerRoadDrawer} when you want the built-in road style
610
+ * plus crop layouts on top.
611
+ */
612
+ export declare class CropRoadDrawer extends DefaultArcadeRacerRoadDrawer {
613
+ /** Creates a crop drawer with configurable density, spacing, and height variation. */
614
+ constructor(options: ArcadeRacerCropRoadDrawerOptions);
615
+ /** Paints crop ground and crop instances for the configured roadside bands. */
616
+ drawStrip(ctx: CanvasRenderingContext2D, frame: ArcadeRacerRoadDrawFrame, strip: ArcadeRacerRoadDrawStrip): void;
617
+ }
244
618
  /**
245
619
  * Construction options for {@link ArcadeRacerEngine}.
246
620
  */
@@ -265,8 +639,18 @@ export interface ArcadeRacerEngineOptions {
265
639
  roadFarWidth?: number;
266
640
  /** Render layer used by the internal road sprite. Default: `0`. */
267
641
  layer?: number;
268
- /** Number of visible driving lanes. Default: `3`. */
642
+ /**
643
+ * Number of visible driving lanes used by the road renderer.
644
+ * Default: `3`.
645
+ */
269
646
  laneCount?: number;
647
+ /**
648
+ * Optional directional grouping for zero-based lane indices.
649
+ *
650
+ * Use this when you want the engine to know which lanes belong to same-
651
+ * direction traffic versus oncoming traffic.
652
+ */
653
+ laneDirections?: ArcadeRacerLaneDirectionsOptions;
270
654
  /** Initial player lane position in range `[-1, 1]`. Default: `0`. */
271
655
  playerLane?: number;
272
656
  /**
@@ -336,6 +720,24 @@ export interface ArcadeRacerEngineOptions {
336
720
  * Default: `3.2`.
337
721
  */
338
722
  offRoadBrakeRate?: number;
723
+ /**
724
+ * Multiplier applied to acceleration while the player is off-road.
725
+ *
726
+ * `1` keeps full acceleration response. Lower values make the car feel like
727
+ * it is struggling for traction on grass or dirt.
728
+ *
729
+ * Default: `0.28`.
730
+ */
731
+ offRoadAccelerationScale?: number;
732
+ /**
733
+ * Blend used to soften the vertical displacement of real collision bodies on hills.
734
+ *
735
+ * `0` keeps the projected body where road geometry places it. `1` anchors it
736
+ * fully to the visible sprite bottom. `0.5` is a balanced midpoint.
737
+ *
738
+ * Default: `0.5`.
739
+ */
740
+ realCollisionVerticalShiftBlend?: number;
339
741
  /** Optional partial performance / handling override for the player car. */
340
742
  performance?: Partial<ArcadeRacerPerformanceOptions>;
341
743
  /** Optional partial heading-based horizon override. */
@@ -346,6 +748,10 @@ export interface ArcadeRacerEngineOptions {
346
748
  debug?: boolean | Partial<ArcadeRacerDebugOptions>;
347
749
  /** Optional partial theme override. */
348
750
  theme?: Partial<ArcadeRacerTheme>;
751
+ /** Optional global road drawer. Defaults to {@link DefaultArcadeRacerRoadDrawer}. */
752
+ roadDrawer?: ArcadeRacerRoadDrawer;
753
+ /** Optional per-range road drawer overrides evaluated in wrapped track distance. */
754
+ roadDrawerRanges?: ArcadeRacerRoadDrawerRange[];
349
755
  }
350
756
  /**
351
757
  * Billboard configuration.
@@ -362,6 +768,21 @@ export interface ArcadeRacerBillboardConfig {
362
768
  side?: "left" | "right";
363
769
  /** Additional roadside offset in logical pixels. Default: `48`. */
364
770
  offset?: number;
771
+ /**
772
+ * Horizontal anchor inside the billboard image in normalized `[0, 1]` space.
773
+ *
774
+ * `0` means the left edge of the image, `0.5` the center, and `1` the right
775
+ * edge. This is useful for wide structures that start on one roadside and
776
+ * extend inward across the road, such as bridges, arches, poles, or
777
+ * inward-leaning fences. Default: `0.5`.
778
+ */
779
+ anchorX?: number;
780
+ /**
781
+ * Vertical anchor inside the billboard image in normalized `[0, 1]` space.
782
+ *
783
+ * `0` means the top edge, `0.5` the middle, and `1` the bottom. Default: `1`.
784
+ */
785
+ anchorY?: number;
365
786
  /** Optional multiplier applied to the projected scale. Default: `1`. */
366
787
  baseScale?: number;
367
788
  /** Number of copies to create. Default: `1`. */
@@ -409,12 +830,200 @@ export interface ArcadeRacerTrafficConfig {
409
830
  * Collision information reported when the player hits a traffic vehicle.
410
831
  */
411
832
  export interface ArcadeRacerTrafficCollision {
833
+ /** Id of the traffic vehicle involved in the collision. */
412
834
  trafficId: string;
835
+ /** Forward/back distance difference between player and traffic body centers. */
413
836
  distanceDelta: number;
837
+ /** Lateral lane-space difference between player and traffic body centers. */
414
838
  laneDelta: number;
839
+ /** Absolute traffic distance at the moment of impact. */
415
840
  trafficDistance: number;
841
+ /** Traffic lane-space position at the moment of impact. */
416
842
  trafficLane: number;
417
843
  }
844
+ /**
845
+ * Weighted traffic profile used by {@link ArcadeRacerTrafficManager}.
846
+ */
847
+ export interface ArcadeRacerTrafficProfile {
848
+ /** Vehicle visual to spawn for this profile. */
849
+ visual: ArcadeRacerVisualSource;
850
+ /** Relative selection weight. Default: `1`. */
851
+ weight?: number;
852
+ /**
853
+ * Travel direction for the spawned vehicle.
854
+ * `"same"` keeps traffic moving with the player, `"oncoming"` sends it toward
855
+ * the player, and `"either"` lets the manager decide using `oncomingChance`.
856
+ * Default: `"same"`.
857
+ */
858
+ direction?: "same" | "oncoming" | "either";
859
+ /** Optional fixed lane for this profile. */
860
+ lane?: number;
861
+ /** Optional allowed lane list for this profile. Falls back to manager lanes. */
862
+ lanes?: number[];
863
+ /** Fixed traffic speed in mph. Overrides the min/max range when provided. */
864
+ speedMph?: number;
865
+ /** Minimum traffic speed in mph when using a random speed range. */
866
+ speedMphMin?: number;
867
+ /** Maximum traffic speed in mph when using a random speed range. */
868
+ speedMphMax?: number;
869
+ /** Fixed scale multiplier for the spawned vehicle. */
870
+ baseScale?: number;
871
+ /** Minimum random scale multiplier. */
872
+ baseScaleMin?: number;
873
+ /** Maximum random scale multiplier. */
874
+ baseScaleMax?: number;
875
+ /** Collision body width in local visual pixels before scale is applied. */
876
+ bodyWidth?: number;
877
+ /** Collision body length in local visual pixels before scale is applied. */
878
+ bodyLength?: number;
879
+ /** Horizontal collision body offset in local visual pixels. */
880
+ bodyOffsetX?: number;
881
+ /** Vertical collision body offset in local visual pixels. */
882
+ bodyOffsetY?: number;
883
+ }
884
+ /**
885
+ * Spawn event emitted by {@link ArcadeRacerTrafficManager}.
886
+ */
887
+ export interface ArcadeRacerTrafficSpawnEvent {
888
+ /** Generated traffic id assigned by the engine. */
889
+ id: string;
890
+ /** Lane-space position used for the spawned vehicle. */
891
+ lane: number;
892
+ /** Absolute spawn distance used for the new vehicle. */
893
+ distance: number;
894
+ /** Spawn speed in engine world units per second. */
895
+ speed: number;
896
+ /** Spawn speed converted to mph for convenience. */
897
+ speedMph: number;
898
+ /** Whether the vehicle is traveling toward the player. */
899
+ oncoming: boolean;
900
+ /** Profile that produced this spawn. */
901
+ profile: ArcadeRacerTrafficProfile;
902
+ }
903
+ /**
904
+ * Despawn event emitted by {@link ArcadeRacerTrafficManager}.
905
+ */
906
+ export interface ArcadeRacerTrafficDespawnEvent {
907
+ /** Id of the traffic vehicle being removed. */
908
+ id: string;
909
+ /** Why the manager decided to remove the vehicle. */
910
+ reason: "behind" | "ahead" | "cleared";
911
+ /** Profile that originally produced the vehicle. */
912
+ profile: ArcadeRacerTrafficProfile;
913
+ }
914
+ /**
915
+ * Configuration for {@link ArcadeRacerTrafficManager}.
916
+ */
917
+ export interface ArcadeRacerTrafficManagerOptions {
918
+ /** Traffic profiles that may be spawned by the manager. */
919
+ profiles: ArcadeRacerTrafficProfile[];
920
+ /** Whether spawning is active. Default: `true`. */
921
+ enabled?: boolean;
922
+ /** Maximum number of manager-owned vehicles alive at once. Default: `14`. */
923
+ maxActive?: number;
924
+ /** Initial number of vehicles to populate immediately. Default: `6`. */
925
+ initialActive?: number;
926
+ /** Time in seconds between spawn attempts. Default: `0.45`. */
927
+ spawnInterval?: number;
928
+ /** Minimum distance ahead of the player where a vehicle may spawn. Default: `900`. */
929
+ spawnAheadMin?: number;
930
+ /** Maximum distance ahead of the player where a vehicle may spawn. Default: `2200`. */
931
+ spawnAheadMax?: number;
932
+ /** Minimum distance behind the player where a faster same-direction vehicle may spawn. Default: `320`. */
933
+ spawnBehindMin?: number;
934
+ /** Maximum distance behind the player where a faster same-direction vehicle may spawn. Default: `760`. */
935
+ spawnBehindMax?: number;
936
+ /** Distance behind the player at which a managed vehicle is despawned. Default: `260`. */
937
+ despawnBehindDistance?: number;
938
+ /** Distance ahead of the player at which a managed vehicle is despawned. Default: `2800`. */
939
+ despawnAheadDistance?: number;
940
+ /** Minimum longitudinal gap between same-lane vehicles. Default: `220`. */
941
+ minGapDistance?: number;
942
+ /**
943
+ * Default lane choices used when a profile does not define its own lanes.
944
+ *
945
+ * When omitted, the manager uses the engine lane layout. If the engine was
946
+ * configured with `laneDirections`, same-direction profiles use those lanes
947
+ * and oncoming profiles use the opposing lanes automatically.
948
+ */
949
+ lanePositions?: number[];
950
+ /** Chance used when a profile has `direction: "either"`. Default: `0.35`. */
951
+ oncomingChance?: number;
952
+ /**
953
+ * Whether same-direction vehicles that are faster than the player should be
954
+ * allowed to enter from behind instead of popping into view ahead.
955
+ * Default: `true`.
956
+ */
957
+ fasterTrafficSpawnsBehind?: boolean;
958
+ /** Optional callback fired after a traffic vehicle is spawned. */
959
+ onSpawn?: (event: ArcadeRacerTrafficSpawnEvent) => void;
960
+ /** Optional callback fired after a manager-owned vehicle is removed. */
961
+ onDespawn?: (event: ArcadeRacerTrafficDespawnEvent) => void;
962
+ }
963
+ /**
964
+ * Fixed competitor description used by {@link ArcadeRacerRaceManager}.
965
+ */
966
+ export interface ArcadeRacerRaceCompetitorProfile {
967
+ /** Display name used in standings and race UI. */
968
+ name: string;
969
+ /** Vehicle visual used for this competitor. */
970
+ visual: ArcadeRacerVisualSource;
971
+ /** Preferred lane for the competitor. */
972
+ lane?: number;
973
+ /** Candidate lanes used when `lane` is omitted. */
974
+ lanes?: number[];
975
+ /** Initial race distance relative to the start line. Default: `0`. */
976
+ startDistance?: number;
977
+ /** Fixed race pace for this competitor in mph. */
978
+ speedMph?: number;
979
+ /** Minimum random race pace in mph. */
980
+ speedMphMin?: number;
981
+ /** Maximum random race pace in mph. */
982
+ speedMphMax?: number;
983
+ /** Fixed scale multiplier for the competitor vehicle. */
984
+ baseScale?: number;
985
+ /** Collision body width in local visual pixels before scale is applied. */
986
+ bodyWidth?: number;
987
+ /** Collision body length in local visual pixels before scale is applied. */
988
+ bodyLength?: number;
989
+ /** Horizontal collision body offset in local visual pixels. */
990
+ bodyOffsetX?: number;
991
+ /** Vertical collision body offset in local visual pixels. */
992
+ bodyOffsetY?: number;
993
+ }
994
+ /**
995
+ * Single race standing entry returned by {@link ArcadeRacerRaceManager.getStandings}.
996
+ */
997
+ export interface ArcadeRacerRaceStanding {
998
+ /** Internal competitor id, or `"player"` for the player row. */
999
+ id: string;
1000
+ /** Display name shown in standings. */
1001
+ name: string;
1002
+ /** Distance progressed toward the finish line. */
1003
+ distance: number;
1004
+ /** Whether this competitor has already crossed the finish line. */
1005
+ finished: boolean;
1006
+ /** Whether this row corresponds to the player. */
1007
+ isPlayer: boolean;
1008
+ /** 1-based race position after sorting by progress. */
1009
+ position: number;
1010
+ }
1011
+ /**
1012
+ * Configuration for {@link ArcadeRacerRaceManager}.
1013
+ */
1014
+ export interface ArcadeRacerRaceManagerOptions {
1015
+ /** Competitors that participate in the race alongside the player. */
1016
+ competitors: ArcadeRacerRaceCompetitorProfile[];
1017
+ /** Race finish distance measured from the start line. */
1018
+ finishDistance: number;
1019
+ /**
1020
+ * Default lane choices used when a competitor does not define lanes.
1021
+ *
1022
+ * When omitted, the manager uses the engine same-direction lane layout when
1023
+ * available, and otherwise falls back to all engine lanes.
1024
+ */
1025
+ lanePositions?: number[];
1026
+ }
418
1027
  /**
419
1028
  * Main public API for the MinimoJS arcade racing module.
420
1029
  *
@@ -457,7 +1066,8 @@ export interface ArcadeRacerTrafficCollision {
457
1066
  * speed: 120,
458
1067
  * });
459
1068
  *
460
- * racer.build();
1069
+ * const roadSprite = racer.build();
1070
+ * game.add(roadSprite);
461
1071
  *
462
1072
  * game.onUpdate = (dt) => {
463
1073
  * racer.accelerateToMph(110, dt);
@@ -478,8 +1088,9 @@ export interface ArcadeRacerTrafficCollision {
478
1088
  * {@link playerLane}, and helper methods such as {@link steerLeft},
479
1089
  * {@link steerRight}, {@link accelerateToMph}, and {@link brakeToMph}.
480
1090
  * 3. Scene objects:
481
- * Use {@link addTraffic} and {@link addBillboard} to place vehicles and
482
- * roadside decoration along the road.
1091
+ * Use {@link addTraffic} and {@link addBillboard} to place vehicles,
1092
+ * roadside decoration, and wide anchored structures such as bridges,
1093
+ * arches, or fences that extend into the road.
483
1094
  * 4. Background and atmosphere:
484
1095
  * Configure atmosphere in the constructor, then use
485
1096
  * {@link addBackgroundLayer} and {@link addHorizonMarker} to place skies,
@@ -498,6 +1109,10 @@ export interface ArcadeRacerTrafficCollision {
498
1109
  * - `playerLane` is the main lateral control value. Around `-1..1` the player
499
1110
  * is on the paved road; beyond that they enter shoulders and off-road space
500
1111
  * depending on `playerLaneLimit`.
1112
+ * - `laneCount` controls how many visible lane bands the road renders. When
1113
+ * you also provide `laneDirections`, the engine can tell which zero-based
1114
+ * lanes belong to same-direction versus oncoming traffic, and helpers such as
1115
+ * {@link getLanePositions} can return those subsets for you.
501
1116
  * - Traffic collisions are resolved in road space, not by 2D sprite overlap.
502
1117
  * - If no image background layers are configured, the engine falls back to a
503
1118
  * built-in procedural sky and mountain backdrop.
@@ -516,18 +1131,46 @@ export interface ArcadeRacerTrafficCollision {
516
1131
  * - Use {@link addBackgroundLayer} for wide, repeating, parallax image bands.
517
1132
  * - Use {@link addHorizonMarker} for directional landmarks that appear only at
518
1133
  * certain headings.
1134
+ * - Prefer {@link ArcadeRacerTrafficManager} as the first solution for dynamic
1135
+ * gameplay traffic. It is the recommended default before building a custom
1136
+ * spawning system with direct {@link addTraffic} and {@link removeTrafficById}
1137
+ * calls.
519
1138
  * - Use constructor options such as `playerBodyWidth`, `playerBodyLength`,
520
1139
  * `playerBodyOffsetX`, and `playerBodyOffsetY` when you need to tune
521
1140
  * collisions in local image pixels without changing the player artwork.
1141
+ * - Use {@link getLanePosition} and {@link getLanePositions} when you want to
1142
+ * place traffic, race competitors, or billboards directly on lane centers
1143
+ * instead of guessing lane-space numbers by hand.
522
1144
  * - Use constructor `debug` options when you need to visualize car image
523
1145
  * bounds and collision bodies while tuning traffic hits.
1146
+ * - Use `roadDrawer`, {@link addRoadDrawerRange}, and
1147
+ * {@link DefaultArcadeRacerRoadDrawer} when you want to restyle the road
1148
+ * globally or switch to custom road painting on specific track spans.
524
1149
  *
525
1150
  * This class is the canonical public surface of the module. Its JSDoc is
526
1151
  * intended to be consumed directly by AI agents and tooling that generate code
527
1152
  * against `minimo-arcaderacer.js`.
528
1153
  */
529
1154
  export declare class ArcadeRacerEngine {
530
- constructor(game: Game, options?: ArcadeRacerEngineOptions);
1155
+ /**
1156
+ * Number of visible driving lanes configured for the road.
1157
+ */
1158
+ get laneCount(): number;
1159
+ /**
1160
+ * Returns the lane-space center position for a zero-based lane index.
1161
+ *
1162
+ * The returned value is expressed in the same `[-1, 1]` road-space used by
1163
+ * `playerLane`, `addTraffic({ lane })`, and billboard lane placement.
1164
+ * Out-of-range indices are clamped to the nearest valid lane.
1165
+ */
1166
+ getLanePosition(laneIndex: number): number;
1167
+ /**
1168
+ * Returns lane-space center positions for all lanes or a directional subset.
1169
+ *
1170
+ * When `laneDirections` was not configured, `"same"` and `"oncoming"` both
1171
+ * fall back to all lanes.
1172
+ */
1173
+ getLanePositions(direction?: "all" | "same" | "oncoming"): number[];
531
1174
  /**
532
1175
  * Current player speed in world units per second.
533
1176
  */
@@ -582,7 +1225,10 @@ export declare class ArcadeRacerEngine {
582
1225
  */
583
1226
  arcRight(radius: number, degrees: number, options?: ArcadeRacerTrackPrimitiveOptions): void;
584
1227
  /**
585
- * Creates and registers the internal road sprite if it does not yet exist.
1228
+ * Creates the internal road sprite if it does not yet exist and returns it.
1229
+ *
1230
+ * The sprite is not added to the scene automatically. Add the returned
1231
+ * `DrawSprite` yourself so the display-list order stays explicit.
586
1232
  */
587
1233
  build(): DrawSprite;
588
1234
  /**
@@ -633,6 +1279,22 @@ export declare class ArcadeRacerEngine {
633
1279
  * Adds a traffic vehicle directly and returns its generated id.
634
1280
  */
635
1281
  addTraffic(visual: ArcadeRacerVisualSource, config: ArcadeRacerTrafficConfig): string;
1282
+ /**
1283
+ * Removes a traffic vehicle by id.
1284
+ *
1285
+ * Returns `true` when a vehicle was found and removed.
1286
+ */
1287
+ removeTrafficById(id: string): boolean;
1288
+ /**
1289
+ * Updates an existing traffic vehicle by id.
1290
+ *
1291
+ * This is intended for higher-level systems such as race managers that need
1292
+ * to drive opponent state explicitly over time.
1293
+ */
1294
+ updateTrafficById(id: string, config: Partial<ArcadeRacerTrafficConfig> & {
1295
+ alpha?: number;
1296
+ loop?: boolean;
1297
+ }): boolean;
636
1298
  /**
637
1299
  * Clears all configured traffic vehicles.
638
1300
  */
@@ -641,6 +1303,16 @@ export declare class ArcadeRacerEngine {
641
1303
  * Clears all configured billboards.
642
1304
  */
643
1305
  clearBillboards(): void;
1306
+ /**
1307
+ * Adds a wrapped track-distance range that swaps the active road drawer.
1308
+ *
1309
+ * When multiple ranges overlap, the most recently added range wins.
1310
+ */
1311
+ addRoadDrawerRange(range: ArcadeRacerRoadDrawerRange): void;
1312
+ /**
1313
+ * Clears all per-range road drawer overrides.
1314
+ */
1315
+ clearRoadDrawerRanges(): void;
644
1316
  /**
645
1317
  * Adds a heading-based horizon marker directly and returns its generated id.
646
1318
  */
@@ -658,3 +1330,102 @@ export declare class ArcadeRacerEngine {
658
1330
  */
659
1331
  clearBackgroundLayers(): void;
660
1332
  }
1333
+ /**
1334
+ * Automatic traffic orchestration for {@link ArcadeRacerEngine}.
1335
+ *
1336
+ * `ArcadeRacerTrafficManager` owns a pool of manager-spawned vehicles and keeps
1337
+ * that pool populated by:
1338
+ *
1339
+ * - choosing weighted traffic profiles
1340
+ * - selecting lanes and spawn distances ahead of the player
1341
+ * - removing vehicles that have fallen sufficiently behind
1342
+ * - respecting a simple same-lane minimum gap
1343
+ *
1344
+ * The manager is intentionally separate from {@link ArcadeRacerEngine} so AI
1345
+ * agents and game code can opt into dynamic traffic only when they need it.
1346
+ * You construct it explicitly with `new ArcadeRacerTrafficManager(racer, ...)`
1347
+ * and call {@link update} each frame.
1348
+ *
1349
+ * Recommended usage:
1350
+ *
1351
+ * Start with `ArcadeRacerTrafficManager` as the default solution for gameplay
1352
+ * traffic. It covers the common arcade-racer needs of weighted vehicle mixes,
1353
+ * lane-aware spacing, spawn windows, and cleanup with a small API surface.
1354
+ * If your game later needs more specialized behavior such as scripted events,
1355
+ * convoy logic, mission traffic, branching-road orchestration, or highly custom
1356
+ * spawning rules, you can replace it with your own traffic strategy while still
1357
+ * using {@link ArcadeRacerEngine.addTraffic} and
1358
+ * {@link ArcadeRacerEngine.removeTrafficById} directly.
1359
+ *
1360
+ * The manager only tracks vehicles that it spawned itself. Vehicles added
1361
+ * manually with {@link ArcadeRacerEngine.addTraffic} remain untouched.
1362
+ */
1363
+ export declare class ArcadeRacerTrafficManager {
1364
+ constructor(racer: ArcadeRacerEngine, options: ArcadeRacerTrafficManagerOptions);
1365
+ /**
1366
+ * Whether the manager is allowed to spawn new vehicles.
1367
+ *
1368
+ * Disabling the manager does not remove existing managed traffic; it only
1369
+ * pauses new spawn attempts. Existing entries continue to be tracked and
1370
+ * despawned when they fall behind the player.
1371
+ */
1372
+ get enabled(): boolean;
1373
+ set enabled(value: boolean);
1374
+ /**
1375
+ * Number of currently active manager-owned traffic vehicles.
1376
+ */
1377
+ get activeCount(): number;
1378
+ /**
1379
+ * Updates manager-owned traffic bookkeeping and performs spawn/despawn work.
1380
+ */
1381
+ update(dt: number): void;
1382
+ /**
1383
+ * Immediately spawns up to `count` new traffic vehicles if valid slots exist.
1384
+ */
1385
+ spawnNow(count?: number): number;
1386
+ /**
1387
+ * Removes all manager-owned vehicles from the racer.
1388
+ */
1389
+ clear(): void;
1390
+ }
1391
+ /**
1392
+ * Fixed-field race competitor orchestration for {@link ArcadeRacerEngine}.
1393
+ *
1394
+ * Use `ArcadeRacerRaceManager` when the player is competing against a known set
1395
+ * of opponents instead of ambient road traffic. The manager creates one traffic
1396
+ * vehicle per competitor, advances each competitor explicitly every frame, and
1397
+ * exposes simple race standings based on forward progress toward a finish
1398
+ * distance.
1399
+ *
1400
+ * This manager is best suited to classic arcade races where:
1401
+ *
1402
+ * - the roster is fixed at race start
1403
+ * - opponents have defined names and pacing
1404
+ * - finishing place matters more than ambient traffic density
1405
+ *
1406
+ * For world-traffic scenarios or open-road cruising, prefer
1407
+ * {@link ArcadeRacerTrafficManager} instead.
1408
+ */
1409
+ export declare class ArcadeRacerRaceManager {
1410
+ constructor(racer: ArcadeRacerEngine, options: ArcadeRacerRaceManagerOptions);
1411
+ /**
1412
+ * Number of AI competitors currently managed by the race.
1413
+ */
1414
+ get competitorCount(): number;
1415
+ /**
1416
+ * Advances competitor progress and synchronizes their traffic vehicles.
1417
+ */
1418
+ update(dt: number): void;
1419
+ /**
1420
+ * Returns race standings including the player.
1421
+ */
1422
+ getStandings(): ArcadeRacerRaceStanding[];
1423
+ /**
1424
+ * Returns the player's current race position.
1425
+ */
1426
+ getPlayerPosition(): number;
1427
+ /**
1428
+ * Removes all race competitors from the racer.
1429
+ */
1430
+ clear(): void;
1431
+ }