@glandais/vcyclist-engine 4.3.0 → 5.0.0

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.
package/index.d.ts ADDED
@@ -0,0 +1,725 @@
1
+ // GENERATED by `./gradlew :codegen:generateTsFacade` — do not edit.
2
+ // Mirrors src/jsMain/kotlin/io/github/glandais/engine/EngineJsApi.kt.
3
+
4
+ /**
5
+ * An opaque Kotlin/JS `Path` instance. It is a real JS object, but it is not
6
+ * `@JsExport`ed and has no TypeScript surface — reach into it through the
7
+ * accessor functions below, never by property.
8
+ *
9
+ * Branded rather than `object` or `any` so two handle kinds cannot be swapped,
10
+ * and so a typo dereferencing one is a compile error.
11
+ */
12
+ declare const PathBrand: unique symbol;
13
+ export type Path = { readonly [PathBrand]: 'Path' };
14
+
15
+ /** The spellings every door parses, derived from the `CorridorMode` catalogue in `commonMain`. */
16
+ export type CorridorMode =
17
+ | 'lane'
18
+ | 'lane-left'
19
+ | 'full-road';
20
+
21
+ /** The spellings every door parses, derived from the `ElevationGainPreset` catalogue in `commonMain`. */
22
+ export type ElevationGainPreset =
23
+ | 'raw'
24
+ | 'barometric'
25
+ | 'dem'
26
+ | 'gps';
27
+
28
+ /** The spellings every door parses, derived from the `RoadCondition` catalogue in `commonMain`. */
29
+ export type RoadCondition =
30
+ | 'dry'
31
+ | 'wet';
32
+
33
+ /** The spellings every door parses, derived from the `PowerModel` catalogue in `commonMain`. */
34
+ export type PowerModel =
35
+ | 'constant'
36
+ | 'durability'
37
+ | 'critical-power'
38
+ | 'from_data';
39
+
40
+ /** The spellings every door parses, derived from the `PointField` catalogue in `commonMain`. */
41
+ export type PointFieldProp =
42
+ | 'latitude'
43
+ | 'longitude'
44
+ | 'distance'
45
+ | 'dx'
46
+ | 'time'
47
+ | 'elapsed'
48
+ | 'dt'
49
+ | 'bearing'
50
+ | 'elevation'
51
+ | 'grade'
52
+ | 'radius'
53
+ | 'aeroCoef'
54
+ | 'windBearing'
55
+ | 'windAlpha'
56
+ | 'pAero'
57
+ | 'pGravity'
58
+ | 'pRollingResistance'
59
+ | 'pWheelBearings'
60
+ | 'pInputPower'
61
+ | 'pCyclistProvidedOptimalPower'
62
+ | 'pCyclistProvidedOptimalPowerWithHarmonics'
63
+ | 'pCyclistPowerNeeded'
64
+ | 'pCyclistProvidedMuscular'
65
+ | 'pCyclistProvidedWheel'
66
+ | 'pComputedTotalPower'
67
+ | 'pComputedWheelPower'
68
+ | 'pComputedPower'
69
+ | 'speed'
70
+ | 'speedMax'
71
+ | 'speedMaxIncline'
72
+ | 'virtSpeedCurrent'
73
+ | 'temperature'
74
+ | 'windSpeed'
75
+ | 'windDirection'
76
+ | 'heartRate'
77
+ | 'cadence'
78
+ | 'wPrimeBalance'
79
+ | 'pBrake'
80
+ | 'trajectoryCurvature'
81
+ | 'roadWidth'
82
+ | 'lateralOffset'
83
+ | 'sourceLatitude'
84
+ | 'sourceLongitude'
85
+ | 'sourceElevation';
86
+
87
+ /** The spellings every door parses, derived from the `CornerKind` catalogue in `commonMain`. */
88
+ export type CornerKind =
89
+ | 'GENTLE'
90
+ | 'CORNER'
91
+ | 'HAIRPIN';
92
+
93
+ /** The spellings every door parses, derived from the `GpxPowerSource` catalogue in `commonMain`. */
94
+ export type GpxPowerSource =
95
+ | 'input'
96
+ | 'computed'
97
+ | 'computed-or-input';
98
+
99
+ /**
100
+ * JS-facing snapshot of a single path point. Read-only ; built lazily by `pointAt`. Field units
101
+ * are degrees, meters, m/s, watts, epoch ms, slope ratio.
102
+ */
103
+ export interface PointDto {
104
+ readonly latitudeDeg: number;
105
+ readonly longitudeDeg: number;
106
+ readonly elevation: number;
107
+ readonly timeMs: number;
108
+ readonly speed: number;
109
+ readonly pComputedPower: number;
110
+ readonly distance: number;
111
+ readonly grade: number;
112
+ }
113
+
114
+ /**
115
+ * JS-facing snapshot of a `<wpt>` waypoint (see `GpxWaypoint`).
116
+ * Flat DTO ; built by `parseGpxWaypoints`. `timeEpochMs` is `null` when `<time>` is absent, same
117
+ * as the Kotlin model.
118
+ */
119
+ export interface WaypointDto {
120
+ readonly latitudeDeg: number;
121
+ readonly longitudeDeg: number;
122
+ readonly elevationM?: number | null;
123
+ readonly name?: string | null;
124
+ readonly description?: string | null;
125
+ readonly symbol?: string | null;
126
+ readonly type?: string | null;
127
+ readonly timeEpochMs?: number | null;
128
+ }
129
+
130
+ /**
131
+ * JS-side mirror of `EnhanceOptions` / `SimplifyPathOptions`. Every flag is optional ; when a
132
+ * caller leaves a field `undefined`, `defaultJsOptions` picks the JS-safe default (skip elevation
133
+ * fetch, skip 1 Hz resample, skip simplify — see task 27 for the timestamp pitfall).
134
+ */
135
+ export interface EnhanceOptionsDto {
136
+ readonly fixElevation?: boolean | null;
137
+ readonly computeMaxSpeeds?: boolean | null;
138
+ readonly virtualizeTrack?: boolean | null;
139
+ readonly computeOnePointPerSecond?: boolean | null;
140
+ readonly simplifyEnabled?: boolean | null;
141
+ readonly simplifyToleranceM?: number | null;
142
+ readonly simplifyZExaggeration?: number | null;
143
+ /**
144
+ * W′ balance annotation pass (ledger R15). Defaults to `true`, which is what JS callers have
145
+ * been getting all along — `WPrimeBalanceOptions` is enabled by default and nothing here ever
146
+ * overrode it, so the `wPrimeBalance` field was already being written, at CP 250 W / W′ 20 kJ.
147
+ * These three fields make it *calibrable*, they do not turn it on.
148
+ */
149
+ readonly wPrimeBalanceEnabled?: boolean | null;
150
+ /**
151
+ * CP (W) for the W′ balance **field**.
152
+ *
153
+ * This is **not** automatically `criticalPower`. The two are independent by
154
+ * design (see `WPrimeBalanceOptions` for why CP/W′ live on the options rather than on
155
+ * `Cyclist`), so passing different values here and on the power provider yields a W′bal trace
156
+ * that does not describe the rider being simulated. Legitimate — reporting one rider's effort
157
+ * against another's physiology — but rarely intended.
158
+ */
159
+ readonly wPrimeBalanceCriticalPower?: number | null;
160
+ /**
161
+ * W′ (J) for the W′ balance field. Same independence caveat as `wPrimeBalanceCriticalPower`.
162
+ */
163
+ readonly wPrimeBalanceWPrime?: number | null;
164
+ /**
165
+ * Curvature estimation. Defaults to on — it corrects `radius` and `speedMax`, so turning it
166
+ * off restores the historical windowed estimate rather than saving work worth saving.
167
+ */
168
+ readonly curvatureEnabled?: boolean | null;
169
+ /**
170
+ * Optimal-trajectory ("racing line") stage. Off by default: enabling it **moves every
171
+ * coordinate** of the result. The original positions are preserved in `sourceLatitude` /
172
+ * `sourceLongitude`.
173
+ */
174
+ readonly racingLineEnabled?: boolean | null;
175
+ /**
176
+ * `"lane"` (default), `"lane-left"`, or `"full-road"` — closed roads and time trials only.
177
+ */
178
+ readonly racingLineCorridor?: CorridorMode | null;
179
+ /**
180
+ * Road width assumed where the GPX supplies none, in metres.
181
+ */
182
+ readonly racingLineRoadWidthM?: number | null;
183
+ /**
184
+ * Scale the reported climbing is measured at: `"raw"`, `"barometric"`, `"dem"`, `"gps"`.
185
+ * See `docs/guides/elevation.md`.
186
+ */
187
+ readonly elevationGainPreset?: ElevationGainPreset | null;
188
+ /**
189
+ * Override the preset's hysteresis dead band, in metres. `0` disables it.
190
+ */
191
+ readonly elevationGainThresholdM?: number | null;
192
+ /**
193
+ * Whether to measure the dead-banded climbing at all. Defaults to `true`.
194
+ */
195
+ readonly elevationGainEnabled?: boolean | null;
196
+ /**
197
+ * Triangular-kernel half-width for the elevation smoother, in metres.
198
+ *
199
+ * The single largest determinant of both the reported climbing and the gradients the
200
+ * simulation rides — it costs a clean route ~1.5 % and a noisy GPS trace ~48 %.
201
+ */
202
+ readonly elevationSmoothWindowM?: number | null;
203
+ /**
204
+ * Web-Mercator zoom for the DEM lookup, `0..15`, used only when `fixElevation` is on.
205
+ *
206
+ * Not part of `EnhanceOptions` — it configures the provider, not the pipeline — so it is read
207
+ * where the provider is built rather than in `toEnhanceOptions`.
208
+ */
209
+ readonly demZoom?: number | null;
210
+ }
211
+
212
+ /**
213
+ * JS-side mirror of `Cyclist`. All fields required when the DTO is passed (Kotlin/JS does not
214
+ * gracefully tolerate optional fields on `external interface` without `?`). Callers can pass
215
+ * `null` for the whole DTO to fall back to engine defaults (80 kg / 0.7 Cd / 0.5 m² / 35° / 100
216
+ * km/h / 0.4 g brake).
217
+ *
218
+ * Passing a key this interface does not declare is an **error** since task 43 — see
219
+ * `requireOnlyKeys`.
220
+ */
221
+ export interface CyclistDto {
222
+ readonly massKg: number;
223
+ readonly cd: number;
224
+ readonly frontalAreaM2: number;
225
+ readonly maxLeanAngleDeg: number;
226
+ readonly maxBrakeG: number;
227
+ readonly maxSpeedKmH: number;
228
+ /**
229
+ * Road surface preset (ledger R9) : `"dry"` (the default) or `"wet"`, case-insensitive. Sets
230
+ * cornering grip and braking together — wet cuts cornering speed by 1.58× and braking to
231
+ * 0.23 g. Any other value throws.
232
+ *
233
+ * **The preset wins over `maxLeanAngleDeg` and `maxBrakeG` when present**, which is the
234
+ * opposite of the CLI, where an explicit `--cyclist-max-angle` overrides `--road-condition`.
235
+ * The CLI can tell "not given" from "given" because its fields are nullable ; here they are
236
+ * not, so a JS caller always supplies all six and "explicit wins" would make the preset
237
+ * unreachable. Omit `roadCondition` to keep the raw values — that is the pre-R9 behaviour, and
238
+ * `"dry"` reproduces the shipped defaults bit-for-bit anyway.
239
+ */
240
+ readonly roadCondition?: RoadCondition | null;
241
+ }
242
+
243
+ /**
244
+ * JS-side mirror of `Bike`. Pass `null` to use defaults (Crr 0.004, road-bike inertias,
245
+ * 0.7 m wheel radius, 0.95 drivetrain efficiency).
246
+ */
247
+ export interface BikeDto {
248
+ readonly crr: number;
249
+ readonly inertiaFront: number;
250
+ readonly inertiaRear: number;
251
+ readonly wheelRadiusM: number;
252
+ readonly efficiency: number;
253
+ /**
254
+ * Lean angle (°) past which the rider stops pedalling; `90` disables the cut-off.
255
+ */
256
+ readonly maxPedalingLeanAngleDeg?: number | null;
257
+ }
258
+
259
+ /**
260
+ * JS-side mirror of constant-wind input. `windDirection` is in **degrees**, `0` = north,
261
+ * `90` = east — `toWindProvider` converts to the radians the engine uses internally.
262
+ *
263
+ * The angle names the direction the wind blows **toward**, not the meteorological "from" it was
264
+ * long documented as. Task g31 established this by simulation; see
265
+ * `PathWind.dominantHeadwindAzimuthDeg`, whose output goes into this field unchanged.
266
+ */
267
+ export interface WindDto {
268
+ readonly windSpeed: number;
269
+ readonly windDirection: number;
270
+ }
271
+
272
+ /**
273
+ * JS-side description of which `CyclistPowerProvider` to instantiate.
274
+ *
275
+ * `type` takes any `PowerModel` wire name — `"constant"`, `"durability"`, `"critical-power"` or
276
+ * `"from_data"` — and the catalog is the single source of that list, so a model added to the
277
+ * engine is reachable here without editing this file.
278
+ *
279
+ * `pacing` and `maxSlewWPerS` are **decorators**, not types : they compose over whichever `type`
280
+ * was chosen, in the order `toProvider` defines.
281
+ */
282
+ export interface PowerProviderDto {
283
+ readonly type: PowerModel;
284
+ readonly power?: number | null;
285
+ readonly useHarmonics?: boolean | null;
286
+ /**
287
+ * CP (W) for `"durability"` and `"critical-power"`. Defaults to 250 W.
288
+ */
289
+ readonly criticalPower?: number | null;
290
+ /**
291
+ * W′ (J) for `"critical-power"` — the reserve spendable above CP. Defaults to 20 kJ.
292
+ */
293
+ readonly wPrime?: number | null;
294
+ /**
295
+ * Terrain pacing (ledger R19) : harder uphill and into headwind, easier downhill, with a causal
296
+ * energy account so the effort is redistributed rather than added. Off by default.
297
+ *
298
+ * A heuristic with **no lookahead** — the rider reacts to the road it is on. Its magnitudes
299
+ * (gradient gain, the ~300 m ramp, the energy-budget window) are the project's own rather than
300
+ * sourced, so they are not exposed here ; use `PowerProviderTerrainPacing` directly from Kotlin
301
+ * to change them.
302
+ */
303
+ readonly pacing?: boolean | null;
304
+ /**
305
+ * Power slew-rate limit (ledger R18) in W/s ; `0` or omitted disables it. 50 is the value
306
+ * Zignoli & Biral use. Same semantics as the CLI's `--cyclist-slew`.
307
+ *
308
+ * Applied **outermost**, so it smooths whatever `type` and `pacing` produced.
309
+ */
310
+ readonly maxSlewWPerS?: number | null;
311
+ }
312
+
313
+ /**
314
+ * Catalog entry for a `PointField`. Exposed to JS
315
+ * via `fieldDefinitions` so consumers can render generic field-pickers without hard-coding
316
+ * the 43-entry list.
317
+ */
318
+ export interface FieldDefinitionDto {
319
+ readonly prop: PointFieldProp;
320
+ readonly unit: string;
321
+ readonly shortDescription: string;
322
+ readonly categoryId: string;
323
+ readonly categoryName: string;
324
+ readonly notSelectable: boolean;
325
+ readonly anglesInRadians: boolean;
326
+ }
327
+
328
+ /**
329
+ * A homogeneous-grade segment of a climb. Distances are absolute along the path, in meters;
330
+ * `grade` is dimensionless (`0.08` = 8 %), matching `averageGrade`.
331
+ */
332
+ export interface ClimbPartDto {
333
+ readonly startDistanceM: number;
334
+ readonly endDistanceM: number;
335
+ readonly startElevationM: number;
336
+ readonly endElevationM: number;
337
+ readonly lengthM: number;
338
+ readonly elevationGainM: number;
339
+ readonly grade: number;
340
+ }
341
+
342
+ /**
343
+ * A detected climb, flattened for the JS boundary.
344
+ */
345
+ export interface ClimbDto {
346
+ readonly startIndex: number;
347
+ readonly endIndex: number;
348
+ readonly startDistanceM: number;
349
+ readonly endDistanceM: number;
350
+ readonly startElevationM: number;
351
+ readonly endElevationM: number;
352
+ readonly lengthM: number;
353
+ readonly elevationGainM: number;
354
+ /**
355
+ * Dimensionless: `0.08` = 8 %.
356
+ */
357
+ readonly averageGrade: number;
358
+ /**
359
+ * Dimensionless, counting only the rising sections.
360
+ */
361
+ readonly climbingGrade: number;
362
+ readonly positiveElevationM: number;
363
+ readonly negativeElevationM: number;
364
+ readonly parts: ClimbPartDto[];
365
+ }
366
+
367
+ /**
368
+ * One detected bend, as the racing-line analysis sees it.
369
+ *
370
+ * @see analyzeRacingLine
371
+ */
372
+ export interface CornerDto {
373
+ readonly fromIndex: number;
374
+ readonly untilIndex: number;
375
+ readonly apexIndex: number;
376
+ /**
377
+ * `"GENTLE"`, `"CORNER"` or `"HAIRPIN"`.
378
+ */
379
+ readonly kind: CornerKind;
380
+ readonly turnRad: number;
381
+ /**
382
+ * `+1` turning left, `-1` turning right.
383
+ */
384
+ readonly direction: number;
385
+ /**
386
+ * 20th percentile of `1/|κ|` over the span — robust to the clothoid ends a mean is biased by.
387
+ */
388
+ readonly radiusQ20M: number;
389
+ readonly radiusMinM: number;
390
+ readonly lengthM: number;
391
+ }
392
+
393
+ /**
394
+ * What the racing-line stage decided, without applying it.
395
+ *
396
+ * Every array has one entry per path station. `corridorLo`/`corridorHi` bound the lateral offset in
397
+ * metres, positive to the left; `lateralOffsetM` is the line that was solved for inside them.
398
+ */
399
+ export interface RacingLineReportDto {
400
+ readonly size: number;
401
+ readonly corners: CornerDto[];
402
+ /**
403
+ * Curvature of the road itself, m⁻¹, positive turning left.
404
+ */
405
+ readonly centerlineCurvature: Float64Array;
406
+ /**
407
+ * Curvature of the solved line, same convention.
408
+ */
409
+ readonly trajectoryCurvature: Float64Array;
410
+ readonly corridorLo: Float64Array;
411
+ readonly corridorHi: Float64Array;
412
+ readonly roadHalfWidthM: Float64Array;
413
+ readonly lateralOffsetM: Float64Array;
414
+ /**
415
+ * Widest the corridor gets anywhere, `hi − lo`, in metres.
416
+ */
417
+ readonly maxCorridorWidthM: number;
418
+ readonly newtonIterations: number;
419
+ /**
420
+ * Final residual relative to the initial gradient — scale-free, so comparable across routes.
421
+ */
422
+ readonly relativeGradient: number;
423
+ readonly converged: boolean;
424
+ readonly activeConstraints: number;
425
+ }
426
+
427
+ /**
428
+ * Parse `xml` and return every `<wpt>` in document order, as flat `WaypointDto` objects.
429
+ */
430
+ export declare function parseGpxWaypoints(xml: string): WaypointDto[];
431
+
432
+ export declare function parseGpx(xml: string): Path;
433
+
434
+ /**
435
+ * Parse `xml` and return **one Path per `<trk>`**, in document order. Tracks with no point are
436
+ * skipped. Segments of a same track are concatenated — distance jumps across a segment
437
+ * boundary ; use `parseGpxSegments` if that artefact matters.
438
+ *
439
+ * **Since task g24 this includes `<rte>` routes**, which used to be dropped silently. Use
440
+ * `parseGpxTracksOnly` for the pre-g24 selection.
441
+ */
442
+ export declare function parseGpxTracks(xml: string): Path[];
443
+
444
+ /**
445
+ * Parse `xml` and return **one Path per `<trkseg>`**, across all tracks, in document order.
446
+ * Empty segments are skipped. Every returned Path is continuous.
447
+ */
448
+ export declare function parseGpxSegments(xml: string): Path[];
449
+
450
+ /**
451
+ * Like `parseGpxTracks` but **`<trk>` only** — routes are left out (task g29).
452
+ *
453
+ * `GpxPathKind` is deliberately not exported: an enum crosses to JavaScript as an object nobody
454
+ * wants to import just to filter a list. Two named functions say the same thing at the call site.
455
+ */
456
+ export declare function parseGpxTracksOnly(xml: string): Path[];
457
+
458
+ /**
459
+ * Like `parseGpxTracks` but **`<rte>` only** — recorded tracks are left out (task g29).
460
+ */
461
+ export declare function parseGpxRoutesOnly(xml: string): Path[];
462
+
463
+ /**
464
+ * Serialise `paths` as a multi-track GPX document — one `<trk>` per Path. `waypoints`, if given,
465
+ * is written as `<wpt>` entries before the tracks (typically the source document's
466
+ * `waypoints`, forwarded so a parse → enhance → write
467
+ * round-trip does not silently drop points of interest — see g03).
468
+ *
469
+ * `trackNames` names the tracks positionally; a shorter list — or `null` — leaves the remaining
470
+ * tracks unnamed, which is `pathsToGpxDocument`'s own contract and **not** the
471
+ * `DEFAULT_TRACK_NAME` that `writeGpx` applies to its single track. Extra names are ignored.
472
+ *
473
+ * `startTimeEpochMs` is what `writeGpxAt` does, applied to every track at once: `<time>` =
474
+ * `startTimeEpochMs + time(i)`. `null` (the default) writes the times as they stand. It is a
475
+ * `Double` for the reason `writeGpxAt` gives.
476
+ *
477
+ * Both arrived late — the WASI door had accepted `trackName` and `startTimeEpochMs` since w09 and
478
+ * this façade had no way to say either, so a multi-track export was the one GPX a JS caller could
479
+ * not date or name.
480
+ */
481
+ export declare function writeGpxTracks(paths: Path[], waypoints?: WaypointDto[], writeExtensions?: boolean, powerSource?: GpxPowerSource | null, trackNames?: string[] | null, startTimeEpochMs?: number | null): string;
482
+
483
+ export declare function pathSize(path: Path): number;
484
+
485
+ export declare function pathTotalDistance(path: Path): number;
486
+
487
+ export declare function pathDurationMs(path: Path): number;
488
+
489
+ export declare function pathElevationGain(path: Path): number;
490
+
491
+ export declare function pathElevationLoss(path: Path): number;
492
+
493
+ /**
494
+ * Cumulative ascent with the dead band applied, or `NaN` if the stage did not run.
495
+ *
496
+ * `NaN` rather than `null` so the export stays a plain `Double` on the JS side; use
497
+ * `pathReportedElevationGain` to get the raw sum as a fallback instead.
498
+ */
499
+ export declare function pathElevationGainFiltered(path: Path): number;
500
+
501
+ /**
502
+ * Counterpart of `pathElevationGainFiltered`. NEGATIVE by convention, or `NaN`.
503
+ */
504
+ export declare function pathElevationLossFiltered(path: Path): number;
505
+
506
+ /**
507
+ * The figure to show a human: dead-banded when measured, the raw sum otherwise.
508
+ */
509
+ export declare function pathReportedElevationGain(path: Path): number;
510
+
511
+ /**
512
+ * Counterpart of `pathReportedElevationGain`. NEGATIVE by convention.
513
+ */
514
+ export declare function pathReportedElevationLoss(path: Path): number;
515
+
516
+ export declare function pointAt(path: Path, i: number): PointDto;
517
+
518
+ /**
519
+ * Serialise `path` as a single-track GPX.
520
+ *
521
+ * `writeExtensions` `false` drops `<extensions>` — no power, heart rate, cadence or temperature,
522
+ * and no `gpxtpx` namespace (task g23). `<ele>` and `<time>` are standard GPX and stay. Useful in
523
+ * a browser for a download meant for a platform with a strict schema.
524
+ *
525
+ * `powerSource` selects which of the path's two power fields lands in `<power>`: `"input"`,
526
+ * `"computed"` or `"computed-or-input"` — the CLI's `--gpx-power-source` values, parsed through
527
+ * the one catalogue in `GpxPowerSource`. `null` keeps the default, which writes the *input*
528
+ * power: emitting simulated data into a format the ecosystem reads as a recording is the caller's
529
+ * decision, not a default's.
530
+ *
531
+ * `trackName` names the `<trk><name>`; `null` keeps `"virtualized"`.
532
+ */
533
+ export declare function writeGpx(path: Path, writeExtensions?: boolean, powerSource?: GpxPowerSource | null, trackName?: string | null): string;
534
+
535
+ /**
536
+ * Serialise `path` with an absolute `<time>` on every point : `<time> = startTimeEpochMs +
537
+ * time(i)` milliseconds (see `startTime` / task g05).
538
+ *
539
+ * `startTimeEpochMs` is a `Double`, not a `Long` : on Kotlin/JS `Long` crosses the boundary as a
540
+ * `BigInt`, which is awkward for callers building it from `Date.now()` or `new Date(...).getTime()`
541
+ * (both already `number`/`Double`). Epoch milliseconds fit exactly in a `Double` until the year
542
+ * 287396 — the same reasoning already used by `pathDurationMs`.
543
+ */
544
+ export declare function writeGpxAt(path: Path, startTimeEpochMs: number, writeExtensions?: boolean, powerSource?: GpxPowerSource | null, trackName?: string | null): string;
545
+
546
+ export declare function enhance(path: Path, options: EnhanceOptionsDto | null): Promise<Path>;
547
+
548
+ /**
549
+ * Enhance `path` using a fully custom `CoursePhysics` (cyclist + bike + wind + power provider).
550
+ *
551
+ * Any DTO parameter may be `null` ; defaults are picked from the engine
552
+ * (see `CyclistDto`, `BikeDto`, `WindDto`, `PowerProviderDto` KDoc for specifics).
553
+ *
554
+ * Like `enhance`, when `options.fixElevation == true` a default `ElevationProvider` is
555
+ * auto-instantiated (Terrarium tiles via HTTP).
556
+ */
557
+ export declare function enhanceWithCourse(path: Path, cyclist: CyclistDto | null, bike: BikeDto | null, wind: WindDto | null, power: PowerProviderDto | null, options: EnhanceOptionsDto | null): Promise<Path>;
558
+
559
+ /**
560
+ * Read field `fieldProp` (camelCase, e.g. `"elevation"` / `"speed"`) at point `i`. Throws if
561
+ * the field name is unknown.
562
+ */
563
+ export declare function getField(path: Path, i: number, fieldProp: PointFieldProp): number;
564
+
565
+ /**
566
+ * Enumerate the 43-entry `PointField` catalog as JS-friendly `FieldDefinitionDto` objects.
567
+ * UIs use this to render generic field-pickers without hard-coding the list.
568
+ */
569
+ export declare function fieldDefinitions(): FieldDefinitionDto[];
570
+
571
+ /**
572
+ * Latitude of point `i` in degrees (convenience wrapper around `latitudeDeg`).
573
+ */
574
+ export declare function pathLatitudeDeg(path: Path, i: number): number;
575
+
576
+ /**
577
+ * Longitude of point `i` in degrees (convenience wrapper around `longitudeDeg`).
578
+ */
579
+ export declare function pathLongitudeDeg(path: Path, i: number): number;
580
+
581
+ /**
582
+ * Serialise `path` to CSV (all 36 `PointField`s, one row per point) — see `CsvWriter` / task g06.
583
+ * Lets a browser demo offer an "Export CSV" button without a server round-trip : the caller
584
+ * wraps the returned `String` in a `Blob` and triggers a download via
585
+ * `URL.createObjectURL`.
586
+ *
587
+ * `separator` takes only its first character (JS has no `Char` type at the interop boundary) ;
588
+ * an empty string falls back to `,`.
589
+ *
590
+ * `decimals` and `lineSeparator` arrived in S4 of the surface-alignment work. The WASI door had
591
+ * accepted `decimals` since w09 while this signature had three parameters, so the same core writer
592
+ * was configurable from one wire door and not the other. `null` on either keeps the `CsvOptions`
593
+ * default rather than a literal restated here.
594
+ */
595
+ export declare function pathToCsv(path: Path, separator: string, unitsInHeader: boolean, decimals?: number | null, lineSeparator?: string | null): string;
596
+
597
+ /**
598
+ * Serialise `path` to JSON, column-oriented (one array per `PointField`) — see `JsonWriter` /
599
+ * task g07. Lets a browser demo hand the result straight to `JSON.parse` and feed a chart
600
+ * library (e.g. Chart.js) without a server round-trip.
601
+ *
602
+ * `decimals` rounds every value; `includeMeta` drops the `size` / `fields` preamble when `false`.
603
+ * Both were reachable from the WASI door and not from here until S4 — `null` keeps the
604
+ * `JsonOptions` default, which is where the default lives.
605
+ */
606
+ export declare function pathToJson(path: Path, pretty: boolean, decimals?: number | null, includeMeta?: boolean | null): string;
607
+
608
+ /**
609
+ * Encode `path` as a Garmin FIT Course file.
610
+ *
611
+ * @param startTimeEpochMs absolute start instant in Unix epoch milliseconds. FIT has no relative
612
+ * clock, so this is mandatory — `Double` rather than `Long` to avoid a BigInt at the JS
613
+ * boundary, matching the convention `pathDurationMs` and `writeGpxAt` already use.
614
+ * @return the complete FIT file. On Kotlin/JS a `ByteArray` surfaces as a JS `Int8Array`.
615
+ */
616
+ export declare function pathToFit(path: Path, name: string, startTimeEpochMs: number): Int8Array;
617
+
618
+ /**
619
+ * Encode **several** paths into one FIT course — one lap and one `TIMER`/`START`…`STOP` event
620
+ * pair per path (task g25, exported here by g29).
621
+ *
622
+ * A separate function rather than a parameter on `pathToFit`: the shape of the first argument
623
+ * changes, and Kotlin/JS has no overloading to lean on.
624
+ *
625
+ * `interPathGapMs` shifts each path after the first by that many milliseconds; `0` (the default)
626
+ * runs them straight on from one another. It is **not** a pause — FIT expresses those with
627
+ * `TIMER`/`PAUSE` events, which this port does not emit.
628
+ */
629
+ export declare function pathsToFit(paths: Path[], name: string, startTimeEpochMs: number, interPathGapMs?: number): Int8Array;
630
+
631
+ /**
632
+ * Azimuth in degrees of the constant wind that makes `path` hardest on average — the opposite of
633
+ * the course's dominant orientation (task g26, exported by g31).
634
+ *
635
+ * The value goes **straight into** a `WindDto.directionDeg` or `Wind(speedMS, directionRad)`: no
636
+ * 180° flip, no unit conversion beyond degrees → radians. The engine's angle convention is not the
637
+ * meteorological one it claims to be, which is why this is exposed as a ready-to-use number rather
638
+ * than as a vector the caller would have to interpret.
639
+ *
640
+ * This function says nothing about wind **speed** — only about the direction that hurts most.
641
+ *
642
+ * @return `NaN` when the question has no answer: fewer than 4 points, or a course whose mean
643
+ * direction cancels out (a perfectly symmetric loop). `0` is a valid answer, so check with
644
+ * `Number.isNaN`.
645
+ */
646
+ export declare function dominantHeadwindAzimuth(path: Path): number;
647
+
648
+ /**
649
+ * Multi-path form of `dominantHeadwindAzimuth` — the tracks of one file weigh equally.
650
+ */
651
+ export declare function dominantHeadwindAzimuthOfTracks(paths: Path[]): number;
652
+
653
+ /**
654
+ * Detect climbs on `path` with the default options — see `ClimbOptions`.
655
+ */
656
+ export declare function detectClimbs(path: Path): ClimbDto[];
657
+
658
+ /**
659
+ * Detect climbs with explicit parameters. Flat scalars rather than an options DTO to keep the
660
+ * JS boundary simple.
661
+ */
662
+ export declare function detectClimbsWithOptions(path: Path, minMinClimbElevationM: number, maxMinClimbElevationM: number, minClimbElevationRatio: number, minGradePercent: number, maxDiffRealGrade: number, booster: number, maxAnalysisPoints?: number | null): ClimbDto[];
663
+
664
+ /**
665
+ * Analyse what the racing line *would* do to `path`, without touching it.
666
+ *
667
+ * Read-only by construction — it takes a `Path` and returns numbers, so asking the question can
668
+ * never move a coordinate. That is the point: the stage rewrites every position it is applied to,
669
+ * and being able to inspect the corridor it assumed and the offset it chose is what makes that
670
+ * inspectable rather than merely optional.
671
+ *
672
+ * Returns `null` for a path too short or too degenerate to analyse, matching the Kotlin API's
673
+ * contract of declining rather than guessing.
674
+ *
675
+ * The `options` are the same `racingLine*` keys `enhance` accepts; `enabled` is ignored here, since
676
+ * calling this *is* the request.
677
+ */
678
+ export declare function analyzeRacingLine(path: Path, options?: EnhanceOptionsDto | null): RacingLineReportDto | null;
679
+
680
+ /**
681
+ * The engine's own defaults for `detectClimbsWithOptions`, read off `ClimbOptions`.
682
+ *
683
+ * `detectClimbsWithOptions` takes its options **positionally**, so changing the last one means
684
+ * naming all the others and there is no field to omit. These are those values, generated
685
+ * from the same catalogue the CLI and WASI doors resolve their defaults through — never
686
+ * transcribed. Spread it and override what you need.
687
+ */
688
+ export declare const climbDefaults: {
689
+ readonly minMinClimbElevationM: number;
690
+ readonly maxMinClimbElevationM: number;
691
+ readonly minClimbElevationRatio: number;
692
+ readonly minGradePercent: number;
693
+ readonly maxDiffRealGrade: number;
694
+ readonly booster: number;
695
+ readonly maxAnalysisPoints: number;
696
+ };
697
+
698
+ /**
699
+ * The engine's own defaults for `pathToCsv`, read off `CsvOptions`.
700
+ *
701
+ * `pathToCsv` takes its options **positionally**, so changing the last one means
702
+ * naming all the others and there is no field to omit. These are those values, generated
703
+ * from the same catalogue the CLI and WASI doors resolve their defaults through — never
704
+ * transcribed. Spread it and override what you need.
705
+ */
706
+ export declare const csvDefaults: {
707
+ readonly separator: ',';
708
+ readonly unitsInHeader: boolean;
709
+ readonly decimals: null;
710
+ readonly lineSeparator: '\n';
711
+ };
712
+
713
+ /**
714
+ * The engine's own defaults for `pathToJson`, read off `JsonOptions`.
715
+ *
716
+ * `pathToJson` takes its options **positionally**, so changing the last one means
717
+ * naming all the others and there is no field to omit. These are those values, generated
718
+ * from the same catalogue the CLI and WASI doors resolve their defaults through — never
719
+ * transcribed. Spread it and override what you need.
720
+ */
721
+ export declare const jsonDefaults: {
722
+ readonly pretty: boolean;
723
+ readonly decimals: null;
724
+ readonly includeMeta: boolean;
725
+ };