@treewyrm/freelancer-webgl2 0.0.0-stage → 0.1.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.
@@ -0,0 +1,4452 @@
1
+ import { Matrix3, Matrix4, Quat, RingShape, Shape, Vector3, Vector4 } from "@treewyrm/freelancer/math";
2
+ import { AnimatedColor, AnimatedCurve, AnimatedFloat, AnimatedTransform, Blending, Effect, Node, NodeInstance } from "@treewyrm/freelancer/alchemy";
3
+ import { Primitive, VMeshData, VMeshPart, VMeshRef, VertexFormat } from "@treewyrm/freelancer/vmesh";
4
+ import { Bone, DeformableModel, Level } from "@treewyrm/freelancer/deformable";
5
+ import { CompoundNode, Hardpoint, Joint, JointState } from "@treewyrm/freelancer/compound";
6
+ import { Material } from "@treewyrm/freelancer/material";
7
+ import { MaterialAnim, MaterialAnimLibrary, MeshSource, RigidModel, RigidPart, Sphere } from "@treewyrm/freelancer/rigid";
8
+ import { Surface } from "@treewyrm/freelancer/surface";
9
+ import { AudioProps, Entity, Event, EventTypeName, FogProps, LightProps, ParamCurve, PathProps, Script } from "@treewyrm/freelancer/thn/scene";
10
+ import { AnimationScript } from "@treewyrm/freelancer/animation";
11
+ import { CubeTexture, Texture, TextureEntry } from "@treewyrm/freelancer/texture";
12
+ /** Acquires a WebGL2 context, or throws: the renderer has no fallback for its absence. */
13
+ export declare const createContext: (canvas: HTMLCanvasElement | OffscreenCanvas, options?: WebGLContextAttributes) => WebGL2RenderingContext;
14
+ /** A linked program and its uniform locations. `null` is GL's own: optimized out by the driver. */
15
+ interface Program<U extends string = string> {
16
+ handle: WebGLProgram;
17
+ uniforms: Record<U, WebGLUniformLocation | null>;
18
+ }
19
+ /** Compiles and links GLSL ES 3.00 sources, looking up the named uniforms once. */
20
+ export declare const createProgram: <U extends string = never>(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string, uniforms?: readonly U[]) => Program<U>;
21
+ interface Common {
22
+ name: string;
23
+ /** Absent, 3 (`0x623f7b0`). `FLT_MAX` and `Infinity` both mean never. */
24
+ lifeSpan: number;
25
+ transform?: AnimatedTransform;
26
+ }
27
+ interface EmitterCommon extends Common {
28
+ category: 'emitter';
29
+ lodCurve?: AnimatedFloat;
30
+ /** Signed: one retail cone holds `0xCDCDCDCD`, which the DLL adds as it stands (`0x6245cf6`). */
31
+ initialParticles: number;
32
+ frequency?: AnimatedCurve;
33
+ maxParticles?: AnimatedCurve;
34
+ initLifeSpan?: AnimatedCurve;
35
+ pressure?: AnimatedCurve;
36
+ emitCount?: AnimatedCurve;
37
+ velocityApproach?: AnimatedCurve;
38
+ }
39
+ interface CubeEmitter extends EmitterCommon {
40
+ kind: 'cube-emitter';
41
+ width?: AnimatedCurve;
42
+ height?: AnimatedCurve;
43
+ depth?: AnimatedCurve;
44
+ minSpread?: AnimatedCurve;
45
+ maxSpread?: AnimatedCurve;
46
+ }
47
+ interface SphereEmitter extends EmitterCommon {
48
+ kind: 'sphere-emitter';
49
+ minRadius?: AnimatedCurve;
50
+ maxRadius?: AnimatedCurve;
51
+ }
52
+ interface ConeEmitter extends EmitterCommon {
53
+ kind: 'cone-emitter';
54
+ minRadius?: AnimatedCurve;
55
+ maxRadius?: AnimatedCurve;
56
+ minSpread?: AnimatedCurve;
57
+ maxSpread?: AnimatedCurve;
58
+ }
59
+ interface Sprite {
60
+ color?: AnimatedColor;
61
+ alpha?: AnimatedFloat;
62
+ texture?: string;
63
+ blend?: Blending;
64
+ texFrame?: AnimatedFloat;
65
+ commonTexFrame?: AnimatedCurve;
66
+ useCommonTexFrame: boolean;
67
+ flipU: boolean;
68
+ flipV: boolean;
69
+ }
70
+ interface AppearanceCommon extends Common {
71
+ category: 'appearance';
72
+ lodCurve?: AnimatedFloat;
73
+ }
74
+ interface BasicAppearance extends AppearanceCommon, Sprite {
75
+ kind: 'basic-appearance';
76
+ triTexture: boolean;
77
+ quadTexture: boolean;
78
+ motionBlur: boolean;
79
+ size?: AnimatedFloat;
80
+ rotate?: AnimatedFloat;
81
+ aspect?: AnimatedFloat;
82
+ }
83
+ interface DustAppearance extends AppearanceCommon, Sprite {
84
+ kind: 'dust-appearance';
85
+ size?: AnimatedFloat;
86
+ }
87
+ interface PerpAppearance extends AppearanceCommon, Sprite {
88
+ kind: 'perp-appearance';
89
+ size?: AnimatedFloat;
90
+ viewingAngleFade: boolean;
91
+ }
92
+ interface RectAppearance extends AppearanceCommon, Sprite {
93
+ kind: 'rect-appearance';
94
+ motionBlur: boolean;
95
+ centerOnPos: boolean;
96
+ viewingAngleFade: boolean;
97
+ scale?: AnimatedFloat;
98
+ length?: AnimatedFloat;
99
+ width?: AnimatedFloat;
100
+ }
101
+ interface BeamAppearance extends AppearanceCommon, Sprite {
102
+ kind: 'beam-appearance';
103
+ quadTexture: boolean;
104
+ disablePlaceHolder: boolean;
105
+ dupeFirstParticle: boolean;
106
+ scale?: AnimatedFloat;
107
+ width?: AnimatedFloat;
108
+ }
109
+ interface OrientedAppearance extends AppearanceCommon, Sprite {
110
+ kind: 'oriented-appearance';
111
+ scale?: AnimatedFloat;
112
+ width?: AnimatedFloat;
113
+ height?: AnimatedFloat;
114
+ viewingAngleFade: boolean;
115
+ /** Keyed on each particle's age, on top of its position (`0x622c6dd`). */
116
+ particleTransform?: AnimatedTransform;
117
+ }
118
+ interface ParticleAppearance extends AppearanceCommon {
119
+ kind: 'particle-appearance';
120
+ lifeName?: string;
121
+ deathName?: string;
122
+ useDynamicRotation: boolean;
123
+ smoothRotation: boolean;
124
+ }
125
+ interface MeshAppearance extends AppearanceCommon {
126
+ kind: 'mesh-appearance';
127
+ }
128
+ interface FieldCommon extends Common {
129
+ category: 'field';
130
+ }
131
+ interface RadialField extends FieldCommon {
132
+ kind: 'radial-field';
133
+ radius?: AnimatedCurve;
134
+ attenuation?: AnimatedFloat;
135
+ magnitude?: AnimatedCurve;
136
+ approach?: AnimatedCurve;
137
+ }
138
+ interface CollideField extends FieldCommon {
139
+ kind: 'collide-field';
140
+ reflectivity?: AnimatedCurve;
141
+ }
142
+ interface TurbulenceField extends FieldCommon {
143
+ kind: 'turbulence-field';
144
+ magnitude?: AnimatedCurve;
145
+ approach?: AnimatedCurve;
146
+ }
147
+ interface AirField extends FieldCommon {
148
+ kind: 'air-field';
149
+ magnitude?: AnimatedCurve;
150
+ approach?: AnimatedCurve;
151
+ }
152
+ interface GravityField extends FieldCommon {
153
+ kind: 'gravity-field';
154
+ gravity?: AnimatedCurve;
155
+ }
156
+ interface DustField extends FieldCommon {
157
+ kind: 'dust-field';
158
+ maxRadius?: AnimatedCurve;
159
+ }
160
+ interface BeamField extends FieldCommon {
161
+ kind: 'beam-field';
162
+ }
163
+ interface FolderNode extends Common {
164
+ category: 'folder';
165
+ kind: 'folder';
166
+ }
167
+ type NodeDescriptor = CubeEmitter | SphereEmitter | ConeEmitter | BasicAppearance | DustAppearance | PerpAppearance | RectAppearance | BeamAppearance | OrientedAppearance | ParticleAppearance | MeshAppearance | RadialField | CollideField | TurbulenceField | AirField | GravityField | DustField | BeamField | FolderNode;
168
+ type Appearance = Extract<NodeDescriptor, {
169
+ category: 'appearance';
170
+ }>;
171
+ interface EffectNode {
172
+ /** The file's entry this came from, which is how a host names it. */
173
+ instance: NodeInstance;
174
+ /** What a CRC resolves to. A flagged entry is a folder and never looked up (`0x621efe7`). */
175
+ descriptor?: NodeDescriptor;
176
+ /** The CRC, as text, when it resolved to nothing or to a type no DLL registers. */
177
+ missing?: string;
178
+ /** Index in `EffectScene.nodes`, or -1 at the top level. */
179
+ parent: number;
180
+ /** In the order they're inserted into their parent, which is entry order. */
181
+ children: number[];
182
+ /** From the `Pair` records, in file order. */
183
+ targets: number[];
184
+ /** Sorts siblings: appearances 100, fields 200, emitters 300, folders 400 (`+0x38`). */
185
+ key: number;
186
+ }
187
+ interface EffectScene {
188
+ name: string;
189
+ nodes: EffectNode[];
190
+ /** Top-level nodes, in entry order. */
191
+ top: number[];
192
+ /** The node a placement reaches, or none, in which case every top-level node takes it. */
193
+ root?: number;
194
+ }
195
+ /** Finds a node by CRC. The namespace is one across all loaded files, so which file is the host's. */
196
+ type ResolveNode = (crc: number) => Node | undefined;
197
+ /** An effect by name, for `FxParticleAppearance`. Names no library defines mean no particle. */
198
+ type EffectLookup = (name: string) => EffectScene | undefined;
199
+ export declare const buildEffect: (effect: Effect, resolve: ResolveNode) => EffectScene;
200
+ /** Builds and caches effects by name, the first of a name winning. */
201
+ export declare const lookupOver: (effects: readonly Effect[], resolve: ResolveNode) => EffectLookup;
202
+ /** How many of a kind the game's pool holds, from `dacom.ini`. A full one evicts its oldest. */
203
+ type PoolSizes = Readonly<Record<Appearance['kind'], number>>;
204
+ export declare const RETAIL_POOLS: PoolSizes;
205
+ /** Where evictions are counted, and the random stream. Effects sharing one compete as in the game. */
206
+ interface EffectPools {
207
+ sizes: PoolSizes;
208
+ held: Map<Appearance['kind'], Set<AppearanceNode>>;
209
+ serial: number;
210
+ rngState: number;
211
+ rngCount: number;
212
+ }
213
+ export declare const createPools: (seed?: number, sizes?: PoolSizes) => EffectPools;
214
+ interface RunContext {
215
+ pools: EffectPools;
216
+ lookup?: EffectLookup;
217
+ sparam: number;
218
+ lod: number;
219
+ }
220
+ interface EffectRun {
221
+ context: RunContext;
222
+ scene: EffectScene;
223
+ nodes: RunNode[];
224
+ top: RunNode[];
225
+ root?: RunNode;
226
+ }
227
+ interface RunNode {
228
+ run: EffectRun;
229
+ index: number;
230
+ key: number;
231
+ descriptor?: NodeDescriptor;
232
+ parent?: RunNode;
233
+ children: RunNode[];
234
+ age: number;
235
+ lifeSpan: number;
236
+ /** Marked by `+0x5c`: it no longer updates or draws. */
237
+ dead: boolean;
238
+ world: Matrix4;
239
+ /** `+0xc(1, M)`: `parent · override · local`, how a cutscene's placement reaches its node. */
240
+ override?: Matrix4;
241
+ /**
242
+ * `+0xc(0, M)`: the node's world for the next compose, outright, its parent and its own transform
243
+ * left out (`alchemy!0x624012a`, `0x62401b1`). How every in-flight effect is placed.
244
+ */
245
+ placed?: Matrix4;
246
+ update: (dt: number) => boolean;
247
+ died?: () => void;
248
+ }
249
+ /** What a field holds between its update and an appearance reading it. */
250
+ type FieldState = {
251
+ kind: 'gravity-field';
252
+ change: number;
253
+ } | {
254
+ kind: 'air-field';
255
+ magnitude: number;
256
+ blend: number;
257
+ } | {
258
+ kind: 'turbulence-field';
259
+ magnitude: number;
260
+ blend: number;
261
+ } | {
262
+ kind: 'radial-field';
263
+ magnitude: number;
264
+ inverseRadius: number;
265
+ attenuation?: AnimatedFloat;
266
+ blend: number;
267
+ } | {
268
+ kind: 'collide-field';
269
+ reflectivity: number;
270
+ dt: number;
271
+ } | {
272
+ kind: 'dust-field';
273
+ radius: number;
274
+ } | {
275
+ kind: 'beam-field';
276
+ };
277
+ interface FieldNode extends RunNode {
278
+ state: FieldState;
279
+ }
280
+ /** A run of a lane's records, in emission order, with the holes a ballistic lane leaves. */
281
+ interface Lane {
282
+ records: Float32Array;
283
+ capacity: number;
284
+ /** Up to here is gone. */
285
+ tail: number;
286
+ head: number;
287
+ count: number;
288
+ /** Pushed each step, so `born == clock` and the vertex stage reads records as they stand. */
289
+ stepped: boolean;
290
+ /** Seconds since it last rebased, so a float stays exact. Only `clock - BORN` means anything. */
291
+ clock: number;
292
+ /** Bumped when records already uploaded change. */
293
+ version: number;
294
+ /** Births, whose parity is the beam's texture mirror. */
295
+ births: number;
296
+ }
297
+ interface ParticleRecord {
298
+ state: Float32Array;
299
+ lifeRun: EffectRun;
300
+ deathRun?: EffectRun;
301
+ blend: Vector4;
302
+ }
303
+ interface AppearanceNode extends RunNode {
304
+ descriptor: Appearance;
305
+ /** How many emitters feed it (`+0xb0`): none, and nothing held, and it's done. */
306
+ refs: number;
307
+ fields: FieldNode[];
308
+ lane?: Lane;
309
+ records?: ParticleRecord[];
310
+ }
311
+ interface AdvanceOptions {
312
+ /** The outer key of every animated property. 0 when absent, as the game's engine-less effects. */
313
+ sparam?: number;
314
+ /** `[EffectLOD]`'s key. 1 when absent, what the game shows close up. */
315
+ lod?: number;
316
+ /** Where the host has the effect: reaches its control root, or else every top-level node. */
317
+ placement?: Matrix4;
318
+ /**
319
+ * How {@link placement} lands. `world`, every in-flight effect's (`ParticleEffect`,
320
+ * `Freelancer!0x4f8baa`), replaces the node's world outright, so a rootless effect's top-level
321
+ * transform is never applied. `override`, a cutscene's (`Thorn_Psys`), is `parent · M · local`.
322
+ */
323
+ placing?: 'world' | 'override';
324
+ /** The camera's world to view, which the dust's streak and fade read. */
325
+ view?: Matrix4;
326
+ }
327
+ export declare class EffectInstance {
328
+ #private;
329
+ readonly top: RunNode[];
330
+ readonly context: RunContext;
331
+ /** Seconds, the effect's clock, which is also its top nodes' age. */
332
+ time: number;
333
+ /** `view` as given to the last advance, and the one before, for the dust. */
334
+ view?: Matrix4;
335
+ previousView?: Matrix4;
336
+ lastStep: number;
337
+ cameraTravel: number;
338
+ previousTravel: number;
339
+ constructor(scene: EffectScene, { lookup, pools }?: {
340
+ lookup?: EffectLookup;
341
+ pools?: EffectPools;
342
+ });
343
+ /**
344
+ * No live appearance: what the game asks before dropping an effect (`+0x2c(4)`,
345
+ * `Freelancer!0x4f8a88`). An emitter, field or folder still alive doesn't keep it.
346
+ */
347
+ get finished(): boolean;
348
+ /** Its emitters stop, and what they made lives out (`+0x34(1)`, `0x4f84d0`). */
349
+ stop(): void;
350
+ /**
351
+ * Gone: every node dies at once, and what it held leaves the pools, as the game releases the Alchemy
352
+ * instance of an effect it destroys. One dropped without this keeps its share of the pools, and
353
+ * every later birth counts it.
354
+ */
355
+ release(): void;
356
+ /**
357
+ * Every live particle, in sub-effects too: which appearance holds it, its age and span, and where it
358
+ * stands and heads in the world, as the sprite's vertex stage carries a record to now.
359
+ */
360
+ eachParticle(visit: (node: EffectNode, age: number, life: number, position: Vector3, velocity: Vector3) => void): void;
361
+ /** One of the game's frames, at the step the host chooses (`0x4f8a20`). */
362
+ advance(dt: number, { sparam, lod, placement, placing, view }?: AdvanceOptions): void;
363
+ }
364
+ interface UVRect {
365
+ u1: number;
366
+ v1: number;
367
+ u2: number;
368
+ v2: number;
369
+ }
370
+ interface EffectDrawCommon {
371
+ /** The appearance, as its file names it. */
372
+ node: EffectNode;
373
+ /** The appearance's world, as last composed: the game draws with it unadvanced. */
374
+ world: Matrix4;
375
+ texture?: {
376
+ name: string;
377
+ entry: Texture;
378
+ };
379
+ blend?: Blending;
380
+ }
381
+ /** The lists a sprite reads, keyed in the vertex stage by each particle's age. */
382
+ interface SpriteLists {
383
+ kind: number;
384
+ color?: AnimatedColor;
385
+ alpha?: AnimatedFloat;
386
+ a?: AnimatedFloat;
387
+ b?: AnimatedFloat;
388
+ c?: AnimatedFloat;
389
+ roll?: AnimatedFloat;
390
+ frame?: AnimatedFloat;
391
+ }
392
+ interface SpriteDraw extends EffectDrawCommon {
393
+ type: 'sprite';
394
+ lane: Lane;
395
+ /** Its clock now: what `BORN` is read against. */
396
+ time: number;
397
+ sparam: number;
398
+ /** `Appearance_LODCurve` at the host's key, on every extent but the dust's. */
399
+ extent: number;
400
+ facing: number;
401
+ shape: number;
402
+ blur: boolean;
403
+ centered: boolean;
404
+ fade: boolean;
405
+ lists: SpriteLists;
406
+ /** The atlas frames a particle's own clock picks among. */
407
+ frames: readonly UVRect[];
408
+ /** Which of them a particle's -1 picks: the playback's. 0 when absent. */
409
+ currentFrame?: number;
410
+ /** The camera, in the appearance's frame. */
411
+ eye: Vector3;
412
+ dust?: {
413
+ previous: Matrix4;
414
+ step: number;
415
+ fade: number;
416
+ };
417
+ }
418
+ /** Interleaved `position, colour, uv` triangles, already in the appearance's frame: a beam, or what the vertex stage can't key. */
419
+ interface TriangleDraw extends EffectDrawCommon {
420
+ type: 'triangles';
421
+ vertices: Float32Array;
422
+ count: number;
423
+ }
424
+ type EffectDraw = SpriteDraw | TriangleDraw;
425
+ interface MaterialMapRule {
426
+ on: 'name' | 'type';
427
+ pattern: RegExp;
428
+ to: string;
429
+ }
430
+ /**
431
+ * Retail's section, in file order. `shading.dll` matches with Spencer's regular expressions, which
432
+ * search rather than anchor (`0x6ed0540`), and these patterns mean the same in JavaScript. So a type
433
+ * rule's plain text matches anywhere in the type. A host with a mod's `dacom.ini` passes its own rules
434
+ * as `SceneModel.materialMap`: JavaScript's patterns are a superset, and how a mod's unusual pattern
435
+ * differs under Spencer's engine is unmeasured.
436
+ */
437
+ export declare const MATERIAL_MAP: readonly MaterialMapRule[];
438
+ /**
439
+ * The type a material is created as. Each rule is added at the front, so the last listed wins
440
+ * (`FMaterialLibrary::AddMapRule`, `RemapType`). Only the name is lowercased (`_strlwr`, `0x6ec9afa`).
441
+ */
442
+ export declare const mapMaterialType: ({ name, type }: Pick<Material, "name" | "type">, rules?: readonly MaterialMapRule[]) => string;
443
+ type TextureFilter = 'nearest' | 'linear';
444
+ type TextureWrap = 'repeat' | 'mirror' | 'clamp';
445
+ interface SamplerState {
446
+ magnify: TextureFilter;
447
+ minify: TextureFilter;
448
+ /** Between mip levels. */
449
+ mipmap: TextureFilter;
450
+ wrapU: TextureWrap;
451
+ wrapV: TextureWrap;
452
+ }
453
+ interface Camera {
454
+ position: Vector3;
455
+ /** Rows, as `./thn/scene` reads a THN `orient`. Looks down local -Z, with +Y up (`0x5568b0`). */
456
+ orientation: Matrix3;
457
+ /** Half the horizontal field of view, in degrees (`RoomCameraSetHalfFovX`). */
458
+ fovh: number;
459
+ near: number;
460
+ far: number;
461
+ }
462
+ /** `Mᵀ(p - position)`, the game's own right-handed view space (`0x40ff50`). */
463
+ export declare const viewMatrix: ({ position, orientation }: Camera) => Matrix4;
464
+ /** A viewport's size in pixels. */
465
+ interface Viewport {
466
+ width: number;
467
+ height: number;
468
+ }
469
+ /**
470
+ * `FRoomCamera::ClassifySphere`'s rank 2 (`0x55687a`): `π (R / z)²` times its pixel scales, under one
471
+ * pixel. `HalfWidth` is `(width - 1) / 2`, as `Rect_to_ViewRect`'s edges are inclusive, and its Y
472
+ * tangent is X's over `width / height` (`RoomCameraConstruct`, `FUN_00556280`).
473
+ */
474
+ export declare const underPixel: (z: number, radius: number, fovh: number, { width, height }: Viewport) => boolean;
475
+ /**
476
+ * RP8 flips view Z and applies a left-handed D3D projection (`FDirectX8::SetPerspective`). For a GL
477
+ * clip space that comes to this plain right-handed perspective: no axis is negated, and z / w maps
478
+ * onto the same window depth as D3D's. `hvaspect` never reaches the screen, so aspect is the viewport's.
479
+ */
480
+ export declare const projectionMatrix: ({ fovh, near, far }: Camera, aspect: number) => Matrix4;
481
+ /** `uv * scale + offset`, on stage 0 alone (`0x6ec7330`, `rp8.dll` \`0x6d0fdd2\`). */
482
+ interface UvTransform {
483
+ offset: [number, number];
484
+ scale: [number, number];
485
+ }
486
+ /**
487
+ * `FImaInstance::Tick` and `FillUvMatrix`: a segment's duration is its own length, and the walk moves
488
+ * on only strictly past it, wrapping always, as nothing reads `MAFlags`. A segment starts at the last
489
+ * one's key, or four zeros, and its velocities are rates. Stored scales are displacements from 1.
490
+ */
491
+ export declare const sampleUvAnimation: ({ segments }: MaterialAnim, time: number) => UvTransform;
492
+ /** One level-of-detail list an archetype writes: its own `LODranges`, or a `[LOD]` block's. */
493
+ interface LodList {
494
+ /** The part a `[LOD]` block is for, by name. Absent on the section's own list. */
495
+ obj?: string;
496
+ /** As written, at most eight. */
497
+ ranges: readonly number[];
498
+ /** `MinSpecLOD`: the levels low detail skips. 1 when absent, but 0 under three ranges. */
499
+ minSpecLod?: number;
500
+ }
501
+ /** The `level_of_detail` option, 0 to 1. At 0.75, the default here, it changes nothing. */
502
+ interface LodDetail {
503
+ detail?: number;
504
+ }
505
+ /** `ApplyLevelOfDetail` (`0x5b4560`): low detail below 0.75, and the scale the far clamp tests. */
506
+ export declare const lodScale: (detail: number) => {
507
+ low: boolean;
508
+ scale: number;
509
+ };
510
+ /**
511
+ * The archetype's `LodPick` (`ComputeLodPickThis`, `0x4021b0`): the largest range any part's list
512
+ * walked, past which the object draws nothing. -1 when none did. A single model's part is `Root`.
513
+ */
514
+ export declare const lodRange: (parts: readonly {
515
+ source: MeshSource;
516
+ name: string;
517
+ }[], lists: readonly LodList[], { detail }?: LodDetail) => number;
518
+ /**
519
+ * Each level's end, in `Switch2`'s unit. The first list with no `obj` or with this part's name
520
+ * overwrites the model's own entry by entry, as far as it reaches and no further (`0x401ed0`), and a
521
+ * negative value ends it. With no `Switch2` the game's floats are uninitialised: `NaN`, which no metric
522
+ * reaches. `lists` are in matching order, `[LOD]` blocks first.
523
+ */
524
+ export declare const levelEnds: (source: MeshSource, name: string, lists?: readonly LodList[], { detail }?: LodDetail) => number[];
525
+ /**
526
+ * `LodSetPick` (`0x6c32040`): the first level whose end reaches the metric. Only ends are read, so a
527
+ * level ending under the one before it is never chosen. Past the last, the part draws nothing.
528
+ */
529
+ export declare const levelAt: (source: MeshSource, metric: number, ends: readonly number[]) => VMeshPart | undefined;
530
+ /**
531
+ * `FRenderManager::RenderInstance`: view depth of the part's centre, not its distance, times
532
+ * tan(fovx½), over the largest axis of the matrix given to the draw. So it widens with the view. The
533
+ * centre being the top level's bounding sphere is inferred (`IEngine +0x88`). Threshold is 1 in a room
534
+ * (`room_control.cpp`); space's isn't read yet.
535
+ */
536
+ export declare const levelMetric: (source: MeshSource, part: Matrix4, transform: Matrix4, camera: Camera) => number;
537
+ /** An `[IGraph]`'s `point = x, value` keys, in file order. A colour graph's values are `0xAARRGGBB`. */
538
+ interface Graph {
539
+ points: readonly (readonly [x: number, value: number])[];
540
+ }
541
+ /** A `[LightSource]`, in the order the system file lists them, which is the game's (`GameLightEnable`). */
542
+ interface SpaceLight {
543
+ type: 'ambient' | 'point' | 'cone' | 'directional';
544
+ position: Vector3;
545
+ /** Made unit, as `direction` is read. Down -Z when absent. */
546
+ direction?: Vector3;
547
+ /** As written, 0 to 255. */
548
+ color: Vector3;
549
+ range: number;
550
+ /** Constant, linear and quadratic. `(0, 1, 0)` when absent (`FEngineLight`'s constructor). */
551
+ attenuation?: Vector3;
552
+ /** `atten_curve`, but an ambient light's is its `ambientCurve`: the colour over `[0, range]`. */
553
+ attenuationCurve?: Graph;
554
+ /** `ambient_curve`: what an ambient light adds, over `[0, range]`. */
555
+ ambientCurve?: Graph;
556
+ /** `color_curve`: a tint over the host's clock, repeating every `period` seconds. */
557
+ colorCurve?: {
558
+ graph: Graph;
559
+ period: number;
560
+ };
561
+ /** A cone's angles. A `[LightSource]` has none, so its cone lights nothing; equipment's has. */
562
+ spot?: Spot;
563
+ }
564
+ /** A `[Light]` equipment's keys, and where its hardpoint is. */
565
+ interface EquipmentLight {
566
+ /** The hardpoint's frame in the world: it shines down its -Z. */
567
+ world: Matrix4;
568
+ /** `color`, 0 to 1. */
569
+ color: Vector3;
570
+ /** `intensity`: its range. No light at all at 0 or less. */
571
+ intensity: number;
572
+ /** `lightsource_cone`, a full angle in degrees: a spot below 180, a point light otherwise. */
573
+ cone?: number;
574
+ }
575
+ /**
576
+ * `CliEquipLightInit` (`0x4fda00`): the light a `[Light]` carries, which retail makes only for the
577
+ * player's own ship. Its range is its intensity, falling off by 16 / range² (`EngineLightSetRadius`).
578
+ */
579
+ export declare const equipmentLight: ({ world, color, intensity, cone }: EquipmentLight) => SpaceLight | undefined;
580
+ /** A `[LightAnim]`: its `frame = time, r, g, b, intensity` keys, as written, in file order. */
581
+ interface LightAnimation {
582
+ frames: readonly {
583
+ time: number;
584
+ color: Vector3;
585
+ intensity: number;
586
+ }[];
587
+ }
588
+ /** An `[Effect]`'s light keys, and the running effect's place and age. */
589
+ interface EffectLight {
590
+ /** Where the effect is: it has no direction. */
591
+ position: Vector3;
592
+ /** `lgt_radius`: its range. No light at all at 0 or less. */
593
+ radius: number;
594
+ /** `lgt_color`, as written, 0 to 255. */
595
+ color: Vector3;
596
+ /** The `[LightAnim]` its `lgt_effect` names. */
597
+ animation?: LightAnimation;
598
+ /** `lgt_range_scale`, which scales each key's intensity. 1 when absent. */
599
+ rangeScale?: number;
600
+ /** `lgt_time_scale`, which scales each key's time. 1 when absent. */
601
+ timeScale?: number;
602
+ /** Seconds since it started. Retail adds each frame's step to a float. */
603
+ elapsed: number;
604
+ }
605
+ /**
606
+ * The light an effect carries (`CreateEffectStart`, `0x544299`), run by its animation
607
+ * (`GameLightTickAnim`, `0x5386d0`): gone once the animation's last key has passed.
608
+ */
609
+ export declare const effectLight: ({ position, radius, color, animation, rangeScale, timeScale, elapsed }: EffectLight) => SpaceLight | undefined;
610
+ /** What one object is lit by: at most four lights, and the ambient, 0 to 1. */
611
+ interface ObjectLighting {
612
+ lights: SceneLight[];
613
+ ambient: Vector3;
614
+ }
615
+ /** `FGraphData::Evaluate` over `[0, max]`, clamped. */
616
+ export declare const evaluateGraph: (graph: Graph, t: number, max: number) => number;
617
+ /**
618
+ * `FGraphData::EvaluateColor` over `[0, period]`, repeating: its bytes. Its weight runs from the low
619
+ * key while the blend still gives the rest to the high one, so retail's colour curves run backwards
620
+ * between keys (`0x41963c`).
621
+ */
622
+ export declare const evaluateColorGraph: (graph: Graph, t: number, period: number) => Vector3;
623
+ interface Entry {
624
+ light: number;
625
+ x: number;
626
+ z: number;
627
+ range: number;
628
+ }
629
+ interface Cell {
630
+ x: number;
631
+ z: number;
632
+ half: number;
633
+ children: (Cell | undefined)[];
634
+ /** Newest first, as each payload is pushed on the front. */
635
+ entries: Entry[];
636
+ }
637
+ /**
638
+ * The XZ quadtree `LightGridRebuildImpl` builds once per system. Its cells only narrow the search, but
639
+ * the order it hands lights back in decides which of equal range win the last of the four places.
640
+ */
641
+ declare class LightGrid {
642
+ readonly root: Cell;
643
+ private cells;
644
+ private entries;
645
+ constructor(lights: readonly SpaceLight[]);
646
+ private cell;
647
+ private child;
648
+ /** `LightGridInsert` (`0x537d20`): into every quadrant its square reaches, while it fits one. */
649
+ private insert;
650
+ private descend;
651
+ /** `LightTreeCollect`: each cell the query's square reaches, itself before its children. */
652
+ collect(cell: Cell | undefined, x: number, z: number, radius: number, into: Cell[]): void;
653
+ }
654
+ /**
655
+ * The directional lights `BandBakeLighting` (`0x54cfb0`) takes at a band's zone: of the first 16
656
+ * `CollectNearbyLights` gives within 100, the first four directional ones, each the way it travels
657
+ * (`EngineLightSample`). There's no reach test, and an ambient light adds nothing here.
658
+ */
659
+ export declare const directionalLightsAt: ({ lights, grid }: LightSet, position: Vector3, time: number) => {
660
+ found: number;
661
+ lights: SceneLight[];
662
+ };
663
+ /** `[Ambient] color` as the device takes it: whole bytes, 0 to 1. */
664
+ export declare const systemAmbient: ({ x, y, z }: Vector3) => Vector3;
665
+ /** What `SpaceObject`'s lighting reads, built once a frame from a system's lights. */
666
+ interface LightSet {
667
+ lights: readonly SpaceLight[];
668
+ grid: LightGrid;
669
+ }
670
+ export declare const lightSet: (lights: readonly SpaceLight[]) => LightSet;
671
+ /** The `[NebulaLight] ambient` an object is in, as written, and how far in, from 0 to 1. */
672
+ interface NebulaAmbient {
673
+ ambient: Vector3;
674
+ factor: number;
675
+ }
676
+ /**
677
+ * `StarSysApplyObjectLighting` with every option on, from where the object stands and its sphere's
678
+ * radius. `ambient` is the system's `[Ambient]`, as written. Its fog is `collectSpaceFrame`'s.
679
+ */
680
+ export declare const lightObject: ({ lights, grid }: LightSet, position: Vector3, radius: number, ambient: Vector3, time: number, nebula?: NebulaAmbient) => ObjectLighting;
681
+ /** `[BackgroundLightning]`, each key over what `FNebulaEffect`'s constructor sets. */
682
+ interface BackgroundLightningSettings {
683
+ /** `gap`: about how long between flashes, from 0.8 to 1.5 of it. */
684
+ gap: number;
685
+ /** `duration`: how long a flash lasts. None at all at 0 or less. */
686
+ duration: number;
687
+ /** `color` over 255: added to the fog's, by up to `intensity`. */
688
+ color: Vector3;
689
+ intensity: number;
690
+ }
691
+ /** `[DynamicLightning]`, each key over what `FNebulaEffect`'s constructor sets. */
692
+ interface DynamicLightningSettings {
693
+ /** `gap`: about how long between flashes, from 0.8 to 1.5 of it. */
694
+ gap: number;
695
+ /** `duration`: how long a flash lasts. No light at all at 0 or less. */
696
+ duration: number;
697
+ /** `color` over 255, by up to the intensity. */
698
+ color: Vector3;
699
+ /** `ambient_intensity`. */
700
+ ambientIntensity: number;
701
+ /** `intensity_increase`: added while the background flashes too. */
702
+ intensityIncrease: number;
703
+ }
704
+ /** Checked in `Freelancer-original.exe`, `0x504b06` to `0x504b42`: every nebula flashes unless told not to. */
705
+ export declare const RETAIL_BACKGROUND_LIGHTNING: Readonly<BackgroundLightningSettings>;
706
+ /** Checked in `Freelancer-original.exe`, `0x504b4e` to `0x504b8a`. */
707
+ export declare const RETAIL_DYNAMIC_LIGHTNING: Readonly<DynamicLightningSettings>;
708
+ /** `rand()`'s range: whole numbers from 0 to 32767. */
709
+ type Random = () => number;
710
+ /**
711
+ * One nebula's lightning timers, which the host keeps for as long as the nebula exists.
712
+ * `collectSpaceFrame` advances them as retail does: the light on every frame by the scene's `step`,
713
+ * and the background only on a frame the camera is deep in the nebula. Retail draws from the game's
714
+ * one `rand()`, so its flashes can't be matched, only their kind.
715
+ */
716
+ export declare class NebulaLightning {
717
+ #private;
718
+ readonly background: BackgroundLightningSettings;
719
+ readonly dynamic: DynamicLightningSettings;
720
+ readonly random: Random;
721
+ constructor(settings?: {
722
+ background?: Partial<BackgroundLightningSettings>;
723
+ dynamic?: Partial<DynamicLightningSettings>;
724
+ }, random?: Random);
725
+ /** A flash on the next step rather than after its wait, for an editor: retail has no such call. */
726
+ fire(kind: 'background' | 'dynamic'): void;
727
+ /** `FNebulaEffect::Update`'s part, each frame: `depth` is its `+0x294`, where `eye` is. */
728
+ update(step: number, depth: number, eye: Vector3): void;
729
+ /** Its light while it flashes: directional, at the camera, after the system's own. */
730
+ get light(): SpaceLight | undefined;
731
+ /** `FillFogRgb`: `base`, the fog's colour, or a flash over it, `update`'s step later. */
732
+ fogColor(base: Vector3): Vector3;
733
+ }
734
+ /** A `[Light]`'s flare keys, as `CliEQLight` holds them. The host reads the INI, `inherit` too. */
735
+ interface FlareLight {
736
+ /** `color` over 255. */
737
+ color: Vector3;
738
+ /** `glow_color` over 255. A `color` line copies itself here while this is still black. */
739
+ glowColor: Vector3;
740
+ /** `min_color` over 255: the bulb's colour while the blink is dark. */
741
+ minColor: Vector3;
742
+ bulbSize: number;
743
+ glowSize: number;
744
+ /** `avg_delay`: about how long between blinks. None at 0 or less. */
745
+ avgDelay: number;
746
+ /** `blink_duration`: how long one lasts, at most `avg_delay`'s draw. */
747
+ blinkDuration: number;
748
+ /**
749
+ * `flare_cone` in degrees, as written: past the first from its forward axis the glow is gone, and
750
+ * from the second to the first it fades. None when absent.
751
+ */
752
+ flareCone?: readonly [number, number];
753
+ }
754
+ /** `FCliEQLight`'s constructor (`0x40a9e0`), what a `[Light]` with no keys has. */
755
+ export declare const RETAIL_FLARE_LIGHT: Readonly<FlareLight>;
756
+ /**
757
+ * A light's blink: `CliEquipLightInit` (`0x4fda00`) and its tick (`0x4fdd00`). A triangle up to 1 and
758
+ * back over the lit part of each period, dark for the rest; always 1 when it doesn't blink. Each
759
+ * light draws its period and phase from the game's `rand()` as it's made.
760
+ */
761
+ export declare class LightBlink {
762
+ #private;
763
+ brightness: number;
764
+ constructor({ avgDelay, blinkDuration }: Pick<FlareLight, 'avgDelay' | 'blinkDuration'>, random?: Random);
765
+ /**
766
+ * A period and phase given, not drawn: `FFlashLight`'s second constructor (`FUN_00557630`), which a
767
+ * room's `[FlashlightSet]` makes its lights with. A phase below 0 stays dark until it's passed 0.
768
+ */
769
+ static timed(period: number, phase: number, blinkDuration: number): LightBlink;
770
+ /** Where in its period it is, 0 to 1. */
771
+ get phase(): number;
772
+ /** Advances by `step`, which the game holds to 0.1, only on a frame its object drew. */
773
+ tick(step: number): void;
774
+ }
775
+ /** A light on a ship or solar: its keys, where its hardpoint is, and its blink. */
776
+ interface SpaceFlare {
777
+ light: FlareLight;
778
+ /** The hardpoint's frame in the world. */
779
+ world: Matrix4;
780
+ /** Steady at full brightness when absent. */
781
+ blink?: LightBlink;
782
+ }
783
+ /** One quad as the batcher records it (`FScreenQuadRecord`): D3D pixels, depth and `1 / w`. */
784
+ interface ScreenQuad {
785
+ x0: number;
786
+ y0: number;
787
+ x1: number;
788
+ y1: number;
789
+ /** `1 - near / -z`, the depth it's tested at. */
790
+ z: number;
791
+ rhw: number;
792
+ u0: number;
793
+ v0: number;
794
+ u1: number;
795
+ v1: number;
796
+ /** `0xAARRGGBB`, alpha always 255. */
797
+ color: number;
798
+ }
799
+ /** A texture frame's rectangle, `GetTextureFrame`'s: `u0`, `v0`, `u1`, `v1`. Whole when absent. */
800
+ type FrameRect = readonly [number, number, number, number];
801
+ interface FlareView {
802
+ camera: Camera;
803
+ /** The camera's view, `viewMatrix(camera)`. */
804
+ view: Matrix4;
805
+ /** The projection's, width over height. */
806
+ aspect: number;
807
+ /** The viewport, in pixels. */
808
+ width: number;
809
+ height: number;
810
+ frame?: FrameRect;
811
+ }
812
+ /**
813
+ * A light's bulb then its glow at `world`, the hardpoint's frame (`ScreenQuadEnqueueFlare`,
814
+ * `0x4038f0`): squares of its sizes at its depth, each dropped under a pixel. Nothing behind the
815
+ * camera, though anything nearer than the near plane passes as retail lets it.
816
+ */
817
+ export declare const flareQuads: (light: FlareLight, world: Matrix4, brightness: number, { camera, view, aspect, width, height, frame }: FlareView) => ScreenQuad[];
818
+ interface SceneLight {
819
+ color: Vector3;
820
+ /** World space, the way the light shines. A point light's is unused. */
821
+ direction: Vector3;
822
+ /** In world space, which makes it a point light: D3D8's, with no light past `range`. */
823
+ position?: Vector3;
824
+ range?: number;
825
+ /** Constant, linear and quadratic, of the distance. */
826
+ attenuation?: Vector3;
827
+ /** With a position, a spot along `direction`: D3D8's angles, inner and outer, in radians. */
828
+ spot?: Spot;
829
+ /**
830
+ * `D3DLIGHT8`'s `Ambient`, which D3D8 adds as the material's ambient times it, attenuated and in
831
+ * the cone, whichever way the surface faces. A THN light's; a system's is black.
832
+ */
833
+ ambient?: Vector3;
834
+ }
835
+ /** `D3DLIGHT8`'s `Theta`, `Phi` and `Falloff`: full angles in radians, no light outside `phi`. */
836
+ interface Spot {
837
+ theta: number;
838
+ phi: number;
839
+ falloff: number;
840
+ }
841
+ /** What a skinned mesh adds: `skinMesh`'s influences and `boneTable`'s matrices, column-major. */
842
+ interface DrawSkin {
843
+ influences: Float32Array;
844
+ bones: Float32Array;
845
+ }
846
+ /** One piece of geometry where the host has posed it, at the detail level it chose. */
847
+ interface PlacedPart {
848
+ reference: VMeshRef;
849
+ world: Matrix4;
850
+ /** A generated mesh, which no library holds: a sphere's patch. */
851
+ mesh?: VMeshData;
852
+ /** Over each group's own, as a sphere's face names its material and its patch can't. */
853
+ materialId?: number;
854
+ /** That material's name, as the file writes it, for a report: an id can't be read back. */
855
+ materialName?: string;
856
+ skin?: DrawSkin;
857
+ /** Added to an `EyeMaterial` group's UVs, as its eye bone turns. */
858
+ eyeOffset?: [number, number];
859
+ /** What the engine tells a sphere's shell of its size. Anything else reads 1 (`Field20` unset). */
860
+ extent?: number;
861
+ }
862
+ interface SceneModel {
863
+ parts: PlacedPart[];
864
+ mesh: (id: number) => VMeshData | undefined;
865
+ material: (id: number) => Material | undefined;
866
+ texture: (name: string) => TextureEntry | undefined;
867
+ /** The archetype's `envmap_material`: the model does not say. Absent means no reflection. */
868
+ environment?: string;
869
+ /** 0 to 1, the engine's per-object value that nomad surfaces and `cloaked` read. 1 when absent. */
870
+ fade?: number;
871
+ /** Drawn through `CloakMaterial`, which the host decides: the game spares nomad archetypes. */
872
+ cloaked?: boolean;
873
+ /** UV animations, found by material name as the game does. */
874
+ materialAnimations?: MaterialAnimLibrary;
875
+ /** `[MaterialMap]` from the host's own `dacom.ini`, in file order. Retail's when absent. */
876
+ materialMap?: readonly MaterialMapRule[];
877
+ /**
878
+ * Drawn through `StarsphereMaterial`: at the camera, turned with the view alone, under white ambient
879
+ * and no lights. Or `BackdropMaterial`, where the camera stands, on planes of its own. Both ignore
880
+ * depth. The host orders models, so a sky goes first.
881
+ */
882
+ sky?: 'starsphere' | 'backdrop';
883
+ /**
884
+ * The instance's user data, which HUD icons and exclusion zones read: over `Ac` and `Oc`. The engine
885
+ * sets it, so without it they use the material's own.
886
+ */
887
+ instance?: {
888
+ ambient: Vector3;
889
+ alpha: number;
890
+ };
891
+ }
892
+ interface PlaceOptions {
893
+ /** Choose each part's level as the game does, from this camera. Unless `level` pins one. */
894
+ camera?: Camera;
895
+ /** The archetype's level-of-detail lists, in matching order, over the model's own `Switch2`. */
896
+ lod?: readonly LodList[];
897
+ /** The `level_of_detail` option, 0 to 1, which the lists answer to. 0.75 changes nothing. */
898
+ detail?: number;
899
+ /** Pins a level, clamped to what a part has. The top one when there is no `camera` either. */
900
+ level?: number;
901
+ /** A part left out draws nothing, and its children still do. */
902
+ visible?: (part: CompoundNode<RigidPart>) => boolean;
903
+ jointState?: (part: CompoundNode<RigidPart>) => JointState | undefined;
904
+ }
905
+ /** A running effect, advanced by the host, and where its textures come from. */
906
+ interface SceneEffect {
907
+ instance: EffectInstance;
908
+ texture: (name: string) => TextureEntry | undefined;
909
+ /** An appearance left out isn't drawn, and still runs: a way of looking. Every one when absent. */
910
+ visible?: (node: EffectNode) => boolean;
911
+ }
912
+ interface Scene {
913
+ camera: Camera;
914
+ /** Width over height of the viewport. */
915
+ aspect: number;
916
+ /** 0 to 1. */
917
+ ambient: Vector3;
918
+ lights: SceneLight[];
919
+ models: SceneModel[];
920
+ /** 0 to 1. Black when absent, which is what the menu clears to. */
921
+ background?: Vector3;
922
+ /**
923
+ * Seconds, the host's clock, for material UV and texture animation. 0 when absent. Retail starts a
924
+ * material's texture playback when it loads (`FMaterial::Setup`), so 0 is when the models loaded.
925
+ */
926
+ time?: number;
927
+ /** After the models, in the order given. `collectSpaceFrame` has the game's order. */
928
+ effects?: readonly SceneEffect[];
929
+ /** Over `RETAIL_FRAME_LIMITS`, for a mod that patches them. */
930
+ limits?: Partial<FrameLimits>;
931
+ }
932
+ /** What the material and appearance DLLs hardcode, and a mod can patch. */
933
+ interface FrameLimits {
934
+ /** The least alpha glass draws at, whatever its `Oc` (`FMaterialImpl::BindGlass`). */
935
+ glassLeastAlpha: number;
936
+ /** A backdrop's own near and far planes (`FFLAppearanceBase::DrawBackdrop`, `BindArg`'s first flag). */
937
+ backdropNear: number;
938
+ backdropFar: number;
939
+ }
940
+ export declare const RETAIL_FRAME_LIMITS: Readonly<FrameLimits>;
941
+ interface DrawTexture {
942
+ name: string;
943
+ entry: Texture | CubeTexture;
944
+ sampler: SamplerState;
945
+ /** Which of the mesh's UV sets it samples. */
946
+ uvSet: number;
947
+ }
948
+ /**
949
+ * Texture stage 1, in the classes that use it. With no texture the rig's picture is as if the stage were
950
+ * off (`bands`), so that's what draws, though a real D3D8 driver may read it as white: see VALIDATION.md.
951
+ */
952
+ interface DrawStage {
953
+ operation: 'add' | 'modulate2x';
954
+ texture?: DrawTexture;
955
+ /** A planet mask's `diag(tile, tile)` texture matrix, on UV0 (`SetDetailTileTransform`). */
956
+ tile?: number;
957
+ }
958
+ /** What `SubmitAtmosphereLock` needs for each vertex's UV, the rest being the same for a draw. */
959
+ interface DrawAtmosphere {
960
+ /** Towards the camera, in the object's space. */
961
+ direction: Vector3;
962
+ /** The cosine of the horizon, where `u` starts. */
963
+ threshold: number;
964
+ /** `u` where the vertex faces the camera. */
965
+ gain: number;
966
+ /** The row of the fade texture, the whole draw's. */
967
+ v: number;
968
+ }
969
+ /**
970
+ * Source and destination: `SRCALPHA / INVSRCALPHA`, `ONE / ONE`, `SRCALPHA / ONE`, `DESTCOLOR / ZERO`,
971
+ * `ONE / INVSRCCOLOR`, `ONE / INVSRCALPHA`.
972
+ */
973
+ type BlendMode = 'alpha' | 'add' | 'alphaAdd' | 'multiply' | 'screen' | 'premultiplied';
974
+ interface Draw {
975
+ mesh: VMeshData;
976
+ reference: VMeshRef;
977
+ /** The mesh was built for this frame alone: the renderer frees it once the frame's drawn. */
978
+ transient?: boolean;
979
+ /** Into the reference's planned index buffer. */
980
+ byteOffset: number;
981
+ count: number;
982
+ world: Matrix4;
983
+ materialName: string;
984
+ diffuse: [number, number, number, number];
985
+ ambient: Vector3;
986
+ emissive: Vector3;
987
+ /** The mesh has vertex colours, which take the place of `diffuse`. */
988
+ colorVertex: boolean;
989
+ /** Unlit white at `diffuse`'s alpha, and stage 1 reads the nomad ramp. */
990
+ nomad: boolean;
991
+ texture?: DrawTexture;
992
+ /** A cube, reflected in camera space, as `2 × cube × colour`. */
993
+ environment?: string;
994
+ environmentTexture?: CubeTexture;
995
+ second?: DrawStage;
996
+ uvTransform?: UvTransform;
997
+ cull: 'back' | 'front' | 'none';
998
+ depthWrite: boolean;
999
+ /** `D3DCMP_LESS` for most. `always` is `ZENABLE 0`, a sky's or the far list's: the same pixels. */
1000
+ depthFunc: 'less' | 'lequal' | 'always';
1001
+ /** Scene ambient in place of the frame's, for this draw. */
1002
+ sceneAmbient?: Vector3;
1003
+ /** The frame's lights don't reach it. */
1004
+ lightless?: boolean;
1005
+ /** In place of the frame's, for this draw: space lights each object by its own. */
1006
+ lights?: SceneLight[];
1007
+ /** In place of the frame's, for this draw. */
1008
+ projection?: Matrix4;
1009
+ /** `D3DVIEWPORT8`'s `MinZ` and `MaxZ`, for this draw: `[0, 1]` when absent. */
1010
+ depthRange?: readonly [number, number];
1011
+ /**
1012
+ * D3D8's `ZBIAS`, 0 to 16, pulling it toward the camera: a room's shadow's 4. How far a level
1013
+ * moves depth is the driver's; here one level is one unit of `polygonOffset`: uncaptured.
1014
+ */
1015
+ depthBias?: number;
1016
+ /** D3D's table fog, on depth: a nebula's, for what it holds (`StarSysApplyObjectLighting`). */
1017
+ fog?: Fog;
1018
+ negateNormals: boolean;
1019
+ normalizeNormals: boolean;
1020
+ blend: boolean;
1021
+ /** `alpha` unless a class sets its own (`bands`). */
1022
+ blendMode: BlendMode;
1023
+ /** Lighting off: white, or the vertex colour, at `diffuse`'s alpha. */
1024
+ unlit: boolean;
1025
+ /** Compared with this, of 255: `D3DRS_ALPHAFUNC`, or `always` with the test off. */
1026
+ alphaReference: number;
1027
+ alphaFunction: 'greater' | 'less' | 'always';
1028
+ atmosphere?: DrawAtmosphere;
1029
+ skin?: DrawSkin;
1030
+ }
1031
+ /**
1032
+ * Where a host's own drawing goes. `begin` is after the clear, `opaque` before the first blended draw,
1033
+ * and `end` after everything. None of them changes the game's order.
1034
+ */
1035
+ type HookName = 'begin' | 'opaque' | 'end';
1036
+ interface FrameHook {
1037
+ name: HookName;
1038
+ /** Index of the draw it goes in front of, so the length of `draws` is after the last. */
1039
+ before: number;
1040
+ }
1041
+ interface ScreenQuadBatch {
1042
+ texture: Texture;
1043
+ quads: ScreenQuad[];
1044
+ }
1045
+ interface Frame {
1046
+ background: Vector3;
1047
+ hooks: FrameHook[];
1048
+ view: Matrix4;
1049
+ projection: Matrix4;
1050
+ ambient: Vector3;
1051
+ lights: SceneLight[];
1052
+ draws: Draw[];
1053
+ /** Each appearance of each effect, after `draws`, in the game's tree order. */
1054
+ effects: EffectDraw[];
1055
+ /** Over every effect, as `SpaceObjDrawPass` sets it in a nebula. */
1056
+ effectFog?: Fog;
1057
+ /** `less` when absent, as the menu's effects capture it; space's are `lequal`. */
1058
+ effectDepthFunc?: 'less' | 'lequal';
1059
+ /** Each tractor's lines, after the effects, in the walk's own bucket (`EFT_TRACTORBEAM`). */
1060
+ tractors?: Draw[];
1061
+ /** Every shot of the frame, once the effects are walked (`EffectVertexBatchFlush`). */
1062
+ beams?: Draw;
1063
+ /**
1064
+ * Screen quads, after the effects, in D3D's pixels: added, the texture times each quad's colour,
1065
+ * `lequal` without writing depth (`ScreenQuadFlush` in `SpaceEffectsDrawFrame`), under
1066
+ * `effectFog` by `1 / rhw`. Each batch is one texture's.
1067
+ */
1068
+ screenQuads?: ScreenQuadBatch[];
1069
+ /**
1070
+ * Clear depth before `afterEffects`: from the cockpit, with the HUD on or the cockpit showing
1071
+ * (`FirstPersonCameraSceneDraw`, `0x518463`). None of what's already drawn can then hide what's next.
1072
+ */
1073
+ clearDepth?: boolean;
1074
+ /**
1075
+ * Drawn after the screen quads, before the `end` hook: a sun's corona, which the game draws once
1076
+ * its effects are done (`ScaleEnvironmentEffects`, `0x51c7d4`), and from the cockpit, its mesh.
1077
+ */
1078
+ afterEffects?: Draw[];
1079
+ /**
1080
+ * After the `end` hook: what the cockpit's mesh queued with the effects option off, which the
1081
+ * game's batcher draws only as the scene ends, past the HUD and every sun's flare (`EndSceneHalf`).
1082
+ */
1083
+ atEnd?: Draw[];
1084
+ /** What didn't draw as authored: materials M3 is to cover, and any mesh or texture not found. */
1085
+ unsupported: string[];
1086
+ /** Materials of a type no DLL registers. They draw nothing in retail either. */
1087
+ unregistered: string[];
1088
+ }
1089
+ /** Whether `collectFrame` draws this type, the one `[MaterialMap]` gives. */
1090
+ export declare const isBuiltType: (type: string) => boolean;
1091
+ /** A compound model's parts, depth first (`li_elite`), for a host that doesn't pose its own. */
1092
+ export declare const placeParts: (root: RigidModel, transform: Matrix4, options?: PlaceOptions) => PlacedPart[];
1093
+ export declare const collectFrame: ({ camera, aspect, ambient, lights, models, background, time, effects, limits }: Scene) => Frame;
1094
+ /** A `[Shape]`'s rectangle in its texture, as UVs. */
1095
+ interface TextureShape {
1096
+ texture: Texture;
1097
+ u0: number;
1098
+ v0: number;
1099
+ u1: number;
1100
+ v1: number;
1101
+ }
1102
+ /**
1103
+ * `TexShapeSetRect` (`0x4256c0`): a `[Shape]`'s `x`, `y`, `w` and `h` in `texture`'s pixels. A corner
1104
+ * of 1 or less is a fraction of the texture, and so is an extent; a zero extent is all of it.
1105
+ */
1106
+ export declare const textureShape: (texture: Texture, { x, y, w, h }: {
1107
+ x?: number;
1108
+ y?: number;
1109
+ w?: number;
1110
+ h?: number;
1111
+ }) => TextureShape;
1112
+ /** A ring file's `[Exterior]`, over `PlanetRing::Load`'s defaults. The host reads the INI. */
1113
+ interface RingExterior {
1114
+ /** `shape`. Without one the ring draws nothing. */
1115
+ shape?: TextureShape;
1116
+ /** `detail_shape`, on the second UV set. */
1117
+ detail?: TextureShape;
1118
+ /** `detail_tile` as written: the detail's V at the inner edge, then its U on odd segments. */
1119
+ detailTile?: readonly [number, number];
1120
+ /** `color` over 255. Kept from the last ring loaded when absent: black here. */
1121
+ color?: Vector3;
1122
+ /** 1 when absent, and halved without a detail shape. */
1123
+ opacity?: number;
1124
+ /** `num_segments`, 32 when absent. */
1125
+ segments?: number;
1126
+ /** `transition_dist`, 150 when absent: how far past its edges its immersion fades. */
1127
+ transitionDistance?: number;
1128
+ }
1129
+ interface RingOptions {
1130
+ /** `high_detail_textures`: under 0.4 the detail shape is dropped (`0x50ac65`). 1 when absent. */
1131
+ highDetailTextures?: number;
1132
+ }
1133
+ /** A run of segments drawn as one, and the sphere about its vertices that the view tests. */
1134
+ interface RingPiece {
1135
+ first: number;
1136
+ last: number;
1137
+ center: Vector3;
1138
+ radius: number;
1139
+ /** Its strip, in world space. */
1140
+ mesh: VMeshData;
1141
+ reference: VMeshRef;
1142
+ }
1143
+ interface PlanetRing {
1144
+ pieces: RingPiece[];
1145
+ shape?: TextureShape;
1146
+ detail?: TextureShape;
1147
+ zone: RingShape;
1148
+ transitionDistance: number;
1149
+ /** Its `[Clouds]`, which the host makes with `RING_CLOUD_VISIBILITY` and its own `rand()`. */
1150
+ clouds?: readonly Clouds[];
1151
+ }
1152
+ /**
1153
+ * `PlanetRing::Load`'s mesh (`0x50a8d3` on): a vertex at the outer radius and one at the inner for
1154
+ * each segment, then eight pieces of them, each with the box's sphere. And `DrawPiece`'s strip for
1155
+ * each (`0x50b77e`): the shape's left edge on even segments and its right on odd, its top at the
1156
+ * outer radius and its bottom at the inner, in the colour and opacity, all in world space.
1157
+ */
1158
+ export declare const buildRing: (exterior: RingExterior, zone: RingShape, { highDetailTextures }?: RingOptions) => PlanetRing;
1159
+ /**
1160
+ * `PlanetRing::GetImmersion` (`0x50bd70`): 1 within the band, ramping to 0 over `transition_dist`
1161
+ * past its radii and past its height. Retail takes the zone's `size` z as the half height here, so
1162
+ * `zone.height` is that value as written.
1163
+ */
1164
+ export declare const ringImmersion: ({ zone, transitionDistance }: Pick<PlanetRing, "zone" | "transitionDistance">, point: Vector3) => number;
1165
+ /**
1166
+ * `DrawPiece` (`0x50b510`): a piece's strip, unlit, in its vertex colour times the shape, the detail
1167
+ * doubling it, blended over what's there with `LEQUAL` and no depth writes or culling. Nothing
1168
+ * without a shape. Its alpha test and fog are whatever the frame left on: `ring_outside` captures
1169
+ * `GREATER 0` and no fog, outside a nebula. In one, the last object's fog may still be on: uncaptured.
1170
+ */
1171
+ export declare const ringPieceDraw: (ring: PlanetRing, piece: RingPiece) => Draw | undefined;
1172
+ /** A `[Clouds]` section, over `FUN_00550240`'s defaults. The host reads the INI. */
1173
+ interface CloudSettings {
1174
+ /** `puff_count`. */
1175
+ puffCount: number;
1176
+ /** `puff_radius`, 50 when absent. */
1177
+ puffRadius?: number;
1178
+ /** `puff_colora` and `puff_colorb` over 255: each puff's colour lies between. B is A when absent. */
1179
+ colorA?: Vector3;
1180
+ colorB?: Vector3;
1181
+ /** `puff_shape`s, at most eight, one texture between them: the first's is bound for all. */
1182
+ shapes: readonly (TextureShape | undefined)[];
1183
+ /** `puff_weights`, one a shape. Even when absent. */
1184
+ weights?: readonly number[];
1185
+ /** `puff_max_alpha`, 0.75 when absent. */
1186
+ maxAlpha?: number;
1187
+ /** `puff_drift`: how fast a puff wanders. 0 when absent. */
1188
+ drift?: number;
1189
+ /** `max_distance`, 1500 when absent: the puffs' sphere is half of it plus a puff's radius. */
1190
+ maxDistance?: number;
1191
+ /** `near_fade_distance`, 50 and 100 when absent: nearer than the first, nothing. */
1192
+ nearFade?: readonly [number, number];
1193
+ /** `lightning_color` over 255, `lightning_intensity` and its timing: a puff's flash. */
1194
+ lightning?: {
1195
+ color: Vector3;
1196
+ intensity?: number;
1197
+ gap: number;
1198
+ duration: number;
1199
+ };
1200
+ /** `disable_lightning_flash`. */
1201
+ disableFlash?: boolean;
1202
+ }
1203
+ /**
1204
+ * How the owner's visibility drives its puffs: past `full` they're opaque, `budget` of it allows all
1205
+ * of them, and under `spawn` a puff is placed only where the owner holds it. A nebula's are 0.1, 0.3
1206
+ * and 0.3 (`FUN_00550240`); a ring sets its own.
1207
+ */
1208
+ interface CloudVisibility {
1209
+ full: number;
1210
+ budget: number;
1211
+ spawn: number;
1212
+ }
1213
+ export declare const NEBULA_CLOUD_VISIBILITY: Readonly<CloudVisibility>;
1214
+ /** `PlanetRing::Load` (`0x50a735`). */
1215
+ export declare const RING_CLOUD_VISIBILITY: Readonly<CloudVisibility>;
1216
+ interface Puff {
1217
+ position: Vector3;
1218
+ size: number;
1219
+ color: Vector3;
1220
+ shape: number;
1221
+ velocity: Vector3;
1222
+ /** The drift's clock, negative while it waits. -1 with no lightning. */
1223
+ timer: number;
1224
+ /** `+0x3c`, how much of the lightning colour it flashes. */
1225
+ flash: number;
1226
+ active: boolean;
1227
+ hidden: boolean;
1228
+ /** Its depth in view at the last draw: the order puffs are kept in. */
1229
+ depth: number;
1230
+ }
1231
+ interface CloudDrawOptions {
1232
+ /** The camera cut this frame (`+0xbc`): the puffs are placed afresh first. */
1233
+ cut?: boolean;
1234
+ /** How far a point is in what holds the clouds, which a fresh puff must be. */
1235
+ holds?: (point: Vector3) => number;
1236
+ /** How far a point is in an exclusion zone of the owner's: past 0.9, a puff there is hidden. */
1237
+ excluded?: (point: Vector3) => number;
1238
+ }
1239
+ export declare class Clouds {
1240
+ #private;
1241
+ readonly settings: CloudSettings;
1242
+ readonly visibility: CloudVisibility;
1243
+ readonly random: Random;
1244
+ /** Its sphere's radius (`+0x100`). */
1245
+ readonly radius: number;
1246
+ /** How far past the near fade its sphere stands (`+0xbc`). */
1247
+ readonly reach: number;
1248
+ constructor(settings: CloudSettings, { visibility, random, detail }?: {
1249
+ visibility?: CloudVisibility;
1250
+ random?: Random;
1251
+ detail?: number;
1252
+ });
1253
+ /** How many are in its sphere, as of the last update. */
1254
+ get active(): number;
1255
+ get puffs(): readonly Readonly<Puff>[];
1256
+ /** The first shown puff's flash on the next step, for an editor: retail has no such call. */
1257
+ fire(): void;
1258
+ /**
1259
+ * `FUN_00550d40`: the sphere follows the camera; puffs that left it go, and as many as
1260
+ * `visibility` allows come back on its surface, where `holds` says the owner is, and drift. One
1261
+ * back deep in an exclusion zone counts, hidden.
1262
+ */
1263
+ update(camera: Camera, step: number, visibility: number, holds?: (point: Vector3) => number, excluded?: (point: Vector3) => number): void;
1264
+ /**
1265
+ * `TexturePanelDrawClouds`: each puff a square facing the camera, turned to the kept up, faded
1266
+ * near the camera and toward its sphere's edge, all in one list of the first shape's texture.
1267
+ * Unlit, blended, both sides, no depth writes, the alpha test at 5. Nothing with none active.
1268
+ * Its depth function is the main list's, `LEQUAL`, and it draws in view space.
1269
+ */
1270
+ draw(camera: Camera, { cut, holds, excluded }?: CloudDrawOptions): Draw | undefined;
1271
+ }
1272
+ /** An `[Exterior]` section, over `FNebulaEffect`'s constructor and `Load`'s defaults. */
1273
+ interface ExteriorSettings {
1274
+ /** `shape`s, at most eight; a name the shapes don't hold is `undefined`. The first's texture is bound. */
1275
+ shapes: readonly (TextureShape | undefined)[];
1276
+ /** `shape_weights`, one a shape. 1 each when absent. */
1277
+ weights?: readonly number[];
1278
+ /** `bit_radius`. Retail leaves it uninitialised when absent: the host decides. */
1279
+ bitRadius: number;
1280
+ /** `bit_radius_random_variation`, held to [0, 0.9999]: a bit is 1 ± it of the radius. 0 when absent. */
1281
+ radiusVariation?: number;
1282
+ /** `color` over 255. Retail leaves it uninitialised when absent: the host decides. */
1283
+ color: Vector3;
1284
+ /** `plane_slices`, 1 when absent: how many rings of latitude. */
1285
+ planeSlices?: number;
1286
+ /** `min_bits`, 4 when absent: a ring's bits at the poles' end. */
1287
+ minBits?: number;
1288
+ /** `max_bits`, 8 when absent: a ring's bits at the middle. */
1289
+ maxBits?: number;
1290
+ /**
1291
+ * `equator_bias`, 1 when absent: how far the rings spread from the equator toward the poles, 0 to
1292
+ * the whole way, 1 none. The constructor's 0 at `+0xd0` is 1 here.
1293
+ */
1294
+ equatorBias?: number;
1295
+ /** `move_bit_percent`, 0.75 when absent: how many `bit_radius` a bit is pulled in from the surface. */
1296
+ moveBitPercent?: number;
1297
+ /** `opacity`, 0.75 when absent. */
1298
+ opacity?: number;
1299
+ }
1300
+ /** What the game's `Nebula` option scales (`NebulaRebuild`): `DAT_006132e0` to `DAT_006132e8`. */
1301
+ interface ExteriorLimits {
1302
+ /** A bit's size at no detail, of its full. */
1303
+ leastSize: number;
1304
+ /** The bits' count at no detail, of their full. */
1305
+ leastCount: number;
1306
+ /** Their opacity at no detail, of its full. */
1307
+ leastOpacity: number;
1308
+ /** Under this detail, no bits (`0x5d5b58`). */
1309
+ leastDetail: number;
1310
+ }
1311
+ export declare const RETAIL_EXTERIOR_LIMITS: Readonly<ExteriorLimits>;
1312
+ interface Bit {
1313
+ /** From the zone's centre. */
1314
+ position: Vector3;
1315
+ radius: number;
1316
+ shape: TextureShape | undefined;
1317
+ /** Its up, which `NebulaOrientBolts` keeps square to the camera. */
1318
+ axis: Vector3;
1319
+ }
1320
+ /**
1321
+ * Whether the game blends a texture by its alpha (`FUN_00552420`): DXT1 to DXT5 do, and so does any
1322
+ * format whose descriptor has alpha bits (`DAT_00615ec8`). A 24-bit Targa is narrowed to R5G6B5 (M1)
1323
+ * and a 16-bit one is X1R5G5B5 (`GetTgaSourceFormat`), so neither does.
1324
+ */
1325
+ export declare const textureHasAlpha: ({ format }: Texture) => boolean;
1326
+ export declare class NebulaExterior {
1327
+ #private;
1328
+ readonly settings: ExteriorSettings;
1329
+ readonly zone: Zone;
1330
+ constructor(settings: ExteriorSettings, zone: Zone, { random, detail, limits }?: {
1331
+ random?: Random;
1332
+ detail?: number;
1333
+ limits?: ExteriorLimits;
1334
+ });
1335
+ /** The bits, from the zone's centre, in the order they draw. */
1336
+ get bits(): readonly Readonly<Bit>[];
1337
+ /**
1338
+ * `FNebulaEffect::Update`'s part: unless the camera was all the way in last frame, the bits turn
1339
+ * to it once a second, or at once on a cut (`NebulaOrientBolts`). Then the clocks run on.
1340
+ */
1341
+ update(eye: Vector3, step: number, depth: number, cut?: boolean): void;
1342
+ /**
1343
+ * `NebulaDrawBolts` from `DrawBoltsPass`: none once the camera is all the way in, nor when the zone
1344
+ * is out of view. Sorted far to near once 1.15 s have passed. Each bit faces the camera about its
1345
+ * up, its back half skipped and its edge fading, fading too as the camera comes within its radius,
1346
+ * and over the second half of the camera's depth. Unlit, unculled, with no depth test or writes,
1347
+ * `ALPHAREF` 4, fogged in the nebula's colour to past its fog's span (`DrawBoltsPass`). `camera` is
1348
+ * the 250000 far plane its callers set; `projection` is that.
1349
+ */
1350
+ draw({ nebula, depth, start, fogDistance }: NebulaState, camera: Camera, view: Matrix4, aspect: number, projection: Matrix4): Draw | undefined;
1351
+ }
1352
+ /**
1353
+ * What a nebula asks of its zone: `IZone`'s position, `GetRadius`, `GetRadiusAlong`, `GetSize` and
1354
+ * `GetOrientation`.
1355
+ */
1356
+ interface Zone {
1357
+ /** Its `shape`: what the exclusion managers measure by kind (`AsteroidExclusionZone::update_dist`). */
1358
+ type: Shape['type'];
1359
+ position: Vector3;
1360
+ /** The sphere around it. */
1361
+ radius: number;
1362
+ /** From its centre to its surface along a unit direction in world space. */
1363
+ radiusAlong: (direction: Vector3) => number;
1364
+ /** `GetSize`: its `size` as written, a missing value 0 (`Universe` zone loading). */
1365
+ size: Vector3;
1366
+ /** `GetOrientation`'s columns: its own x, y and z in world space. */
1367
+ axes: readonly [Vector3, Vector3, Vector3];
1368
+ /** `GetSurfacePoint` (`ZONE`, vt+0x44): where its surface is, for a point. */
1369
+ surfacePoint: (point: Vector3) => Vector3;
1370
+ /** `InZone` (`0x6339220`) with no margin: whether a point is in it. */
1371
+ holds: (point: Vector3) => boolean;
1372
+ /** `Intersect` (vt+0x4c): whether a segment meets it, or starts in it. */
1373
+ meets: (start: Vector3, end: Vector3) => boolean;
1374
+ }
1375
+ /** A system's `[Nebula]`: its zone, and what its file's `[Fog]` and `[NebulaLight]` say. */
1376
+ interface SpaceNebula {
1377
+ zone: Zone;
1378
+ fog: {
1379
+ /** `near`, 0 when absent. */
1380
+ near?: number;
1381
+ /** `distance`, `limits.nebulaFogDistance` when absent. */
1382
+ distance?: number;
1383
+ /** `color` over 255, each kept below 1 (`ParseFogSection`). */
1384
+ color: Vector3;
1385
+ };
1386
+ /** `[NebulaLight] ambient`, as written, 0 to 255. */
1387
+ ambient?: Vector3;
1388
+ /**
1389
+ * `[NebulaLight]`'s suns seen from in it: their glows drawn again at `sun_burnthrough_intensity`
1390
+ * times its depth, 0.5 when absent, and `sun_burnthrough_scaler` times their size, 1.75 when absent
1391
+ * (`FNebulaEffect`'s constructor; `DrainMeshAndAsteroids`, `0x51fad9`).
1392
+ */
1393
+ burnthrough?: {
1394
+ intensity?: number;
1395
+ scaler?: number;
1396
+ };
1397
+ /** `[Exclusion Zones]`, in file order: holes in its fog. */
1398
+ exclusions?: readonly NebulaExclusion[];
1399
+ /** Its flashes, of its fog and of a light, which retail gives every nebula. None when absent. */
1400
+ lightning?: NebulaLightning;
1401
+ /** `[Exterior] fill_shape`: the two sheets drawn across it from outside and in its edge. */
1402
+ fill?: TextureShape;
1403
+ /** `[Exterior]`'s bits about it, seen from outside and in its edge. */
1404
+ exterior?: NebulaExterior;
1405
+ /** Its `[Clouds]`, in file order, drawn about the camera in it. */
1406
+ clouds?: readonly Clouds[];
1407
+ /** Its zone's `edge_fraction`, which a puff spawning in its edge is held to. 0 when absent. */
1408
+ edgeFraction?: number;
1409
+ }
1410
+ /** An `exclusion` of a nebula's file, and its zone, which retail requires to be a sphere. */
1411
+ interface NebulaExclusion {
1412
+ zone: Zone;
1413
+ /** The zone's `edge_fraction`, 0 to 1: how far in from its surface the hole deepens. 0 when absent. */
1414
+ edgeFraction?: number;
1415
+ /** `fog_far`: where the fog ends deep in it. None when absent (`NebulaExclusionZone`'s -1). */
1416
+ fogFar?: number;
1417
+ /** Its `zone_shell`, drawn about it while the camera is in it. */
1418
+ shell?: ExclusionShell;
1419
+ }
1420
+ /** A `zone_shell` and the keys after it (`FNebulaZoneShell`), shared by every zone naming it. */
1421
+ interface ExclusionShell {
1422
+ /** The model placed under `world`, its levels picked as the game's camera picks them. */
1423
+ place: (world: Matrix4) => SceneModel;
1424
+ /** The zone's transform, unscaled: `IZone::GetTransform`. */
1425
+ transform: Matrix4;
1426
+ /** `shell_scalar`, at least 1. 1 when absent. */
1427
+ scalar?: number;
1428
+ /** `max_alpha`, at most 1. 1 when absent. */
1429
+ maxAlpha?: number;
1430
+ /**
1431
+ * `exclusion_tint` over 255, each held to 1. Retail leaves it uninitialised when absent, so a shell
1432
+ * without one draws in whatever the heap held: the host decides.
1433
+ */
1434
+ tint: Vector3;
1435
+ }
1436
+ /** What retail hardcodes about nebulae, and a mod can patch. */
1437
+ interface NebulaLimits {
1438
+ /** A `[Fog]` with no `distance` (`FNebulaEffect::Load`). */
1439
+ nebulaFogDistance: number;
1440
+ /** Past the fog's end, the far plane in a core (`FogFarClamp`, `0x5d9668`). */
1441
+ nebulaFarMargin: number;
1442
+ /** The least far plane in a core (`+0x2bc`, set by `FNebulaEffect`'s constructor). */
1443
+ nebulaLeastFar: number;
1444
+ /** Past an exclusion zone's diameter, the least far plane in it (`FogFarClamp`, `0x5db0c0`). */
1445
+ exclusionFarMargin: number;
1446
+ }
1447
+ export declare const RETAIL_NEBULA_LIMITS: Readonly<NebulaLimits>;
1448
+ /** D3D's fog: linear, none nearer than `start` and all of `color` from `end`, or exponential. */
1449
+ interface Fog {
1450
+ /** 0 to 1, whole bytes. */
1451
+ color: Vector3;
1452
+ start: number;
1453
+ end: number;
1454
+ /** `D3DFOGMODE`. A nebula's is linear; a room's `fogmode` can be any. Linear when absent. */
1455
+ mode?: 'linear' | 'exp' | 'exp2';
1456
+ /** What `exp` and `exp2` read: `e^-(density × W)`, and `e^-(density × W)²`. */
1457
+ density?: number;
1458
+ }
1459
+ /** A zone as written: what `shape` can't hold. A sphere's `rotate` turns its axes alone. */
1460
+ interface WrittenZone {
1461
+ /** Its `size`, a missing value 0. From `shape` when absent, as the game's files write it. */
1462
+ size?: Vector3;
1463
+ /** Its `rotate`. From `shape` when absent, none for a sphere. */
1464
+ orientation?: Quat;
1465
+ }
1466
+ /**
1467
+ * `ZONE::GetRadius`, `GetRadiusAlong`, `GetSize`, `GetOrientation` and `GetSurfacePoint` for each
1468
+ * shape the game knows.
1469
+ */
1470
+ export declare const zoneOf: (shape: Shape, written?: WrittenZone) => Zone;
1471
+ /** `FNebulaEffect::ZoneExteriorFactor`: 0 outside, rising to 1 a fog distance in from its surface. */
1472
+ export declare const exteriorFactor: (zone: Zone, distance: number, point: Vector3) => number;
1473
+ /**
1474
+ * `ZONE::GetImmersion` (`0x633ac30`) for a zone `radiusAlong` describes, as a sphere is: 0 outside,
1475
+ * rising to 1 an edge fraction of the way in.
1476
+ */
1477
+ export declare const immersion: (zone: Zone, edgeFraction: number, point: Vector3) => number;
1478
+ /** The exclusion zone the camera is in, and its immersion there (`+0x24`, `+0x28`). */
1479
+ interface InExclusion {
1480
+ exclusion: NebulaExclusion;
1481
+ immersion: number;
1482
+ }
1483
+ /** What `FNebulaEffect::Update` keeps from where the camera is, each frame. */
1484
+ interface NebulaState {
1485
+ nebula: SpaceNebula;
1486
+ fogDistance: number;
1487
+ /** `+0x294`: how deep the camera is, over the fog's whole span, up to 1. */
1488
+ depth: number;
1489
+ /** `+0x2ac`, the fog's start. */
1490
+ start: number;
1491
+ /** `+0x2b0`, the fog's end. */
1492
+ end: number;
1493
+ /** How far the camera is outside its surface, negative inside (`Update`'s `D`). */
1494
+ surface: number;
1495
+ /** Only while the camera is in the nebula. */
1496
+ inExclusion?: InExclusion;
1497
+ }
1498
+ /** `FNebulaEffect::Update`'s depth and fog range. */
1499
+ export declare const nebulaState: (nebula: SpaceNebula, eye: Vector3, limits?: NebulaLimits) => NebulaState;
1500
+ /** `FNebulaEffect::FogFactor`: where the fog ends for what stands at `point`. */
1501
+ export declare const fogEnd: (state: NebulaState, point: Vector3) => number;
1502
+ /** `FNebulaEffect::FogFarClamp`: the pass's far plane in its core, before the system's cuts it. */
1503
+ export declare const fogFar: (state: NebulaState, limits?: NebulaLimits) => number;
1504
+ /** The nebula a point is in, and how far, from 0 to 1: `Gameobj`'s `+0x38` and `+0x3c`. */
1505
+ interface Within {
1506
+ state: NebulaState;
1507
+ factor: number;
1508
+ }
1509
+ /** `StarSysFindNebula` (`0x5553b0`): the `hint` if it holds `point`, or the first that does. */
1510
+ export declare const findNebula: (nebulae: readonly NebulaState[], point: Vector3, hint?: SpaceNebula) => Within | undefined;
1511
+ /** The nebula the camera answers to (`DAT_00675244`), and how far it's in. */
1512
+ interface SpaceField {
1513
+ state: NebulaState;
1514
+ /** `DAT_0067524c`: 0 outside it, 1 in its edge, 2 in its core. */
1515
+ enclosure: 0 | 1 | 2;
1516
+ }
1517
+ /** A nebula `SelectFieldControllers` leaves to the far list (`ZItemNebula`), keyed by its score. */
1518
+ interface DeferredNebula {
1519
+ state: NebulaState;
1520
+ key: number;
1521
+ }
1522
+ /** The camera's field, and every other nebula, drawn from the far list. */
1523
+ interface NebulaList {
1524
+ field?: SpaceField;
1525
+ deferred: DeferredNebula[];
1526
+ }
1527
+ /**
1528
+ * `SelectFieldControllers` (`0x51b730`): the first nebula the camera is in, taking a core over an
1529
+ * edge, or else the one whose surface is nearest within `range`. Each other nebula is deferred with
1530
+ * the squared distance to its surface, however far, and a field it replaces with the best score.
1531
+ * In a core, none is (`ClearBuckets`, `0x51b9c7`).
1532
+ */
1533
+ export declare const listNebulae: (nebulae: readonly NebulaState[], eye: Vector3, range: number) => NebulaList;
1534
+ /** `listNebulae`'s field alone. */
1535
+ export declare const pickField: (nebulae: readonly NebulaState[], eye: Vector3, range: number) => SpaceField | undefined;
1536
+ type Rgb = readonly [number, number, number];
1537
+ /** How a host's frames of the sprites look: none of it is the game's. */
1538
+ interface SpriteFrameStyle {
1539
+ /** An appearance's outline; none leaves its sprites unframed. */
1540
+ outline: (node: EffectNode) => Rgb | undefined;
1541
+ axisX: Rgb;
1542
+ axisY: Rgb;
1543
+ normal: Rgb;
1544
+ alpha: number;
1545
+ }
1546
+ interface HookContext {
1547
+ gl: WebGL2RenderingContext;
1548
+ frame: Frame;
1549
+ /** With D3D's pixel shift, so a host's lines land on the pixels the ship did. */
1550
+ viewProjection: Matrix4;
1551
+ /**
1552
+ * Frames each sprite of these effects as this frame's camera has them, whether or not the frame
1553
+ * draws them: an outline, two axes and a normal, under the depth state the hook has set.
1554
+ */
1555
+ outlineEffects: (effects: readonly SceneEffect[], style: SpriteFrameStyle) => void;
1556
+ }
1557
+ /**
1558
+ * The host's own drawing. It finds the depth buffer the game path left, with no vertex array or
1559
+ * sampler object bound, and may change any GL state: the renderer sets its own again after each hook,
1560
+ * so nothing leaks into the game path.
1561
+ */
1562
+ type FrameHooks = Partial<Record<HookName, (context: HookContext) => void>>;
1563
+ /** A rectangle of the target in pixels, from its bottom left as GL counts. */
1564
+ interface ViewportRect {
1565
+ x: number;
1566
+ y: number;
1567
+ width: number;
1568
+ height: number;
1569
+ }
1570
+ interface RenderOptions {
1571
+ /**
1572
+ * Where the picture goes, as D3D's viewport: drawn and cleared there alone, the rest of the target
1573
+ * left as it was. The letterbox's picture, `(0, bar, w, h − 2·bar)`, with the scene's `aspect` its
1574
+ * own (THN-HOST.md, the letterbox). The whole target when absent.
1575
+ */
1576
+ viewport?: ViewportRect;
1577
+ }
1578
+ interface Renderer {
1579
+ /** Draws, and answers false if there is no context to draw with for now. */
1580
+ render: (frame: Frame, hooks?: FrameHooks, options?: RenderOptions) => boolean;
1581
+ /** Frees what depends on an edited mesh or texture. The next frame uses it afresh. */
1582
+ invalidate: (asset: VMeshData | Texture | CubeTexture) => void;
1583
+ /** Frees everything, and the renderer with it. */
1584
+ dispose: () => void;
1585
+ }
1586
+ /**
1587
+ * Takes a context, and keeps only GPU handles keyed on what the host's frames hold. So a lost context
1588
+ * needs nothing from the host but the next frame, whose data rebuilds them.
1589
+ */
1590
+ export declare const createRenderer: (gl: WebGL2RenderingContext) => Renderer;
1591
+ interface Extent {
1592
+ /** In the model's own space. */
1593
+ center: Vector3;
1594
+ radius: number;
1595
+ }
1596
+ /** The extent of a model as it loads, with its root at the origin. */
1597
+ export declare const instanceExtent: (model: RigidModel) => Extent;
1598
+ /** An `[EffectType]` row, each key over its built-in one. */
1599
+ interface EffectType {
1600
+ priority: number;
1601
+ /** `generic_priority`, for an effect drawn through its generic appearance. */
1602
+ genericPriority: number;
1603
+ /** `lod_type`: which `[EffectLOD]` row, 1 to 6. 0 is none. */
1604
+ lodType: number;
1605
+ radius: number;
1606
+ /** `visibility = EXIST_OFFSCREEN`: unseen, it runs on instead of ending. */
1607
+ existOffscreen: boolean;
1608
+ /** `update = UPDATE_OFFSCREEN`: unseen, its effect still steps (`0x4f8ae9`). */
1609
+ updateOffscreen: boolean;
1610
+ /** `pbubble`: past near, how far it shows narrows from far by `generic_effect_perf`. */
1611
+ pbubble: {
1612
+ near: number;
1613
+ far: number;
1614
+ };
1615
+ /**
1616
+ * `run_time`: seconds it runs before it ends, counted down as it's walked (`0x4f8a57`). Under 0,
1617
+ * until its effect ends; killed, at most 1 more (`0x4f8680`).
1618
+ */
1619
+ runTime: number;
1620
+ }
1621
+ /** Each row's nickname, which an `[Effect]`'s `effect_type` and an `[EffectType]` name. */
1622
+ export declare const EFFECT_TYPE_NAMES: readonly string[];
1623
+ /**
1624
+ * An `effect_type` or `[EffectType]` nickname as `SpaceEffect.type` takes it: case and all, as
1625
+ * `EffectTypeIndexFromName` (`0x4fd500`) compares. `undefined` for a name not in the table.
1626
+ */
1627
+ export declare const effectTypeIndex: (name: string) => number | undefined;
1628
+ /**
1629
+ * An `[EffectType]`'s `lod_type`, `EFT_LOD_TRAIL` to `EFT_LOD_SMALL_DISTANT`, as 1 to 6: any case
1630
+ * (`LoadEffectTypeSection`). `undefined` for any other, which leaves the row's as it was.
1631
+ */
1632
+ export declare const effectLodType: (name: string) => number | undefined;
1633
+ /**
1634
+ * An `[EffectLOD]`'s `type`, `lod_type_trail` to `lod_type_small_distant`, as the row of
1635
+ * `effectLods` it fills, 1 to 6: any case (`LoadEffectLodSection`, `0x42dae0`).
1636
+ */
1637
+ export declare const effectLodRow: (name: string) => number | undefined;
1638
+ /** `EFT_NONE` to `EFT_FLASHLIGHT`, the table `[EffectType]` overlays (`.data` `0x612a68`). */
1639
+ export declare const RETAIL_EFFECT_TYPES: readonly Readonly<EffectType>[];
1640
+ /** An `[EffectLOD]` row: how large on screen, in pixels, it fades in over, and the least it shows at. */
1641
+ interface EffectLodSizes {
1642
+ maxLodScreenSize: number;
1643
+ minLodScreenSize: number;
1644
+ minScreenSize: number;
1645
+ }
1646
+ /** What the retail effect is drawn under, besides the camera. */
1647
+ interface EffectViewOptions {
1648
+ /** The `generic_effect_perf` option, 0 to 1. 1 when absent: every priority shows, to `pbubble`'s far. */
1649
+ effectDetail?: number;
1650
+ /** Over `RETAIL_EFFECT_TYPES`, by index. */
1651
+ effectTypes?: readonly (Partial<EffectType> | undefined)[];
1652
+ /** `[EffectLOD]` rows by `lod_type`, 1 to 6. None when absent, as the static initialiser's -1s. */
1653
+ effectLods?: readonly (EffectLodSizes | undefined)[];
1654
+ /** The viewport's width in pixels, which the LOD's screen size reads. No LOD when absent. */
1655
+ viewportWidth?: number;
1656
+ }
1657
+ /** A placed effect, as the view test asks of it. */
1658
+ interface PlacedEffect {
1659
+ type: number;
1660
+ /** Where it is: its world transform's translation. */
1661
+ position: Vector3;
1662
+ /** Drawn through its `[Effect]`'s generic appearance, which has its own priority. */
1663
+ generic?: boolean;
1664
+ /** The ship or solar it's on. */
1665
+ watched?: {
1666
+ position: Vector3;
1667
+ /** Its cloak, 0 to 1. At 1 the effect hides. */
1668
+ cloak?: number;
1669
+ /** The nebula it was last in, tried first. */
1670
+ nebula?: SpaceNebula;
1671
+ };
1672
+ }
1673
+ /**
1674
+ * `draw`: seen. `run`: unseen, and `EXIST_OFFSCREEN`. `die`: unseen, and frozen in place to end.
1675
+ * `hold`: not walked this frame, so neither drawn nor stepped, as in a gate tunnel.
1676
+ */
1677
+ interface EffectView {
1678
+ fate: 'draw' | 'run' | 'die' | 'hold';
1679
+ /** The `ALCHEMY_LOD` blend, 0 to 1, for its next step's `lod`. */
1680
+ lod: number;
1681
+ }
1682
+ /** The camera's place in the frame: its view, its nebula, and the system's nebulae. */
1683
+ interface EffectViewFrame {
1684
+ camera: Camera;
1685
+ aspect: number;
1686
+ view: Matrix4;
1687
+ field?: SpaceField;
1688
+ nebulae: readonly NebulaState[];
1689
+ }
1690
+ /**
1691
+ * `UpdateVisibility` and what `EffectTypeBucketWalk` does with it. The game keeps last frame's answer
1692
+ * where it doesn't test, but clears it once an effect draws, so that answer is taken as unseen.
1693
+ */
1694
+ export declare const viewEffect: ({ type, position, generic, watched }: PlacedEffect, { camera, aspect, view, field, nebulae }: EffectViewFrame, { effectDetail, effectTypes, effectLods, viewportWidth }?: EffectViewOptions) => EffectView;
1695
+ /** A zone with a `spacedust`, which sets its `0x200000` property. */
1696
+ interface DustZone {
1697
+ zone: Zone;
1698
+ /** Its `edge_fraction`, 0 when absent. */
1699
+ edgeFraction?: number;
1700
+ /** `spacedust`: an `[Effect]` nickname. Empty as written empty. */
1701
+ dust: string;
1702
+ /** `spacedust_maxparticles`, 0 when absent, which takes 50. */
1703
+ maxParticles?: number;
1704
+ }
1705
+ /** A started dust effect: what a `SpaceEffect` is but where it is. */
1706
+ type DustEffect = Omit<SpaceEffect, 'position' | 'watched'>;
1707
+ /** The options `SpaceDustUpdate` reads. */
1708
+ interface DustOptions {
1709
+ /** `space_dust_detail` from `PerfOptions.ini`, 1 when absent (`DAT_00614080`). */
1710
+ detail?: number;
1711
+ /** Game option `0x19`, on when absent. Off, each frame drops every dust effect first. */
1712
+ background?: boolean;
1713
+ /** Each effect type's `run_time`, by index: how long a killed one winds down. -1 when absent. */
1714
+ runTimes?: readonly (number | undefined)[];
1715
+ }
1716
+ /** `SpaceDustUpdate`'s numbers, which a mod can patch. */
1717
+ interface DustLimits {
1718
+ /** `DAT_00614084`: the dust's frame, along the camera's Z. */
1719
+ offset: number;
1720
+ /** `DAT_005c8930`: zone dust runs only above this detail. */
1721
+ detailThreshold: number;
1722
+ /** `DAT_005d3e94`: a zone dust's intensity for each of its particles. */
1723
+ intensityPerParticle: number;
1724
+ /** `0x54c013`: a zone's particles when it writes none. */
1725
+ defaultZoneParticles: number;
1726
+ /** `DAT_005c75f0`: a fading dust at or under this intensity is killed. */
1727
+ fadeCutoff: number;
1728
+ /** `FUN_0054bde0`: the system dust's particles at full and at no detail. */
1729
+ systemParticles: {
1730
+ full: number;
1731
+ none: number;
1732
+ };
1733
+ /** `DAT_005e08f8`: the system dust's intensity for each of those. */
1734
+ systemIntensityPerParticle: number;
1735
+ /** `SpaceEffectsDrawFrame` (`0x4f7600`): the most one walk steps an effect by. */
1736
+ maxStep: number;
1737
+ }
1738
+ export declare const RETAIL_DUST_LIMITS: Readonly<DustLimits>;
1739
+ /** A dust effect in the frame's walk, and when it joined it. */
1740
+ interface DustEffectAt {
1741
+ effect: SpaceEffect;
1742
+ serial: number;
1743
+ }
1744
+ /** Where the dust is: its world, and its point. */
1745
+ interface DustFrame {
1746
+ world: Matrix4;
1747
+ position: Vector3;
1748
+ }
1749
+ /**
1750
+ * `GetSmallestZoneWithPropertiesAt(point, 0x200000)` (`0x633b620`): the zone with the least
1751
+ * `GetRadius` that holds the point, the first of equals. Retail asks its zone tree, whose order
1752
+ * isn't read; this takes the order given, so only equal radii can differ.
1753
+ */
1754
+ export declare const smallestDustZone: (zones: readonly DustZone[], point: Vector3) => DustZone | undefined;
1755
+ export declare class SpaceDust {
1756
+ #private;
1757
+ /** `[Dust] spacedust`, empty when the system has none. */
1758
+ readonly system: string;
1759
+ /** Starts the `[Effect]` of a nickname: none when there's no such archetype. */
1760
+ readonly start: (name: string) => DustEffect | undefined;
1761
+ readonly limits: Readonly<DustLimits>;
1762
+ /**
1763
+ * `FUN_0054bde0`, from `StarSysLoadIni`'s `[Dust]`: a system with no such section passes none, and
1764
+ * its particles stay 0.
1765
+ */
1766
+ constructor(system: string | undefined, start: (name: string) => DustEffect | undefined, limits?: Partial<DustLimits>);
1767
+ /** Every effect it holds, released at once: for a host done with this dust. */
1768
+ release(): void;
1769
+ /**
1770
+ * The dust's frame: the camera's, `offset` along its Z (`FUN_0054c320`). That axis is the
1771
+ * orientation's third column, `(x.z, y.z, z.z)`.
1772
+ */
1773
+ frame({ position, orientation }: Camera): DustFrame;
1774
+ /**
1775
+ * `SpaceDustUpdate` (`FUN_0054beb0`) from `StarSysUpdate`, then each linked effect stepped as the
1776
+ * walk does, by at most `maxStep`. Zone dust runs only above the detail threshold: the zone is the
1777
+ * smallest of `zones` that holds the dust's point, its intensity its particles over 500 times
1778
+ * its immersion there. A new name hands the old effect to the fading slot, which runs at the
1779
+ * rest of that immersion until it's under `fadeCutoff`. The system's dust starts on the first
1780
+ * update and is placed from the next, at 80 particles over 500, or toward 30 as detail falls.
1781
+ */
1782
+ update(camera: Camera, step: number, zones: readonly DustZone[], { detail, background, runTimes }?: DustOptions): void;
1783
+ /** The effects for the frame's walk, newest linked first, each where it was last placed. */
1784
+ effects(): DustEffectAt[];
1785
+ /**
1786
+ * What the frame's view test made of `effects()`, in its order. Unseen and not
1787
+ * `EXIST_OFFSCREEN`, an effect is frozen and dead, and isn't placed again (`0x4f789f`).
1788
+ */
1789
+ viewed(views: readonly (EffectView | undefined)[]): void;
1790
+ }
1791
+ /** A `[Band]` as written, over `FUN_0054c580`'s defaults. The host reads the INI. */
1792
+ interface BandSettings {
1793
+ /** `shape`: only its texture is used. Without one the band draws nothing. */
1794
+ shape?: TextureShape;
1795
+ /** `texture_aspect`, 1 when absent; read times 1.15. */
1796
+ textureAspect?: number;
1797
+ /** `height`, 1000 when absent. */
1798
+ height?: number;
1799
+ /** `offset_dist` takes this from the zone's size; `offset_percent` scales the size by it. */
1800
+ offset?: {
1801
+ distance: number;
1802
+ } | {
1803
+ percent: number;
1804
+ };
1805
+ /** `color_shift`, 1 when absent: the field's colours are multiplied by it. */
1806
+ colorShift?: Vector3;
1807
+ /** `fade`'s four distances, in heights: 1500, 2500, 100000 and 130000 when absent. */
1808
+ fade?: readonly [number, number, number, number];
1809
+ /** `vert_increase`, at least 1: points to a ring's segment, and how often U repeats. */
1810
+ vertIncrease?: number;
1811
+ /** `render_parts`, 1 when absent: the pieces the ring is cut into, at most half its points. */
1812
+ renderParts?: number;
1813
+ /** `cull_mode`: 1 hides the faces turned away, 2 those turned toward. 0 when absent. */
1814
+ cullMode?: number;
1815
+ /** `ambient_intensity`, 1 when absent. */
1816
+ ambientIntensity?: number;
1817
+ }
1818
+ /** What a band takes of its field: the zone's frame and `size` as written, and the field's colours. */
1819
+ interface BandField {
1820
+ position: Vector3;
1821
+ orientation: Quat;
1822
+ /** `size` as written; a sphere's has its radius in X alone. */
1823
+ size: Vector3;
1824
+ /** `diffuse_color` over 255. */
1825
+ diffuse: Vector3;
1826
+ /** `ambient_color` over 255. */
1827
+ ambient: Vector3;
1828
+ /** `ambient_increase`, 0 to 255. */
1829
+ ambientIncrease?: Vector3;
1830
+ }
1831
+ /** One of the pieces the frame lists: a run of the ring's points and the sphere the view tests. */
1832
+ interface BandPiece {
1833
+ first: number;
1834
+ last: number;
1835
+ center: Vector3;
1836
+ radius: number;
1837
+ }
1838
+ /** What the bake lights a band with: the system's `[Ambient]` as written, and the lights it found. */
1839
+ interface BandLighting {
1840
+ ambient: Vector3;
1841
+ /** How many lights `CollectNearbyLights` found. With none, nothing is baked. */
1842
+ found: number;
1843
+ lights: readonly SceneLight[];
1844
+ }
1845
+ export declare class AsteroidBand {
1846
+ readonly settings: BandSettings;
1847
+ /** Its zone's centre, where it is lit. */
1848
+ readonly position: Vector3;
1849
+ /**
1850
+ * `+0x54`: what its alpha is held under. The field's own band keeps 1; an `[ExclusionBand]` takes
1851
+ * `nearFieldFraction` each frame.
1852
+ */
1853
+ private maxAlpha;
1854
+ /** Each point of the ring, in world space, with its outward normal. */
1855
+ readonly points: readonly {
1856
+ position: Vector3;
1857
+ normal: Vector3;
1858
+ }[];
1859
+ readonly halfHeight: number;
1860
+ readonly pieces: readonly BandPiece[];
1861
+ /** The squares of the four fade distances, in world units. */
1862
+ private readonly fades;
1863
+ private readonly diffuse;
1864
+ private readonly ambient;
1865
+ private readonly increase;
1866
+ /** `+0x34` and `+0x38` of each point: its fade, and whether the camera is on its normal's side. */
1867
+ private readonly fade;
1868
+ private readonly facing;
1869
+ /** `+0x1c` and `+0x28`: the colour seen from the normal's side, and from behind. */
1870
+ private front;
1871
+ private back;
1872
+ private baked;
1873
+ constructor(settings: BandSettings, field: BandField);
1874
+ /**
1875
+ * `BandUpdate`: each point's fade from the camera, in by `fade`'s first two distances and out by
1876
+ * its last two, and edge-on below a |cos| of 0.5. Where it's faded out past the far end, its
1877
+ * facing is last frame's. `maxAlpha` is kept for the draw; under 0.001 nothing else is updated.
1878
+ */
1879
+ update(eye: Vector3, maxAlpha?: number): void;
1880
+ /** Whether `bake` has run with lights to bake. */
1881
+ get isBaked(): boolean;
1882
+ /**
1883
+ * `BandBakeLighting`, once: both sides of each point, the ambient plus each light's diffuse by how
1884
+ * squarely it falls, softened round the back, and held under 1. The ambient is the system's
1885
+ * raised by `ambient_increase`, in the band's ambient tint, times `ambient_intensity`.
1886
+ */
1887
+ bake({ ambient, found, lights }: BandLighting): void;
1888
+ /**
1889
+ * `BandDrawPiece`: a quad between each two points of the piece, in two passes, those with either end
1890
+ * turned away and then those turned toward; each end in its side's colour and its fade, under
1891
+ * the alpha `update` was given, U stepping by `vert_increase`. A quad with both ends transparent is left out, and a
1892
+ * piece with none draws nothing. Unlit, blended, both sides, with no depth test or writes.
1893
+ */
1894
+ pieceDraw(index: number): Draw | undefined;
1895
+ }
1896
+ /** An `[AsteroidBillboards]` section, over the field's constructor defaults. The host reads it. */
1897
+ interface BillboardSettings {
1898
+ /** `shape`. None draws nothing. */
1899
+ shape?: TextureShape;
1900
+ /** `count`, truncated, 100 when absent. */
1901
+ count?: number;
1902
+ /**
1903
+ * `size`, ordered least first: a rock is between the two. When absent the constructor's 500 then
1904
+ * 100, unordered, so rocks run from 500 down to 100.
1905
+ */
1906
+ size?: readonly [number, number];
1907
+ /** `start_dist`, 1500 when absent: how far ahead the rocks begin. */
1908
+ startDistance?: number;
1909
+ /** `fade_dist_percent`, held to [0, 1], 0.3 when absent: how much of the sphere fades. */
1910
+ fadePercent?: number;
1911
+ /** `ambient_intensity`, 1 when absent. */
1912
+ ambientIntensity?: number;
1913
+ /** `color_shift`, 1 each when absent. */
1914
+ colorShift?: Vector3;
1915
+ }
1916
+ /** What a field gives its billboards. */
1917
+ interface BillboardField {
1918
+ zone: Zone;
1919
+ edgeFraction?: number;
1920
+ /** `fill_dist`, truncated, and the `Asteroids` option's reach, `(1 - d) · 0.65 + d`. */
1921
+ fillDistance: number;
1922
+ detailScale?: number;
1923
+ cubeSize: number;
1924
+ /** The field's colours: `diffuse_color` and `ambient_color` over 255. */
1925
+ diffuse: Vector3;
1926
+ ambient: Vector3;
1927
+ /** Its exclusion zones; with none, retail has no manager, and every rock stands. */
1928
+ exclusions?: readonly CubeExclusion[];
1929
+ }
1930
+ /** What the frame lights the billboards with at the camera, after the field's ambient pass. */
1931
+ interface BillboardLighting {
1932
+ /** `D3DRS_AMBIENT` as the pass left it: whole bytes, 0 to 1. */
1933
+ ambient: Vector3;
1934
+ /** Up to four directional lights there, each the way it shines (`directionalLightsAt`). */
1935
+ lights: readonly SceneLight[];
1936
+ /** The `LIGHTING` option: off, the rocks are the tinted diffuse. On when absent. */
1937
+ lit?: boolean;
1938
+ /** What the ambient pass left on: the field's nebula's fog. */
1939
+ fog?: Fog;
1940
+ }
1941
+ interface Rock$1 {
1942
+ position: Vector3;
1943
+ half: number;
1944
+ alpha: number;
1945
+ visible: boolean;
1946
+ flipU: boolean;
1947
+ flipV: boolean;
1948
+ swap: boolean;
1949
+ negate: boolean;
1950
+ /** Its index over the count: rocks past a zone's density are left out. */
1951
+ phase: number;
1952
+ }
1953
+ export declare class AsteroidBillboards {
1954
+ #private;
1955
+ readonly settings: BillboardSettings;
1956
+ readonly field: BillboardField;
1957
+ readonly random: Random;
1958
+ /** `Float00`: the rocks' sphere. */
1959
+ readonly radius: number;
1960
+ /** `Float04`: how far ahead its centre is. */
1961
+ readonly ahead: number;
1962
+ /** `Float2C`: how much of it fades, short of the centre's depth. */
1963
+ readonly fade: number;
1964
+ /** `Float40` and `Float44`: nearer than the first a rock fades, gone by the second. */
1965
+ readonly nearFade: readonly [number, number];
1966
+ /** `DensityScale`: with no zone's density, rocks past it are left out. */
1967
+ readonly density: number;
1968
+ constructor(settings: BillboardSettings, field: BillboardField, { random, detail }?: {
1969
+ random?: Random;
1970
+ detail?: number;
1971
+ });
1972
+ get rocks(): readonly Readonly<Rock$1>[];
1973
+ /**
1974
+ * `BillboardNearCamera` (`FUN_0054e040`): whether the camera is within the sphere's reach, a
1975
+ * quarter past it, of the field's surface.
1976
+ */
1977
+ near(eye: Vector3): boolean;
1978
+ /**
1979
+ * `BillboardDraw`: the rocks placed afresh in the sphere on a cut, or those that left it on its
1980
+ * surface; faded toward its edge, more narrowly past its centre, and near the cubes' reach.
1981
+ * Opaque rocks draw first, `ALPHAREF` 128 and no blending, then the faded ones blended with no
1982
+ * depth writes, `ALPHAREF` 5. Each a triangle facing the camera, its base on the right or the up,
1983
+ * lit at its corners from the camera's light. Nothing when the camera is far from the field.
1984
+ */
1985
+ draw(camera: Camera, lighting: BillboardLighting, cut?: boolean): Draw[];
1986
+ /** The alpha a rock at `point` draws with, seen from `camera`. */
1987
+ alphaAt(camera: Camera, point: Vector3): number;
1988
+ /**
1989
+ * The rocks' colours seen from `camera`: a corner's toward the right, the left, the up and the
1990
+ * down, and the centre's.
1991
+ */
1992
+ coloursAt(camera: Camera, lighting: BillboardLighting): {
1993
+ corners: Vector3[];
1994
+ centre: Vector3;
1995
+ };
1996
+ }
1997
+ /** A `[DynamicAsteroids]` section, over the manager's constructor (`FUN_005256a0`). The host reads it. */
1998
+ interface DynamicSettings {
1999
+ /** `count`, truncated, 10 when absent. */
2000
+ count?: number;
2001
+ /**
2002
+ * `placement_radius`, 150 when absent. Its spawn point is 0.7 of it ahead, or 75 when absent.
2003
+ * Every retail file also has `placement_offset`, which nothing reads.
2004
+ */
2005
+ placementRadius?: number;
2006
+ /** `max_velocity`, 50 when absent: the most a rock moves on each axis, a second. */
2007
+ maxVelocity?: number;
2008
+ /** `max_angular_velocity`, 5 when absent: the most it turns about each axis, radians a second. */
2009
+ maxAngularVelocity?: number;
2010
+ /** `color_shift`, 1 each when absent. */
2011
+ colorShift?: Vector3;
2012
+ }
2013
+ /** What a field gives its dynamic asteroids. */
2014
+ interface DynamicField {
2015
+ /** The section's `zone`, or the field's: the spawn point must be within it. None, anywhere. */
2016
+ zone?: Zone;
2017
+ /** That zone's `edge_fraction`. 0 when absent. */
2018
+ edgeFraction?: number;
2019
+ /** The field's `diffuse_color` and `ambient_color` over 255. */
2020
+ diffuse: Vector3;
2021
+ ambient: Vector3;
2022
+ /** Its exclusion zones; those with `excludeDynamicAsteroids` keep the spawn point out. */
2023
+ exclusions?: readonly CubeExclusion[];
2024
+ }
2025
+ /** A started `particle_effect`: what a `SpaceEffect` is but where it is, which the rock gives it. */
2026
+ type DynamicEffect = Omit<SpaceEffect, 'position' | 'watched'>;
2027
+ /** One rock to draw: its frame and its fade. */
2028
+ interface DynamicDraw {
2029
+ world: Matrix4;
2030
+ alpha: number;
2031
+ }
2032
+ interface Rock {
2033
+ position: Vector3;
2034
+ orientation: Matrix3;
2035
+ velocity: Vector3;
2036
+ /** Its angular velocity about its own axes. */
2037
+ spin: Vector3;
2038
+ /** `Alpha` (`+0xb0`): the fade last worked out, never written when it's made. */
2039
+ alpha: number;
2040
+ /** `bPlaced` (`+0xad`): within the sphere since it was last placed. */
2041
+ placed: boolean;
2042
+ /** Its companion's `particle_effect`, started as it was placed. */
2043
+ effect?: DynamicEffect;
2044
+ /** What the effect's `SetIntensity` was last given: its `sparam`. */
2045
+ intensity: number;
2046
+ /** The `ALCHEMY_LOD` blend its effect's last view gave. */
2047
+ lod: number;
2048
+ /** When its effect started, over every field's: the game walks a type's newest first. */
2049
+ serial: number;
2050
+ }
2051
+ /** A started rock effect, and when it started. */
2052
+ interface DynamicEffectAt {
2053
+ effect: SpaceEffect;
2054
+ serial: number;
2055
+ }
2056
+ export declare class DynamicAsteroids {
2057
+ #private;
2058
+ readonly settings: DynamicSettings;
2059
+ readonly field: DynamicField;
2060
+ readonly random: Random;
2061
+ /** `SlotCount`: `count` scaled by the `Asteroids` option, `(1 - d) · 0.5 + d`, rounded. */
2062
+ readonly count: number;
2063
+ /** `PlacementRadius` and `PlacementRadiusInner`. */
2064
+ readonly radius: number;
2065
+ readonly ahead: number;
2066
+ /** `TintedDiffuse` and `TintedAmbient`: `color_shift` times the field's, held under 1. */
2067
+ readonly diffuse: Vector3;
2068
+ readonly ambient: Vector3;
2069
+ /** Starts the archetype's `particle_effect` afresh: none when it has none. */
2070
+ readonly effect: (() => DynamicEffect) | undefined;
2071
+ constructor(settings: DynamicSettings, field: DynamicField, { random, detail, effect }?: {
2072
+ random?: Random;
2073
+ detail?: number;
2074
+ effect?: () => DynamicEffect;
2075
+ });
2076
+ get rocks(): readonly (Readonly<Rock> | undefined)[];
2077
+ /** `CliDynamicAsteroidUpdate`'s spawn point: `ahead` along the view, `(m[2], m[5], m[8])`. */
2078
+ centre({ position, orientation }: Camera): Vector3;
2079
+ /** The fade of a rock at a point in the world, under this camera. */
2080
+ alphaAt(camera: Camera, point: Vector3): number | undefined;
2081
+ /**
2082
+ * `CliDynamicAsteroidUpdate`, after the physics has moved the rocks by `step` seconds; the order
2083
+ * of the two is a guess. A rock inside 1.05 of the sphere is faded over its outer 0.35, by the
2084
+ * radius squared. Any other is placed afresh on the sphere, or released if the spawn point is
2085
+ * outside the zone, in a zone that excludes them, or `blocked`: the player's ship in a lane.
2086
+ * Their effects step by `effectStep`, the effect walk's own, `step` when absent.
2087
+ */
2088
+ update(camera: Camera, step?: number, { blocked, effectStep }?: {
2089
+ blocked?: boolean;
2090
+ effectStep?: number;
2091
+ }): void;
2092
+ /**
2093
+ * The rocks' effects for the frame's walk, in slot order, each where its rock is, with when it
2094
+ * started: the frame walks every field's newest first, as the game's list is (`FUN_004fc920`).
2095
+ */
2096
+ effects(): DynamicEffectAt[];
2097
+ /** What the frame's view test made of `effects()`, in its order: each one's `lod` next step. */
2098
+ viewed(views: readonly (EffectView | undefined)[]): void;
2099
+ /**
2100
+ * `CliDynamicAsteroidDraw`'s rocks, in slot order: each above 0.005, at the alpha it was last
2101
+ * faded to, which a rock placed this frame kept from before. Each drawn hands its alpha, held to
2102
+ * [0, 1], to its effect as its intensity (`0x5265c4`, `FParticleEffect::SetIntensity`).
2103
+ */
2104
+ draws(): DynamicDraw[];
2105
+ }
2106
+ /** Each axis's four angles in radians, `[Cube]`'s `xaxis_rotation` and the rest. */
2107
+ type CubeRotations = readonly [readonly number[], readonly number[], readonly number[]];
2108
+ /** `CAsteroidField`'s constructor: quarter turns about each axis. */
2109
+ export declare const RETAIL_CUBE_ROTATIONS: CubeRotations;
2110
+ /** `xaxis_rotation` and its kin as written, in degrees, to `CubeRotations`. */
2111
+ export declare const cubeRotations: (degrees: readonly (readonly number[])[]) => CubeRotations;
2112
+ /** `CAsteroidField::closest_cube_pos` (`0x45ee0`). */
2113
+ export declare const closestCubePosition: ({ x, y, z }: Vector3, size: number) => Vector3;
2114
+ /** `compute_cube_id` (`0x62a6010`): 0 for a position past the grid. */
2115
+ export declare const cubeId: ({ x, y, z }: Vector3) => number;
2116
+ /** An `asteroid =` line's turn, its three angles in degrees as written. */
2117
+ export declare const entryOrientation: (x: number, y: number, z: number) => Matrix4;
2118
+ /**
2119
+ * `compute_cube_orientation` (`0x62a4010`): an LCG seeded with the cube's id says whether it's
2120
+ * empty, at `frequency`, and which of each axis's four angles turns it.
2121
+ */
2122
+ export declare const cubeOrientation: (id: number, rotations: CubeRotations, frequency: number) => Matrix4 | undefined;
2123
+ /** One `asteroid =` of a `[Cube]`: the level it draws, and where in the cube. */
2124
+ interface CubeAsteroid {
2125
+ mesh: VMeshData;
2126
+ reference: VMeshRef;
2127
+ /** Its offset in the cube: the line's three, each held to [-1, 1], times half a cube. */
2128
+ offset: Vector3;
2129
+ orientation: Matrix4;
2130
+ }
2131
+ /**
2132
+ * `CliAsteroidMergeCubeMesh`: every asteroid's vertices turned and moved into the cube, one mesh in
2133
+ * list order, drawn whole with the first's material, each copied into `cubeFormat`'s layout.
2134
+ */
2135
+ export declare const mergeCube: (asteroids: readonly CubeAsteroid[]) => {
2136
+ mesh: VMeshData;
2137
+ reference: VMeshRef;
2138
+ } | undefined;
2139
+ /** A zone that takes cubes, as `[Exclusion Zones]` names it. */
2140
+ interface CubeExclusion {
2141
+ zone: Zone;
2142
+ /** Its zone's `edge_fraction`. 0 when absent. */
2143
+ edgeFraction?: number;
2144
+ /** `empty_cube_frequency` of its own: then it thins the cubes rather than taking them. */
2145
+ emptyFrequency?: number;
2146
+ /** `exclude_billboards` above 0: no billboard stands in it. */
2147
+ excludeBillboards?: boolean;
2148
+ /** `billboard_count`, truncated, when above 0: how many billboards stand in it. */
2149
+ billboardCount?: number;
2150
+ /**
2151
+ * `exclude_dynamic_asteroids` above 0, on a sphere or an ellipsoid (`062a4f81`): no dynamic
2152
+ * asteroid is placed about a point in it.
2153
+ */
2154
+ excludeDynamicAsteroids?: boolean;
2155
+ }
2156
+ /** What the cube walk needs of a field, as its file and zone give it. The host reads them. */
2157
+ interface CubeField {
2158
+ zone: Zone;
2159
+ /** Its zone's `edge_fraction`. 0 when absent. */
2160
+ edgeFraction?: number;
2161
+ /** `cube_size`, 400 when absent. */
2162
+ cubeSize: number;
2163
+ /** `fill_dist`, 1200 when absent. */
2164
+ fillDistance: number;
2165
+ /** `empty_cube_frequency`, 0 when absent. */
2166
+ emptyFrequency?: number;
2167
+ /** `max_alpha`, 1 when absent. */
2168
+ maxAlpha?: number;
2169
+ /**
2170
+ * `test_asteroid_placement`, set by the key alone: no cube is drawn, though its band, billboards
2171
+ * and dynamic asteroids are (`CliAsteroidDrawNearField`, `0x522a80`). No retail file sets it.
2172
+ */
2173
+ testPlacement?: boolean;
2174
+ rotations?: CubeRotations;
2175
+ exclusions?: readonly CubeExclusion[];
2176
+ /**
2177
+ * `+0x230`: how far the walk reaches, from the `Asteroids` option as last applied, `(1 - d) ·
2178
+ * 0.65 + d`. 1 when absent, as at 1 and at load.
2179
+ */
2180
+ detailScale?: number;
2181
+ }
2182
+ /** A cube to draw: where, turned how, and how faded. */
2183
+ interface CubeDraw {
2184
+ position: Vector3;
2185
+ orientation: Matrix4;
2186
+ /** `max_alpha`, less over the last half cube before the walk's reach. */
2187
+ alpha: number;
2188
+ }
2189
+ /** `CAsteroidField::near_field` (`0x62a53c0`): within the zone, or near enough its surface. */
2190
+ export declare const nearField: (field: CubeField, eye: Vector3) => boolean;
2191
+ /**
2192
+ * `CliAsteroidDrawNearField`: the cubes to draw, in the walk's order. A box of cubes spans the view
2193
+ * out to `fill_dist`; each is kept if it's within the side planes and the reach, the zone holds it
2194
+ * past 0.001, no exclusion takes it, and its id doesn't leave it empty. The camera's own cube is
2195
+ * drawn last if the walk missed it. Nothing past `nearField`, nor with `testPlacement`.
2196
+ */
2197
+ export declare const fieldCubes: (field: CubeField, camera: Camera, aspect: number,
2198
+ /** Counts the centres within view depth and the frustum, which the zone and exclusions are asked. */
2199
+ counted?: {
2200
+ tested: number;
2201
+ }) => CubeDraw[];
2202
+ /** A field as the frame draws it: its cube walk, its merged mesh's lookups, and its colours. */
2203
+ interface SpaceAsteroidField extends CubeField {
2204
+ /** `mergeCube`'s mesh, with the lookups its material and textures resolve through. */
2205
+ cube: Pick<SceneModel, 'material' | 'texture' | 'materialMap'> & {
2206
+ mesh: VMeshData;
2207
+ reference: VMeshRef;
2208
+ };
2209
+ /** `diffuse_color` over 255, or `tint_field`'s. */
2210
+ diffuse: Vector3;
2211
+ /** `ambient_color` over 255, or `tint_field`'s. */
2212
+ ambient: Vector3;
2213
+ /** `ambient_increase`, 0 to 255 a channel, added to the ambient the field is lit by. */
2214
+ ambientIncrease?: Vector3;
2215
+ /**
2216
+ * `contains_fog_zone`: lit and fogged by the nebula the camera is in, at its depth. Without, by
2217
+ * the nebula holding its zone's centre, in full.
2218
+ */
2219
+ containsFogZone?: boolean;
2220
+ /** Its `[Band]`, built from its zone and colours. */
2221
+ band?: AsteroidBand;
2222
+ /** Its `[ExclusionBand]`s, in file order, each about its own `zone`. */
2223
+ exclusionBands?: readonly AsteroidBand[];
2224
+ /** Its `[AsteroidBillboards]`, when the `Asteroids` option is above 1 and effects are on. */
2225
+ billboards?: AsteroidBillboards;
2226
+ /**
2227
+ * Its `[DynamicAsteroids]`, in file order, each with its archetype's top level as `mergeCube`
2228
+ * gives it alone: the whole mesh drawn with its first group's material.
2229
+ */
2230
+ dynamic?: readonly {
2231
+ rocks: DynamicAsteroids;
2232
+ model: SpaceAsteroidField['cube'];
2233
+ }[];
2234
+ }
2235
+ /** A `[lens_glow]`, over `SunEffectBodyInit`'s defaults (`FUN_0050d300`, `FUN_0050d420`). */
2236
+ interface LensGlow {
2237
+ /** `shape`. Nothing draws without one, but its samples still block the flare. */
2238
+ shape?: TextureShape;
2239
+ /** `radius_scale`, 2 when absent: the corona's reach, in the star's radii. */
2240
+ radiusScale?: number;
2241
+ /** `inner_color`, at the centre. White when absent. */
2242
+ innerColor?: Vector3;
2243
+ /** `outer_color`, at the rim. White when absent. */
2244
+ outerColor?: Vector3;
2245
+ /** `glow_fade_in_seconds` and `glow_fade_out_seconds`, 0.25 when absent: each sample's easing. */
2246
+ fadeIn?: number;
2247
+ fadeOut?: number;
2248
+ /** `interior_tween`, 0.5 when absent: where the inner ring stands, centre to rim. */
2249
+ interiorTween?: number;
2250
+ /** `interior_ray_tween`, 1 when absent: where on screen the inner ring's rays are cast from. */
2251
+ interiorRayTween?: number;
2252
+ /** `max_LOD`, truncated, 5 when absent: the most samples, from 0 (one) to 5 (25). */
2253
+ maxLod?: number;
2254
+ }
2255
+ /** What a ray from the camera hits: `PhySys::FindRayCollisions`'s `RayHit`, and its `CObject`. */
2256
+ interface RayHit {
2257
+ /** What the host calls the object, compared with the sun's own solar and the player's ship. */
2258
+ object?: unknown;
2259
+ /** `CObject::WorldPosition`. */
2260
+ position: Vector3;
2261
+ /** `CObject::Radius`. */
2262
+ radius: number;
2263
+ /** The part hit is a shield's: it never blocks. */
2264
+ shield?: boolean;
2265
+ }
2266
+ /** What the corona asks of the frame. */
2267
+ interface CoronaView {
2268
+ camera: Camera;
2269
+ view: Matrix4;
2270
+ /** The viewport in pixels. */
2271
+ width: number;
2272
+ height: number;
2273
+ /**
2274
+ * `PhySys::FindRayCollisions` from the camera to `to`, at most `limit` hits. None when absent, so
2275
+ * nothing blocks.
2276
+ */
2277
+ rayHits?: (from: Vector3, to: Vector3, limit: number) => readonly RayHit[];
2278
+ /** The sun's own solar, which its rays pass through (`0x50c767`). */
2279
+ own?: unknown;
2280
+ /** The player's ship from the cockpit, which they pass through too (`0x50c798`). */
2281
+ player?: unknown;
2282
+ /** The corona's fewest samples, `sunLeastLod`. */
2283
+ leastLod: number;
2284
+ /** `1 - ZoneOcclusion`: the corona's alpha. */
2285
+ visibility: number;
2286
+ }
2287
+ /**
2288
+ * The `SunEffect` option, as `PerfOptions.ini` writes it, to the quality `G_SunEffectQuality`:
2289
+ * times 1.33333, held to 1 (`LAB_00510bf0`). At 0 or less no corona, spine or flare draws.
2290
+ */
2291
+ export declare const sunEffectQuality: (option: number) => number;
2292
+ /**
2293
+ * The corona's least LOD at a quality: `ftol((1 - Q) · max + 0.5)`. A star is made after the option
2294
+ * is applied and before its `max_LOD` is read, so this is of the default 5 (`0x510e9c`); the option
2295
+ * changed in a system gives its stars their own `max_LOD`'s instead, which the host can pass.
2296
+ */
2297
+ export declare const sunLeastLod: (quality: number, maxLod?: number) => number;
2298
+ /**
2299
+ * `UpdateIntensity`, the solar's own update: the step the next draw eases by, and the flare's
2300
+ * brightness toward what the corona last showed. Retail's suns write `intensity_fade_in` 0, which
2301
+ * loads as 1e-7, then `zone_occlusion_fade_in`, which lands there too (`0x511381`).
2302
+ */
2303
+ export declare const updateStar: (star: Star, step: number) => void;
2304
+ /**
2305
+ * Whether the frame since its last update left every fade as it found it: with the camera still,
2306
+ * the next frame would draw it the same, so a host may stop drawing. Not before its first frame.
2307
+ */
2308
+ export declare const starSettled: (star: Star) => boolean;
2309
+ /** The flare's brightness, `+0x64`, and how much a zone hides it, `+0x34`. */
2310
+ export declare const starIntensity: (star: Star) => {
2311
+ intensity: number;
2312
+ zoneOcclusion: number;
2313
+ };
2314
+ /** One `spine`: its length and width in the star's pixel scale, its colours base to tip, alpha. */
2315
+ interface StarSpine {
2316
+ length: number;
2317
+ width: number;
2318
+ innerColor: Vector3;
2319
+ outerColor: Vector3;
2320
+ alpha: number;
2321
+ }
2322
+ /** A `[spines]` (`FUN_0050fba0`), over `FStarSphere`'s defaults. */
2323
+ interface StarSpines {
2324
+ /** `shape`. Nothing draws without one. */
2325
+ shape?: TextureShape;
2326
+ /** `radius_scale`, 1 when absent: every spine's length and width times it. */
2327
+ radiusScale?: number;
2328
+ /** `min_radius` and `max_radius`, 1 and 1000 when absent: the pixel scale held between. */
2329
+ minRadius?: number;
2330
+ maxRadius?: number;
2331
+ /** In file order, spread evenly round from the right, anticlockwise on screen. */
2332
+ spines: readonly StarSpine[];
2333
+ }
2334
+ /** A `bead` of a `[lens_flare]`: how far along, how big, its colour and alpha. */
2335
+ interface FlareBead {
2336
+ along: number;
2337
+ size: number;
2338
+ color: Vector3;
2339
+ alpha: number;
2340
+ }
2341
+ /** A `[lens_flare]` (`FUN_0050f3a0`), over `FStarSphere`'s defaults. */
2342
+ interface StarLensFlare {
2343
+ /** `shape`: its texture, whole. Nothing draws without one. */
2344
+ shape?: TextureShape;
2345
+ /** `min_radius` and `max_radius`, 5 and 800 when absent: the pixel scale held between. */
2346
+ minRadius?: number;
2347
+ maxRadius?: number;
2348
+ /**
2349
+ * Its beads. Retail sorts them by where the heap put them, but they add, so any order gives the
2350
+ * same pixels.
2351
+ */
2352
+ beads: readonly FlareBead[];
2353
+ }
2354
+ /**
2355
+ * `FStarSphere::Draw` (`LAB_00511ca0`) after the effects: the zone's occlusion eased toward
2356
+ * `zoneBlocked`, what showed of the corona noted for the flare, the sun projected, its samples
2357
+ * cast, then its spines and its corona drawn. Nothing past a zone's full occlusion, nor with the sun
2358
+ * behind.
2359
+ */
2360
+ export declare const starCorona: (star: Star, position: Vector3, zoneBlocked: boolean, view: Omit<CoronaView, "visibility">) => Draw[];
2361
+ /**
2362
+ * `FStarSphere::DrawFlare` (`LAB_005124f0`), at the cockpit pass's end: the beads along the line
2363
+ * from the sun through the screen's middle, at the screen place and pixel scale the last draw left,
2364
+ * each a square of the whole texture, added by alpha (`SRCALPHA, ONE`). They fade as the sun nears
2365
+ * the middle, brighten with `Intensity`, and go with the zone's occlusion and the sun behind.
2366
+ */
2367
+ export declare const starFlare: (star: Star, view: {
2368
+ view: Matrix4;
2369
+ width: number;
2370
+ height: number;
2371
+ }) => Draw | undefined;
2372
+ /** A `[star_glow]`, over `FStarSphere`'s defaults. `star_glow` and `star_center` both name one. */
2373
+ interface StarGlow {
2374
+ /** `shape`, a `tex_shape` of `StarArch.ini`'s `[Texture]`. Nothing draws without one. */
2375
+ shape?: TextureShape;
2376
+ /** `scale`, 1 when absent: times the star's `radius`, half the fan's width. */
2377
+ scale?: number;
2378
+ /** `inner_color`, at the fan's centre. White when absent; each is held to 1 as it's drawn. */
2379
+ innerColor?: Vector3;
2380
+ /** `outer_color`, at its rim. White when absent. */
2381
+ outerColor?: Vector3;
2382
+ }
2383
+ /**
2384
+ * A `[star]`, which the host reads (`FStarSphere::Load`, `0x511020`). Its `alpha` keys, its
2385
+ * `star_glow`'s included, are read and never used: the fans take their alpha from the caller.
2386
+ */
2387
+ interface Star {
2388
+ /** `radius`, 1000 when absent. */
2389
+ radius?: number;
2390
+ /**
2391
+ * `star_glow`: the first of the section's `star_glow` keys that names a `[star_glow]`. With none,
2392
+ * the defaults: no shape, so nothing draws, but its scale of 1 still sizes the star.
2393
+ */
2394
+ glow?: StarGlow;
2395
+ /** `star_center`: the last key's, and none when that names no `[star_glow]`. */
2396
+ center?: StarGlow;
2397
+ /** `lens_glow`: the first that names a `[lens_glow]`. Its defaults when none does. */
2398
+ lensGlow?: LensGlow;
2399
+ /** `spines`: the first that names a `[spines]`. None when none does. */
2400
+ spines?: StarSpines;
2401
+ /** `lens_flare`: the first that names a `[lens_flare]`. None when none does. */
2402
+ lensFlare?: StarLensFlare;
2403
+ /**
2404
+ * `+0x20` and `+0x24`, 1 s each when absent: how fast the flare follows what the corona shows.
2405
+ * `intensity_fade_in` and `_out` write them, a 0 as 1e-7, but so do `zone_occlusion_fade_in` and
2406
+ * `_out`, unheld (`0x511381`, `0x5113b7`): whichever comes last in the section wins.
2407
+ */
2408
+ intensityFade?: {
2409
+ fadeIn?: number;
2410
+ fadeOut?: number;
2411
+ };
2412
+ /**
2413
+ * `+0x28` and `+0x2c`, 2 s each when absent: how fast a zone between hides it. No key reaches them
2414
+ * in retail, which a mod's patch could.
2415
+ */
2416
+ zoneOcclusionFade?: {
2417
+ fadeIn?: number;
2418
+ fadeOut?: number;
2419
+ };
2420
+ }
2421
+ /** `StarSphereMaxGlowScale` (`0x512620`): the solar's sphere, `Slot178`'s radius about its position. */
2422
+ export declare const starRadius: ({ radius, glow, center }: Star) => number;
2423
+ /**
2424
+ * `FStarSphere::DrawBillboard` (`0x5118d0`): the star's `star_glow`, then its `star_center` at
2425
+ * `alpha` squared, each a square of `radius × size × scale` across, turned to face the camera about
2426
+ * its up. Unlit, unculled, with no depth test or writes, under `projection`: the camera's own, its far
2427
+ * plane at 250000. The solar's own draw gives an `alpha` and `size` of 1 (`DrawSimple`).
2428
+ */
2429
+ export declare const starGlowDraws: (star: Star, position: Vector3, camera: Camera, projection: Matrix4, alpha?: number, size?: number) => Draw[];
2430
+ /** One of an object's physics shapes, in the frame its points are in. */
2431
+ interface HitboxSurface {
2432
+ surface: Surface;
2433
+ /** Its frame in the world: the object's for the root part's, its own part's for a `!fxd` one. */
2434
+ world: Matrix4;
2435
+ }
2436
+ /**
2437
+ * An object's collision shapes as `BuildIVP_Real` (`0x6288dd0`) makes them: its `.sur`'s root part,
2438
+ * which holds every fixed child's hulls too, and each `!fxd` part's own. A `.sph` solar's is a ball
2439
+ * of its `solar_radius` instead (`BuildIVP_Sphere`, from `CSolar`'s `0x62b78c5`).
2440
+ */
2441
+ interface Hitbox {
2442
+ surfaces?: readonly HitboxSurface[];
2443
+ /** A ball about the object's position. Its hits have no hull, so part 0, never a shield. */
2444
+ ball?: number;
2445
+ /** Whether a hull's id, a part's, is a shield's (`CObject::is_shield_part`). None when absent. */
2446
+ shield?: (id: number) => boolean;
2447
+ }
2448
+ /** An object a ray can hit, and what the hit reports of it. */
2449
+ interface HitboxObject {
2450
+ /** Handed back as the hit's `object`. */
2451
+ object?: unknown;
2452
+ /** `CObject::WorldPosition`. */
2453
+ position: Vector3;
2454
+ /** `CObject::Radius`. */
2455
+ radius: number;
2456
+ hitbox: Hitbox;
2457
+ }
2458
+ /**
2459
+ * `FindRayCollisions` from `from` to `to`: each hull entered, nearest first, at most `limit`. Inner
2460
+ * nodes' bounding hulls only cull, as IVP's ledge tree uses them.
2461
+ */
2462
+ export declare const castRay: (objects: readonly HitboxObject[], from: Vector3, to: Vector3, limit: number) => RayHit[];
2463
+ /** Vertices as `x, y, z, u, v`, colours as D3D's bytes `b, g, r, a`, and triangles. */
2464
+ interface ImmediateBatch {
2465
+ vertices: number[];
2466
+ colors: number[];
2467
+ indices: number[];
2468
+ count: number;
2469
+ }
2470
+ /** What retail hardcodes, and a mod can patch. */
2471
+ interface BeamLimits {
2472
+ /** The shot's sphere, in the view test (`FUN_004f9540`'s `+0x74`). */
2473
+ radius: number;
2474
+ /** Seconds it fades over, once it's over, and the most it can have left (`DAT_006129cc`). */
2475
+ fade: number;
2476
+ /** Added to `core_length` and `tail_length` for how far it travels while it grows (`DAT_005d9e0c`). */
2477
+ growthPad: number;
2478
+ }
2479
+ export declare const RETAIL_BEAM_LIMITS: Readonly<BeamLimits>;
2480
+ /** A `[BeamSpear]` or `[BeamBolt]`, as `FUN_0042e4a0` reads it: colours are 0 to 1, by 1/255 as a float. */
2481
+ interface BeamArchetype {
2482
+ kind: 'spear' | 'bolt';
2483
+ tipColor: Vector3;
2484
+ tailColor: Vector3;
2485
+ coreColor: Vector3;
2486
+ secCoreColor: Vector3;
2487
+ outerColor: Vector3;
2488
+ secOuterColor: Vector3;
2489
+ headBrightness: number;
2490
+ trailBrightness: number;
2491
+ tipLength: number;
2492
+ tailLength: number;
2493
+ coreLength: number;
2494
+ coreWidth: number;
2495
+ secCoreWidth: number;
2496
+ headWidth: number;
2497
+ /** The width on a shot's first frame, from outside the cockpit. */
2498
+ flashSize: number;
2499
+ /** `head_texture = ball`: the top half of the head's V, where anything else has the bottom. */
2500
+ ballHead: boolean;
2501
+ /** `trail_texture = thin`: the left of the trail's U, where anything else has the right. */
2502
+ thinTrail: boolean;
2503
+ }
2504
+ /** As written, a colour 0 to 255: the keys `[BeamSpear]` and `[BeamBolt]` use. */
2505
+ interface BeamSection {
2506
+ kind: BeamArchetype['kind'];
2507
+ tipColor?: Vector3;
2508
+ tailColor?: Vector3;
2509
+ coreColor?: Vector3;
2510
+ secCoreColor?: Vector3;
2511
+ outerColor?: Vector3;
2512
+ secOuterColor?: Vector3;
2513
+ headBrightness?: number;
2514
+ trailBrightness?: number;
2515
+ tipLength?: number;
2516
+ tailLength?: number;
2517
+ coreLength?: number;
2518
+ coreWidth?: number;
2519
+ secCoreWidth?: number;
2520
+ headWidth?: number;
2521
+ flashSize?: number;
2522
+ headTexture?: string;
2523
+ trailTexture?: string;
2524
+ }
2525
+ /**
2526
+ * A section to what's drawn: each key defaults to 1 (`FUN_0042e430`), then `FUN_0042e920` holds the
2527
+ * lengths to the widths and each brightness to 0.01 to 1. A bolt's tail is held to `sec_core_width`
2528
+ * where a spear's is to `core_width`.
2529
+ */
2530
+ export declare const beamArchetype: (section: BeamSection) => BeamArchetype;
2531
+ /**
2532
+ * `LookMatrix` (`Common/common_csys.cpp`): the turn whose -Z runs along `direction`. The player's guns
2533
+ * look along each barrel's aim like this, the shot's own orientation aside (`CliBeamActivateEffects`).
2534
+ * Its side is a retail quirk: `(-x, y)` of the negated direction, and `+Y` straight ahead.
2535
+ */
2536
+ export declare const lookMatrix: ({ x, y, z }: Vector3) => Matrix3;
2537
+ /** Where a shot starts, as `CliBeamActivateEffects` (`0x53f340`) has it. */
2538
+ interface BeamStart {
2539
+ orientation: Matrix3;
2540
+ position: Vector3;
2541
+ /** How fast it leaves, which sets how long it takes to grow to its length. */
2542
+ speed: number;
2543
+ /**
2544
+ * The player's own: it looks along each barrel's aim (`lookMatrix`), and a spear's first frame
2545
+ * takes `flash_size`. Only then does a spear flash, and never from the cockpit.
2546
+ */
2547
+ look?: boolean;
2548
+ }
2549
+ /**
2550
+ * One shot, from the frame it's fired to the frame it's gone. Each frame the host places it, and
2551
+ * `collectSpaceFrame` ages and draws it.
2552
+ *
2553
+ * A hit ends it at once (`FClientBeamGameobj::Slot194`): the host stops giving it. A life that runs
2554
+ * out `kill`s it: it coasts on and fades, and the host drops it once it's `dead`.
2555
+ */
2556
+ export declare class BeamEffect {
2557
+ readonly archetype: BeamArchetype;
2558
+ orientation: Matrix3;
2559
+ position: Vector3;
2560
+ private readonly limits;
2561
+ private readonly look;
2562
+ private readonly growth;
2563
+ private age;
2564
+ private left;
2565
+ private velocity?;
2566
+ dead: boolean;
2567
+ constructor(archetype: BeamArchetype, { orientation, position, speed, look }: BeamStart, limits?: Partial<BeamLimits>);
2568
+ /** Where the shot's object is. A killed shot goes on by itself, and this does nothing. */
2569
+ place(orientation: Matrix3, position: Vector3): void;
2570
+ /**
2571
+ * Its life ran out (`LAB_004f97a0`): from its object's velocity it coasts, and fades over
2572
+ * `limits.fade`, or what it has left of that if killed again.
2573
+ */
2574
+ kill(velocity: Vector3): void;
2575
+ /**
2576
+ * One frame of the walk (`EffectTypeBucketWalk`), the step held to 0.1 there: its coast, then
2577
+ * `UpdateVisibility`, then `TickDraw`, ending with what it builds into `batch`, if it's seen.
2578
+ */
2579
+ walk(step: number, seen: BeamSeen, batch: BeamBatch): void;
2580
+ private build;
2581
+ }
2582
+ /** The view test's answer for a place and radius. */
2583
+ type BeamSeen = (position: Vector3, radius: number) => 'draw' | 'run' | 'die';
2584
+ /** A frame's vertices and triangles, as `FUN_0046eab0` and `FUN_004fc9a0` append them. */
2585
+ interface BeamBatch extends ImmediateBatch {
2586
+ firstPerson: boolean;
2587
+ }
2588
+ export declare const beamBatch: (firstPerson?: boolean) => BeamBatch;
2589
+ /** What retail hardcodes, and a mod can patch. */
2590
+ interface TractorLimits {
2591
+ /** The most arms drawn, counting only those holding loot (`JG 0xa`, `0x512e5f`). */
2592
+ arms: number;
2593
+ /** The beam's segment length, and so its quads' (`0x40a00000`). */
2594
+ width: number;
2595
+ /** How far each point strays, by the segment length (`0x3e4ccccd`). */
2596
+ jitter: number;
2597
+ /** The loot's radius, by its physical radius: where the beam stops (`DAT_005cf100`). */
2598
+ lootScale: number;
2599
+ /** The most the beam's end strays in `+x` and `+y` (`0x3f000000`). */
2600
+ endJitter: number;
2601
+ /** Arcs off a loot the tip is in (`0x513227`). */
2602
+ sparks: number;
2603
+ /** An arc's segment length. */
2604
+ sparkWidth: number;
2605
+ /** The arcs' cone, at a tip a whole radius in (`DAT_005db708`, 0.8π). */
2606
+ sparkSpread: number;
2607
+ /** How far back along the beam from the loot's centre the arcs turn about (`DAT_005d3e08`). */
2608
+ sparkPullBack: number;
2609
+ /** A beam quad's length and width past its segment (`DAT_005c9804`). */
2610
+ lineQuad: number;
2611
+ /** An arc quad's (`DAT_005c9800`). */
2612
+ arcQuad: number;
2613
+ }
2614
+ export declare const RETAIL_TRACTOR_LIMITS: Readonly<TractorLimits>;
2615
+ /** One arm holding loot, as `TractorArm` has it this frame. An arm with none is left out. */
2616
+ interface TractorArm {
2617
+ /** `GetTipPos`: how far the arm has reached. */
2618
+ tip: Vector3;
2619
+ /** The loot's place (`CObject +0x2c`). */
2620
+ loot: Vector3;
2621
+ /** Its physical radius (`get_physical_radius_r`). */
2622
+ radius: number;
2623
+ }
2624
+ /**
2625
+ * A working tractor this frame (`IsFunctioning`); the host leaves out one that isn't. Its `color`
2626
+ * and `maxLength` are its `[Tractor]`'s `color` and `max_length`, as written.
2627
+ */
2628
+ interface TractorBeam {
2629
+ /** `CETractor::GetSourcePos`. */
2630
+ source: Vector3;
2631
+ maxLength: number;
2632
+ /** 0 to 255, as written: each is truncated to a byte (`FCliTractor::ReadUnknown`). */
2633
+ color: Vector3;
2634
+ /** In the tractor's own order. */
2635
+ arms: readonly TractorArm[];
2636
+ }
2637
+ /** A `[layer]` of a `[gate_tunnel]`, as `GateTunnelIniLoad` (`0x4fe1c0`) reads it. */
2638
+ interface TunnelLayer {
2639
+ /** `texture`, a shape of `FX/effect_shapes.ini`: its texture's first frame, whole. None if not found. */
2640
+ texture?: Texture;
2641
+ /** `color`, 0 to 1: the bytes as written over 255. */
2642
+ color: Vector3;
2643
+ /**
2644
+ * `near_alpha` and `far_alpha`. Retail's files write `near_alpha_factor` and `far_alpha_factor`,
2645
+ * which `Freelancer-original.exe` has no string for, so every retail layer fades from 1 to 0.
2646
+ */
2647
+ nearAlpha: number;
2648
+ farAlpha: number;
2649
+ radiusFactor: number;
2650
+ /** `u_offset` and `v_offset`: where the texture starts, scrolled on by `du` and `dv` a second. */
2651
+ uOffset: number;
2652
+ vOffset: number;
2653
+ du: number;
2654
+ dv: number;
2655
+ vScale: number;
2656
+ }
2657
+ /** A `[layer]`'s defaults (`0x4fe70c`). */
2658
+ export declare const RETAIL_TUNNEL_LAYER: Readonly<TunnelLayer>;
2659
+ /**
2660
+ * A `[gate_tunnel]`'s keys, as `GateTunnelIniLoad` reads them. `fade_distance`, `near_alpha` and
2661
+ * `far_alpha` here are never read.
2662
+ */
2663
+ interface TunnelSettings {
2664
+ writeDepth: boolean;
2665
+ /** `num_spline_control_points`. Under 2 builds no curves, where retail reads past their ends. */
2666
+ controlPoints: number;
2667
+ /** `x_range`, `y_range` and `z_range`: the path wanders `x` and `y` across, and runs `z` long. */
2668
+ range: Vector3;
2669
+ minRadius: number;
2670
+ maxRadius: number;
2671
+ /** What the radius is ahead of the ship, at the window's far end. */
2672
+ farRadiusFactor: number;
2673
+ /** Of the path, a second. */
2674
+ minSpeed: number;
2675
+ maxSpeed: number;
2676
+ timeToMaxSpeed: number;
2677
+ /** `num_t_steps`: along the path, the first strip's length 1 / it. */
2678
+ tSteps: number;
2679
+ /** `num_s_steps`: around each ring. */
2680
+ sSteps: number;
2681
+ minRotation: number;
2682
+ maxRotation: number;
2683
+ /** `min_rgb` and `max_rgb`, 0 to 1: the colour offsets' range. */
2684
+ minRgb: Vector3;
2685
+ maxRgb: Vector3;
2686
+ /** Two at most: a third is refused. */
2687
+ layers: readonly TunnelLayer[];
2688
+ }
2689
+ /** `FGateTunnel`'s constructor (`0x4fee30`). */
2690
+ export declare const RETAIL_TUNNEL: Readonly<TunnelSettings>;
2691
+ /** `SetState`: 0 idle, 1 built for the jump out, 2 in the tunnel, 3 jumping in. */
2692
+ type TunnelState = 0 | 1 | 2 | 3;
2693
+ /**
2694
+ * One loaded `[gate_tunnel]`. The game keeps one of each and hands it to the player's jump
2695
+ * (`FJumpShipEffect`, `0x503440`), which places it at the archetype's `jump_out_hp` and drives its state;
2696
+ * the host does both. Loading draws its curves, and so does each jump out.
2697
+ */
2698
+ export declare class GateTunnel {
2699
+ /** The gate's `jump_out_hp` in the world: the path runs down its -Z, from 5 out. */
2700
+ frame: Matrix4;
2701
+ state: TunnelState;
2702
+ /** Where the ship is along the path, 0 to 1. */
2703
+ phase: number;
2704
+ /** The ship, as `Update` beams it along the path in states 2 and 3: right, up and back, and where. */
2705
+ ship: Matrix4 | undefined;
2706
+ readonly layers: TunnelLayer[];
2707
+ private readonly settings;
2708
+ private readonly random;
2709
+ private readonly pathX;
2710
+ private readonly pathY;
2711
+ private readonly pathZ;
2712
+ private readonly radius;
2713
+ private readonly rotation;
2714
+ private readonly brightness;
2715
+ private readonly offsetR;
2716
+ private readonly offsetG;
2717
+ private readonly offsetB;
2718
+ private speedClock;
2719
+ private segment;
2720
+ private lit;
2721
+ constructor(settings?: Partial<TunnelSettings>, { random }?: {
2722
+ random?: Random;
2723
+ });
2724
+ /**
2725
+ * The light `EnterState1` makes: directional, mostly down, white, flickering once a draw has
2726
+ * switched it on. In the tunnel the player's ship is lit by it alone, over the jump's
2727
+ * `jump_ambient` (`FClientShipGameobj::Slot184`, `0x546480`); `EnterState3` releases it, so a ship
2728
+ * jumping in has none.
2729
+ */
2730
+ get light(): SceneLight | undefined;
2731
+ /** `SetState` (`FUN_004ff360`). */
2732
+ setState(state: TunnelState): void;
2733
+ /** `Update` (`FUN_004ff3c0`), each step of the jump's states 2 and 3. */
2734
+ update(step: number): void;
2735
+ /**
2736
+ * `Draw` (`FUN_004ffab0`): each layer from 0.1 of the path ahead of the ship to 0.01 behind, one
2737
+ * strip around the tube at a time, nearer strips longer by 1.1 each. Unculled and blended, the
2738
+ * first layer by alpha and the second `ONE, INVSRCALPHA`, unlit in its vertex colour times the
2739
+ * texture, which wraps; depth written as `write_depth_buffer` says. It switches the light on.
2740
+ *
2741
+ * Depth is tested `LEQUAL`, Win3D's own state, and the alpha test and fog are what the frame left:
2742
+ * `GREATER 0` and none, as `jump_hole` captured them.
2743
+ */
2744
+ draws(): Draw[];
2745
+ private build;
2746
+ private makeLight;
2747
+ private pathPoint;
2748
+ private pathFrame;
2749
+ private emitLayer;
2750
+ }
2751
+ interface SpaceObject {
2752
+ /** Not drawn for a sun: its `star` is. */
2753
+ model: SceneModel;
2754
+ /** `get_position`, which its sort distance is from. */
2755
+ position: Vector3;
2756
+ /** `Slot178`, in world space: what it's tested against the view with. */
2757
+ sphere: {
2758
+ center: Vector3;
2759
+ radius: number;
2760
+ };
2761
+ /** Only a solar reaches the far list. Anything else stops at the system's draw range. */
2762
+ solar?: {
2763
+ /**
2764
+ * In `G_LargeSolars` (`0x540d18`), `extent` over 200 and type bit 7 clear: no
2765
+ * `solarQueryRange` limit.
2766
+ */
2767
+ large?: boolean;
2768
+ /**
2769
+ * `CObject::Radius`: the engine's extent of its instance, the root part's top level, not its
2770
+ * `.sur`. `sphere`'s radius when absent.
2771
+ */
2772
+ extent?: number;
2773
+ /** Planet or moon, `TypeBits & 7` with no star sphere: sorted by its surface, not its centre. */
2774
+ planet?: boolean;
2775
+ };
2776
+ /**
2777
+ * A ship's `pilot_mesh`, placed at its `HpPilot` less the pilot's own `HpConnect`
2778
+ * (`AttachPilotMesh`, `0x544380`). Drawn before the model, only where it reflects (`0x546087`).
2779
+ * The game's option 0x1d turns it off, so the host decides.
2780
+ */
2781
+ pilot?: SceneModel;
2782
+ /**
2783
+ * Equipment with a model, each hung on its hardpoint by `attachJoint` and placed with its own
2784
+ * archetype's lists. The engine joins it as the last child of the part holding the hardpoint, so
2785
+ * it draws after that part's subtree (`ConnectInstanceChild`, `RenderInstance`); here, after the
2786
+ * whole model, in the order given. Only the order within the object can differ: batched glass
2787
+ * still waits for its flush, but an unbatched draw on a part with later siblings goes after them.
2788
+ */
2789
+ attached?: readonly SceneModel[];
2790
+ /**
2791
+ * Its lights' flares, in the order its effect list holds them. They join the frame only when it
2792
+ * draws at the first tier (`GameobjSweepEffectList`), and only then do they blink.
2793
+ */
2794
+ flares?: readonly SpaceFlare[];
2795
+ /**
2796
+ * A solar's `star`: a sun. Its draw is its glow and nothing else, neither its model nor what's
2797
+ * attached (`Slot188`, `0x548220`); `sphere` is `starRadius` about its position (`Slot178`). The
2798
+ * game's sun option (0x24) turns it off, so the host decides.
2799
+ */
2800
+ star?: Star;
2801
+ /**
2802
+ * Its collision shapes, which a sun's corona rays test. A hit reports `position` and `CObject::Radius`,
2803
+ * taken as `solar.extent`, else `sphere`'s radius. Nothing to hit when absent.
2804
+ */
2805
+ hitbox?: Hitbox;
2806
+ /** The archetype's `nomad`: goes three lists up, after everything of its kind (`0x51f5b7`). */
2807
+ nomad?: boolean;
2808
+ /** The archetype's `distance_render`. Past it, it stops reflecting, and `lodRange` may apply. */
2809
+ distanceRender?: number;
2810
+ /** The archetype's `LodPick`, the largest of its `LODranges`. Past it, it draws nothing. */
2811
+ lodRange?: number;
2812
+ /**
2813
+ * The nebula it was last in, which retail keeps and tries first. It matters only where nebulae
2814
+ * overlap, and none do in retail. The first that holds it when absent. `null` for none at all: a
2815
+ * solar finds its nebula once, as its `[Object]` is read (`FUN_005485c0`), so one listed before
2816
+ * the system's `[Nebula]` sections has none (`asteroid_nebula`'s plant).
2817
+ */
2818
+ nebula?: SpaceNebula | null;
2819
+ }
2820
+ /** A space frame, and what became of each of its effects. */
2821
+ interface SpaceFrame extends Frame {
2822
+ /**
2823
+ * In the scene's order: an effect with a `position` is drawn, runs unseen, or ends, as the game
2824
+ * would. The host steps it with `lod` next, by no more than `effectMaxStep`. Undefined for one with no position, which draws.
2825
+ */
2826
+ effectViews: (EffectView | undefined)[];
2827
+ /** Each of the scene's `asteroids`, in order: its cubes drawn, and the centres `fieldCubes` tested. */
2828
+ asteroidCubes: {
2829
+ drawn: number;
2830
+ tested: number;
2831
+ }[];
2832
+ }
2833
+ /** `EffectTypeInfoByIndex`, from `[EffectType]`: the game's walk is by type. */
2834
+ interface SpaceEffect extends SceneEffect, Partial<Omit<PlacedEffect, 'type'>> {
2835
+ /** `[EffectType]`'s index, `EFT_NONE` 0. With a `position`, the view test decides it. */
2836
+ type: number;
2837
+ }
2838
+ interface SpaceScene {
2839
+ /** Its `far` is ignored: the frame's comes from `farClip`, or the field's. */
2840
+ camera: Camera;
2841
+ aspect: number;
2842
+ /** `[Ambient] color`, as written: 0 to 255. */
2843
+ ambient: Vector3;
2844
+ /** Its `[LightSource]`s, in file order. */
2845
+ lights: readonly SpaceLight[];
2846
+ objects: readonly SpaceObject[];
2847
+ /**
2848
+ * The star sphere's layers, in the order to draw them: those `skyLayers` names. Each stands at its
2849
+ * `instanceExtent` centre, as `StarSphereLoadLayer` (`0x552fd0`) moves it.
2850
+ */
2851
+ sky?: readonly SceneModel[];
2852
+ /** `[SystemInfo] space_farclip`, the far plane, past the draw range by `limits.rangeMargin`. */
2853
+ farClip?: number;
2854
+ /** Over `RETAIL_SPACE_LIMITS`, for a mod that patches them. */
2855
+ limits?: Partial<SpaceLimits>;
2856
+ /** Over `RETAIL_COCKPIT_LIMITS`, for a mod that patches them. */
2857
+ cockpitLimits?: Partial<CockpitLimits>;
2858
+ /** Its `[Nebula]`s, in file order: the first that holds a point is the one it's in. */
2859
+ nebulae?: readonly SpaceNebula[];
2860
+ /** Within a type in the order given, which is newest first in the game (`FUN_004fc920`). */
2861
+ effects?: readonly SpaceEffect[];
2862
+ /**
2863
+ * Every gun's shot in flight, newest first as the game's list holds them (`EffectTypeListLink`).
2864
+ * The frame ages them by `step`. A host drops one once it hits, or once it's `dead`.
2865
+ */
2866
+ beams?: readonly BeamEffect[];
2867
+ /** `tex_shape = code_beam`, from `[Texture]` in `effect_shapes.ini`. */
2868
+ beamTexture?: Texture;
2869
+ /** Every working tractor reaching for loot, newest first as the game's list holds them. */
2870
+ tractors?: readonly TractorBeam[];
2871
+ /** `tex_shape = line`, from `[Texture]` in `effect_shapes.ini`. */
2872
+ lineTexture?: Texture;
2873
+ /** `rand()` for the tractors' jitter. */
2874
+ tractorRandom?: Random;
2875
+ /** Over `RETAIL_TRACTOR_LIMITS`, for a mod that patches them. */
2876
+ tractorLimits?: Partial<TractorLimits>;
2877
+ /**
2878
+ * The camera is the cockpit's (`FFirstPersonCamera`): a shot's first frame shows no flash
2879
+ * (`0x4fa2a0`), and the frame goes on with its pass (`FirstPersonCameraSceneDraw`, `0x518420`).
2880
+ */
2881
+ firstPerson?: boolean;
2882
+ /** The HUD option (`0xa`), on: from the cockpit, depth is cleared for it even with no mesh. */
2883
+ hud?: boolean;
2884
+ /** Its mesh, when the `cockpit` option's applied value is over 0.999. Only from the cockpit. */
2885
+ cockpit?: SpaceCockpit;
2886
+ /**
2887
+ * The effects option (`0x25`), off. The host leaves out the effects and rings then; here it says
2888
+ * only that the batcher's bin 1 stays on, so what the cockpit's mesh queues waits for `atEnd`.
2889
+ */
2890
+ effectsOff?: boolean;
2891
+ /** `[System] space_color`, 0 to 1. Deep in a nebula, its fog's colour instead. */
2892
+ background?: Vector3;
2893
+ time?: number;
2894
+ /** The options and data the effects' view test reads. */
2895
+ effectView?: EffectViewOptions;
2896
+ /**
2897
+ * Seconds since the last frame, which each nebula's lightning advances by, and each drawn light's
2898
+ * blink, held to 0.1. 0 when absent.
2899
+ */
2900
+ step?: number;
2901
+ /**
2902
+ * Seconds the camera has had since the last frame, for what answers it rather than the world: the
2903
+ * suns' fades and the nebulae's turning and sorting. `step` when absent, as in the game, where
2904
+ * they're one. A host that stops its world's clock and lets the camera fly passes 0 as `step`.
2905
+ */
2906
+ cameraStep?: number;
2907
+ /** The camera cut to this frame, as it does entering a system: clouds are placed afresh. */
2908
+ cut?: boolean;
2909
+ /** The player's ship is in a trade lane: no dynamic asteroid is placed (`0x525da9`). */
2910
+ lane?: boolean;
2911
+ /** Its asteroid fields, in the order the system lists them. */
2912
+ asteroids?: readonly SpaceAsteroidField[];
2913
+ /** Its planets' rings, which the effects option shows (`0x25`). */
2914
+ rings?: readonly PlanetRing[];
2915
+ /** The viewport in pixels, which flares are sized and clipped in. No flares when absent. */
2916
+ viewport?: {
2917
+ width: number;
2918
+ height: number;
2919
+ };
2920
+ /** `fx/efx.txm`'s `shine`, from `[Texture]` in `effect_shapes.ini`, and its frame. */
2921
+ shine?: {
2922
+ texture: Texture;
2923
+ frame?: FrameRect;
2924
+ };
2925
+ /**
2926
+ * The `SunEffect` option as `PerfOptions.ini` writes it, the effects, sun and sun effects options
2927
+ * (0x25, 0x24, 0x28) being on. With none, or none above 0, no sun's corona draws.
2928
+ */
2929
+ sunEffect?: number;
2930
+ /**
2931
+ * `PhySys::FindRayCollisions`: what a ray from the camera hits. Each hit's `object` is compared
2932
+ * with a sun's own `SpaceObject` and with `player`. From the objects' `hitbox`es when absent.
2933
+ */
2934
+ rayHits?: CoronaView['rayHits'];
2935
+ /** The player's ship, seen from the cockpit: a sun's rays pass through it. None from outside. */
2936
+ player?: unknown;
2937
+ /** The system's space dust, which the frame updates and steps (`StarSysUpdate`, `0x554ecb`). */
2938
+ dust?: SpaceDust;
2939
+ /** Its zones with a `spacedust`, in file order, which `dust` looks among. */
2940
+ dustZones?: readonly DustZone[];
2941
+ /** `space_dust_detail` and game option `0x19`. Run times are the effect types'. */
2942
+ dustOptions?: Omit<DustOptions, 'runTimes'>;
2943
+ /**
2944
+ * The player is jumping, in the tunnel: the frame is the tunnel, the ship and two of the effect
2945
+ * types, and nothing else of the system, whose nebulae, fields and dust it leaves alone.
2946
+ */
2947
+ jump?: SpaceJump;
2948
+ }
2949
+ /** `[Cockpit] mesh`, placed where `FirstPersonCameraUpdate` has it, under the ship's turn. */
2950
+ interface SpaceCockpit {
2951
+ model: SceneModel;
2952
+ /** Its world bounding sphere (`ClientMeshHolderGetBoundingSphere`), which it's lit by. */
2953
+ sphere: {
2954
+ center: Vector3;
2955
+ radius: number;
2956
+ };
2957
+ /** The ship's nebula, as `Slot18c` gives it, and how deep. Its ambient and fog light the mesh. */
2958
+ within?: {
2959
+ state: NebulaState;
2960
+ factor: number;
2961
+ };
2962
+ }
2963
+ /** What the cockpit's pass hardcodes, and a mod can patch (`0x5c8914`, `0x5c8918`). */
2964
+ interface CockpitLimits {
2965
+ near: number;
2966
+ far: number;
2967
+ /** Its mesh keeps to the back of the depth range, behind all the HUD may draw. */
2968
+ depthRange: readonly [number, number];
2969
+ /** The rest, which the pass leaves set for the HUD and the scene's end. */
2970
+ afterDepthRange: readonly [number, number];
2971
+ }
2972
+ export declare const RETAIL_COCKPIT_LIMITS: Readonly<CockpitLimits>;
2973
+ /**
2974
+ * The player's jump while its ship is in states 2 and 3, the tunnel (`SpaceObjDrawPass`, `0x51c235`).
2975
+ * The host steps the tunnel first (`FJumpShipEffect::Update`), which places the ship.
2976
+ */
2977
+ interface SpaceJump {
2978
+ /** The player's ship, where the tunnel beamed it. */
2979
+ ship: SpaceObject;
2980
+ /** The gate's descriptor's `jump_tunnel`, when found. */
2981
+ tunnel?: GateTunnel;
2982
+ /** The descriptor's `jump_ambient`, as written, 0 to 255: the ship's ambient. */
2983
+ ambient: Vector3;
2984
+ /** Its `jump_background_color`, as written, 0 to 255: the clear. */
2985
+ background: Vector3;
2986
+ }
2987
+ /** A `[Background]` key: `basic_stars`, `complex_stars` or `nebulae`. */
2988
+ type SkyLayer = 'basic' | 'complex' | 'nebulae';
2989
+ /**
2990
+ * The layers that draw, in order, at the `star_sphere` option: never both kinds of stars
2991
+ * (`StarSphereApplyDetail`, `0x552cd0`; `StarSphereRender`, `0x552ea0`). Strictly above each step.
2992
+ */
2993
+ export declare const skyLayers: (detail: number) => SkyLayer[];
2994
+ /** What retail hardcodes, and a mod can patch. Distances are in world units. */
2995
+ interface SpaceLimits extends FrameLimits, NebulaLimits {
2996
+ /** A system with no `space_farclip` (`FUN_00410130`). */
2997
+ farClip: number;
2998
+ /** The least far plane a `space_farclip` can set (`FUN_00410150`). */
2999
+ leastFarClip: number;
3000
+ /** How far short of the far plane the draw range ends (`FUN_00410150`). */
3001
+ rangeMargin: number;
3002
+ /** The far list's plane (`DAT_00610534`). */
3003
+ farListPlane: number;
3004
+ /** How far from the camera a small solar is looked for (`DAT_00613ec8`). */
3005
+ solarQueryRange: number;
3006
+ /** Nearer than this, an object leaves its field's list. Retail holds its square (`0x5dc35c`). */
3007
+ nearDistance: number;
3008
+ /** How deep in another nebula an object can be and still draw (`0x5cbf58`). */
3009
+ otherFieldDepth: number;
3010
+ /** How far short of the draw range a ring piece goes to the far list (`0x5d3d24`). */
3011
+ ringFarMargin: number;
3012
+ /** The most the effect walk steps an effect by: `SpaceEffectsDrawFrame` (`0x4f75eb`). */
3013
+ effectMaxStep: number;
3014
+ }
3015
+ export declare const RETAIL_SPACE_LIMITS: Readonly<SpaceLimits>;
3016
+ export declare const collectSpaceFrame: ({ camera: given, aspect, ambient, lights, objects, sky, farClip, limits, nebulae, effects, background, time, step, cameraStep, effectView, viewport, shine, sunEffect, rayHits, player, rings, cut, lane, asteroids, dust, dustZones, dustOptions, beams, beamTexture, tractors, lineTexture, tractorRandom, tractorLimits, firstPerson, hud, cockpit, cockpitLimits, effectsOff, jump }: SpaceScene) => SpaceFrame;
3017
+ /**
3018
+ * The overlay for a frame in a field, or nothing: none at a depth of 0 or less, nor past the frame
3019
+ * that reached 1. The quad runs from the viewport's corner to its last pixel, `0` to `width - 1`
3020
+ * (`GetViewport`'s rectangle ends on it), under `Win3DSetOrtho2DForCamera`, so retail leaves the
3021
+ * last column and row bare. Without a viewport it covers the whole view. Untextured (`0xde1` off),
3022
+ * blended, with no depth test or writes and no culling; `ALPHAREF` is the frame's, 0 as captured.
3023
+ */
3024
+ export declare const fogOverlayDraw: ({ nebula, depth }: NebulaState, view: Matrix4, viewport?: {
3025
+ width: number;
3026
+ height: number;
3027
+ }) => Draw | undefined;
3028
+ /** A `[FlashlightSet]` or `[FlashlightLine]` from a room's INI. The host reads it. */
3029
+ interface FlashlightSet {
3030
+ /** `color`, or `icolor` over 255. */
3031
+ color: Vector3;
3032
+ /** The glow's half-size; the bulb's is a quarter of it. */
3033
+ scale: number;
3034
+ /** Seconds between one light's blink and the next's. */
3035
+ gap: number;
3036
+ /** How long each blink lasts. */
3037
+ blink: number;
3038
+ /** The pause after the last light's, before the first's again. */
3039
+ endpause: number;
3040
+ }
3041
+ /** One light: its colours, sizes and cone, its blink, and where the host has put it. */
3042
+ export declare class FlashLight {
3043
+ readonly color: Vector3;
3044
+ readonly minColor: Vector3;
3045
+ readonly bulbSize: number;
3046
+ readonly glowSize: number;
3047
+ /** `flare_cone`'s cosines: the glow's gone below `inner`, and whole from `outer`. */
3048
+ readonly cone: {
3049
+ inner: number;
3050
+ outer: number;
3051
+ };
3052
+ readonly blink: LightBlink;
3053
+ /** Its hardpoint's frame in the world, which the host keeps up to date (`FUN_00558470`). */
3054
+ world: Matrix4;
3055
+ /** The bulb's colour, `min_color` to `color` with the blink, and the glow's, black to `color`. */
3056
+ bulbColor: Vector3;
3057
+ glowColor: Vector3;
3058
+ constructor(color: Vector3, minColor: Vector3, bulbSize: number, glowSize: number, cone: {
3059
+ inner: number;
3060
+ outer: number;
3061
+ }, blink: LightBlink);
3062
+ /**
3063
+ * A `[Light]` on a prop or ship (`FUN_00557700`), `always_on` and with a hardpoint. Its glow is
3064
+ * `color`'s, not `glow_color`'s, and its period and phase are drawn from `rand()`.
3065
+ */
3066
+ static fromLight(light: FlareLight, random?: Random): FlashLight;
3067
+ /** `RoomControlLoadFlashlights` (`FUN_0044a3a0`): one light per frame, staggered by `gap`. */
3068
+ static ofSet({ color, scale, gap, blink, endpause }: FlashlightSet, frames: readonly Matrix4[]): FlashLight[];
3069
+ /** `FUN_00557860`: the blink advanced by `step` seconds, and both colours made from it. */
3070
+ update(step: number): void;
3071
+ }
3072
+ /** `FUN_005579b0`'s cone term: 1 inside `outer`, 0 past `inner`, a ramp between. */
3073
+ export declare const coneLevel: (light: FlashLight, eye: Vector3) => number;
3074
+ /** `fx/efx.txm`'s `shine` and `bulb`. */
3075
+ interface FlashlightTextures {
3076
+ shine: Texture;
3077
+ bulb: Texture;
3078
+ }
3079
+ /** At most this many of a prop's or ship's lights draw (`FUN_00558580`); a room's own aren't held to it. */
3080
+ export declare const FLASHLIGHTS_PER_SET = 32;
3081
+ /**
3082
+ * `DrawFlashlightFlares`: every glow, then every bulb, each a quad facing the camera, added, `LEQUAL`
3083
+ * without writing depth, no culling and no fog. A black one is left out.
3084
+ */
3085
+ export declare const flashlightDraws: (lights: readonly FlashLight[], camera: Camera, { shine, bulb }: FlashlightTextures) => Draw[];
3086
+ interface RoomShadow {
3087
+ /**
3088
+ * A ship's or a type-5 prop's (`DrawFadingShadows`, slot 11): it fades and grows with height, and
3089
+ * takes its box. Else a character's (`DrawFixedShadows`, slot 12): a fixed size and darkness, under
3090
+ * a point just behind it.
3091
+ */
3092
+ fading: boolean;
3093
+ /** `Float1C`, the floor's height: where the quad lies. A prop's is 0. */
3094
+ floor: number;
3095
+ /** The instance's world, whose position it's under and whose Z axis turns it. */
3096
+ world: Matrix4;
3097
+ /**
3098
+ * Fading only: the box `IRenderer::GetBounds` gives at 1.0 for the instance's archetype. Which part
3099
+ * of a compound answers is unread. Without it, as for an instance with no archetype, the
3100
+ * character's small box.
3101
+ */
3102
+ bounds?: {
3103
+ min: Vector3;
3104
+ max: Vector3;
3105
+ };
3106
+ }
3107
+ /** What `DrawShadowLike` hardcodes (`DAT_06704064..90`), and a mod can patch. */
3108
+ interface ShadowLimits {
3109
+ /** The small box: half its width in X and Z, and what a fixed shadow lifts its quad by. */
3110
+ extentX: number;
3111
+ extentZ: number;
3112
+ lift: number;
3113
+ /** How far along its Z axis a fixed shadow sits: behind, as it's negative. */
3114
+ offset: number;
3115
+ /** Above this height over the floor, no shadow; heights are fractions of it. */
3116
+ fadeRange: number;
3117
+ /** A fading quad's scale: `fadeQuad × fade² + scaleBias`. */
3118
+ fadeQuad: number;
3119
+ scaleBias: number;
3120
+ /** A fading shadow's alpha is this less the fade, a fixed one's `fixedAlpha`; the colour's is its square. */
3121
+ fadeAlpha: number;
3122
+ fixedAlpha: number;
3123
+ }
3124
+ export declare const RETAIL_SHADOW_LIMITS: Readonly<ShadowLimits>;
3125
+ /** One quad: its corners, each `[x, y, z, u, v]`, and the vertex alpha, of 255. */
3126
+ interface ShadowQuad {
3127
+ corners: [number, number, number, number, number][];
3128
+ alpha: number;
3129
+ }
3130
+ /**
3131
+ * One shadow's quad, and the scale it leaves. `DAT_06704078` is a global that only a fading shadow
3132
+ * sets, so a fixed one takes whatever the last fading one left, in this frame or an earlier one.
3133
+ */
3134
+ export declare const shadowQuad: ({ fading, floor, world, bounds }: RoomShadow, scale: number, limits?: ShadowLimits) => {
3135
+ quad?: ShadowQuad;
3136
+ scale: number;
3137
+ };
3138
+ /**
3139
+ * The quad as `DrawShadowLike` states it: lighting off, the texture times white at the quad's alpha,
3140
+ * blended, `LEQUAL`, no culling, no depth written, `ZBIAS` 4. The alpha test and fog are the device's
3141
+ * as the last object left them; the fog is passed, the test is taken as off: uncaptured.
3142
+ */
3143
+ export declare const shadowDraw: ({ corners, alpha }: ShadowQuad, texture: Texture, fog?: Fog) => Draw;
3144
+ /** A THN `LIGHT` that's on, in world space. Its type is its shape: `SceneLight`'s. */
3145
+ interface RoomLight extends SceneLight {
3146
+ /** `LtGrp`: it lights only objects of its group. */
3147
+ group: number;
3148
+ }
3149
+ interface RoomObject {
3150
+ /**
3151
+ * Its `render_type` (`ThornGetRenderType`) is the model's `sky`: 1 a starsphere, 2 a backdrop
3152
+ * (`FUN_0044af60`). A character's is always a model.
3153
+ */
3154
+ model: SceneModel;
3155
+ /** The engine instance's position, which its lights are picked from. */
3156
+ position: Vector3;
3157
+ /** `SrtGrp`, a signed byte: lower layers draw first. 0 when absent. */
3158
+ layer?: number;
3159
+ /** `LtGrp`. 0 when absent, the `FRoomObject` default. */
3160
+ lightGroup?: number;
3161
+ /** Slot 2. Drawn all the same when false, under black ambient (`FUN_0044ae80`). True when absent. */
3162
+ visible?: boolean;
3163
+ /** Picks lights. Unlit, it has none, and the ambient alone. True when absent. */
3164
+ lit?: boolean;
3165
+ /** The room's fog reaches it. True when absent. */
3166
+ fog?: boolean;
3167
+ /**
3168
+ * The `SCENE` entity's `ambient` it takes in place of the room's, as written: 0 to 255. A
3169
+ * compound gets one only through its scene (`FThornCompound::SetScene`).
3170
+ */
3171
+ ambient?: Vector3;
3172
+ /** Its own effects, a ship's flames, after every model, in its layer's turn. */
3173
+ effects?: readonly SceneEffect[];
3174
+ /** The shadow it casts, drawn just before it: a character's, a ship's with one, a type-5 prop's. */
3175
+ shadow?: RoomShadow;
3176
+ /**
3177
+ * Its `always_on` `[Light]`s, in its translucent pass (slot 8), the first 32: a prop's with its
3178
+ * fourth THN flag set (`FObjectProp::DrawTranslucent`), a ship's (`FObjectShip::DrawTranslucent`).
3179
+ * The host steps and places them.
3180
+ */
3181
+ flashlights?: readonly FlashLight[];
3182
+ }
3183
+ interface RoomShadows {
3184
+ /** `efxshadow`, from `fx/efx.txm`. */
3185
+ texture: Texture;
3186
+ /** What the last fading shadow left (`DAT_06704078`): the last frame's `shadowScale`. 1 at first. */
3187
+ scale?: number;
3188
+ /** Over `RETAIL_SHADOW_LIMITS`, for a mod that patches them. */
3189
+ limits?: Partial<ShadowLimits>;
3190
+ }
3191
+ /** A room's frame, and what its shadows leave for the next. */
3192
+ interface RoomFrame extends Frame {
3193
+ shadowScale: number;
3194
+ }
3195
+ interface RoomScene {
3196
+ camera: Camera;
3197
+ aspect: number;
3198
+ /**
3199
+ * In pixels: a part covering under one isn't drawn (`FRoomCamera::ClassifySphere`'s rank 2). Every
3200
+ * part is sized as if the viewport were infinite when absent.
3201
+ */
3202
+ viewport?: Viewport;
3203
+ /** The `SCENE` entity's `ambient`, as written: 0 to 255 (`RoomData`, `+0xd8`). White when absent. */
3204
+ ambient?: Vector3;
3205
+ /**
3206
+ * Every THN light that's on, in the order the script registered them (`DAT_0066d130`). A room's own
3207
+ * list (`+0xe4`) is never filled in retail, nor is its single-light mode ever set (`+0x4fc`).
3208
+ */
3209
+ lights: readonly RoomLight[];
3210
+ /** In the order they were made, which is the order within a layer (`DAT_00679a10`). */
3211
+ objects: readonly RoomObject[];
3212
+ /**
3213
+ * The `SCENE`'s fog, when it's on and the effects and nebula options are (`GfFogIsActive`). Its
3214
+ * `fogtable` N asks D3D for vertex fog, which here is per pixel all the same: uncaptured.
3215
+ */
3216
+ fog?: Fog;
3217
+ /**
3218
+ * With the shadows option, `shadow_glare` and the room's `ShadowLight` all on (`FUN_0044b370`). No
3219
+ * shadows when absent.
3220
+ */
3221
+ shadows?: RoomShadows;
3222
+ /**
3223
+ * The room's own, from its `[FlashlightSet]`s and `[FlashlightLine]`s, after every object's, all
3224
+ * of them, with the effects and flashlights options on. The host steps them (`FRoomRuntime::Update`).
3225
+ */
3226
+ flashlights?: readonly FlashLight[];
3227
+ /** `shine` and `bulb`, which any flashlight needs to draw. */
3228
+ flashlightTextures?: FlashlightTextures;
3229
+ /** The scripts' `PSYS`es, sequence by sequence, after every object's (`FUN_00456740`). */
3230
+ effects?: readonly SceneEffect[];
3231
+ background?: Vector3;
3232
+ time?: number;
3233
+ limits?: Partial<FrameLimits>;
3234
+ }
3235
+ /** `FUN_00557170`'s cap, through `GfLightsApplyNearest` (`FUN_005572b0`). */
3236
+ export declare const ROOM_LIGHTS_PER_OBJECT = 4;
3237
+ /**
3238
+ * `GfLightScore` (`FUN_005586f0`): 3 for a directional light, else 2 less its distance squared over
3239
+ * its range squared, a spot outside its half cone taking the angle's cosine for the 2. Undefined for
3240
+ * no range at all, which isn't picked.
3241
+ */
3242
+ export declare const lightScore: (light: SceneLight, position: Vector3) => number | undefined;
3243
+ /**
3244
+ * `GfLightsSelectNearest` (`FUN_00557170`) and `EngineLightApplyList`: the group's lights, highest
3245
+ * score first, a later one before an equal one, cut to four, and then any under a byte's worth left off.
3246
+ */
3247
+ export declare const pickRoomLights: (lights: readonly RoomLight[], position: Vector3, group: number) => SceneLight[];
3248
+ /** What one object is lit by: `FUN_0044ae80`, with the lighting option on. */
3249
+ export declare const lightRoomObject: ({ lights, ambient, fog }: Pick<RoomScene, "lights" | "ambient" | "fog">, object: RoomObject) => {
3250
+ lights: SceneLight[];
3251
+ ambient: Vector3;
3252
+ fog?: Fog;
3253
+ };
3254
+ export declare const collectRoomFrame: (scene: RoomScene) => RoomFrame;
3255
+ /**
3256
+ * The sheets for a nebula, or nothing: none once the camera's depth reaches 1, nor without a fill
3257
+ * shape, nor when both face the camera. Sheet A stands along the zone's longer of x and z, sheet B
3258
+ * along its y, each `size` × 1.15 from the centre, and across it to the zone's surface × 1.15.
3259
+ * A sphere written with one value has no y, so its sheet B has no height, and draws.
3260
+ *
3261
+ * Unlit, unculled, alpha-blended, with no depth test or writes (`DrawFogShell`), under the 250000 far
3262
+ * plane its callers set; `projection` is that. Fog and `ALPHAREF` are what the frame left: fog is off
3263
+ * wherever retail calls it, as the far list's nodes and `DrawBoltsPass` leave it, and the alpha test
3264
+ * is taken as `GREATER 0`. **Uncaptured.**
3265
+ */
3266
+ export declare const fillSheetsDraw: ({ nebula, depth }: NebulaState, eye: Vector3, projection: Matrix4) => Draw | undefined;
3267
+ /** One effect where it is this frame. */
3268
+ interface EffectPlacement {
3269
+ /** Names its instance across frames: the first frame a key is placed starts it. */
3270
+ key: unknown;
3271
+ /** Starts its `[Effect]` afresh: the instance, its textures and its type. */
3272
+ start: () => DynamicEffect;
3273
+ /** Its world transform, which it steps at and is tested by. */
3274
+ world: Matrix4;
3275
+ /** `SetIntensity`'s value, its `sparam`. 0 when absent. */
3276
+ sparam?: number;
3277
+ watched?: PlacedEffect['watched'];
3278
+ generic?: boolean;
3279
+ /**
3280
+ * Its owner let go (`FParticleEffect::Kill`, `0x4f84d0`): from the first frame that says so its
3281
+ * emitters stop and it has at most a second left. Where it goes then is still the host's.
3282
+ */
3283
+ released?: boolean;
3284
+ }
3285
+ export declare class PlacedEffects {
3286
+ #private;
3287
+ /**
3288
+ * This frame's effects for `SpaceScene.effects`, newest first. A key not placed again is dropped,
3289
+ * and one that has ended stays out for as long as it's placed: the game destroys an effect it no
3290
+ * longer sees, and nothing starts it again (`0x4f789f`).
3291
+ */
3292
+ place(placements: readonly EffectPlacement[], { effectTypes }?: EffectViewOptions): SpaceEffect[];
3293
+ /**
3294
+ * After the frame, each placed effect as the walk leaves it: unseen and not `EXIST_OFFSCREEN`, it's
3295
+ * destroyed. Otherwise its run time counts down and it ends at 0, or once its effect has; then it
3296
+ * steps by `scene.step`, held to `effectMaxStep`, at its view's LOD, unless it's unseen and its type
3297
+ * isn't `UPDATE_OFFSCREEN` (`0x4f8ae9`). Not walked (`hold`), nothing happens to it.
3298
+ */
3299
+ step(scene: SpaceScene, { effectViews }: Pick<SpaceFrame, 'effectViews'>): void;
3300
+ /** Every effect held, released: for a host done with them all. */
3301
+ release(): void;
3302
+ /** Whether a key's effect has ended, or `undefined` where it isn't held. */
3303
+ ended(key: unknown): boolean | undefined;
3304
+ }
3305
+ /**
3306
+ * Every part's world transform: `W_child = W_parent · L_child`, the root at `model`. A joint with no
3307
+ * state stands at rest. Parts are keyed by identity.
3308
+ */
3309
+ export declare const partTransforms: <T>(root: CompoundNode<T>, model: Matrix4, stateOf?: (part: CompoundNode<T>) => JointState | undefined) => Map<CompoundNode<T>, Matrix4>;
3310
+ /**
3311
+ * The joint that hangs `child`'s hardpoint on `parent`'s, as attached equipment is connected
3312
+ * (`ConnectAttachedHardpoints`, `0x62940c0`, then `HardpointConnect`, `EngBase` `IHardpoint` slot 6).
3313
+ * Two revolute hardpoints make a revolute joint limited by the narrower range. Otherwise the child's
3314
+ * type decides: a fixed one lands its point and frame on the parent's, a revolute one turns about its
3315
+ * own axis within its own limits. A revolute joint's orientation is the parent's times the child's,
3316
+ * untransposed as retail has it; no reference attaches a child whose frame isn't the identity.
3317
+ */
3318
+ export declare const attachJoint: (parent: Hardpoint, child: Hardpoint) => Joint;
3319
+ /** A `bodyparts.ini` group's table: `switch = percent, distance` rows, the percent descending. */
3320
+ interface SwitchTable {
3321
+ rows: readonly (readonly [percent: number, distance: number])[];
3322
+ /** Degrees, the whole angle. `RETAIL_SWITCH_FOVX` when the section writes none. */
3323
+ fovx?: number;
3324
+ }
3325
+ /** What a `[DetailSwitchTable]` with no `fovx` reads. A mod patching it passes its own as `fovx`. */
3326
+ export declare const RETAIL_SWITCH_FOVX = 40;
3327
+ /** `DetailSwitchTable::get_switch_distance`: linear between rows, clamped at both ends. */
3328
+ export declare const switchDistance: ({ rows }: SwitchTable, percent: number) => number;
3329
+ /** `adjust_distance`: a narrow lens counts as closer, against the table's own. `fovh` is half the angle. */
3330
+ export declare const adjustedDistance: ({ fovx }: SwitchTable, distance: number, fovh: number) => number;
3331
+ /** The first level whose distance reaches this one, or none past the table's far cutoff. */
3332
+ export declare const costumeLevel: (levels: readonly Level[], table: SwitchTable, distance: number, fovh: number) => number | undefined;
3333
+ /** The game's table holds this many, and nothing checks it (`0x65f3898`). Retail's largest is 86. */
3334
+ export declare const MAX_BONES = 150;
3335
+ interface SkinMesh {
3336
+ mesh: VMeshData;
3337
+ reference: VMeshRef;
3338
+ /** Four bone indices, then four weights, per vertex. */
3339
+ influences: Float32Array;
3340
+ }
3341
+ /** The same object for the same level, so a renderer's buffers are shared between characters. */
3342
+ export declare const skinMesh: (level: Level, bones: number) => SkinMesh;
3343
+ /**
3344
+ * `K = W(root)⁻¹ · W(bone) · B`, `B` being `Bone to root` as stored (`Skin`). A slot with no world keeps
3345
+ * the identity, as one past the bound cursor does in retail.
3346
+ */
3347
+ export declare const boneTable: (bones: readonly Bone[], worlds: readonly (Matrix4 | undefined)[], root: Matrix4) => Float32Array;
3348
+ interface SkinOptions {
3349
+ /** Pinned, clamped to what a part has. The top one when there's neither this nor `lod`. */
3350
+ level?: number;
3351
+ /** Each part's level as the game chooses it, `tables` in part order. */
3352
+ lod?: {
3353
+ camera: Camera;
3354
+ tables: readonly SwitchTable[];
3355
+ };
3356
+ /**
3357
+ * A level at or past this draws rigid, at bind. A costume's default is the builder's, by slot:
3358
+ * the body none, a head or hand 2 (\`0x443b60\`). It goes by position, the first part taken as the
3359
+ * body, so a costume with no body passes its own, or its head skins every level.
3360
+ */
3361
+ skinnedLevels?: readonly number[];
3362
+ jointState?: (bone: CompoundNode<Bone>) => JointState | undefined;
3363
+ /**
3364
+ * The skin as modelled, ignoring the joints. `chain` is the default and what the game draws: its
3365
+ * joints' rest plus `jointState`. On a body that rest is another pose entirely, which it only ever
3366
+ * shows under a script, so a viewer with none playing wants `bind` (library `DEFORMABLE.md`).
3367
+ */
3368
+ pose?: 'chain' | 'bind';
3369
+ }
3370
+ interface SkinPose {
3371
+ /** Each bone's world, by its place in the table. A slot with none keeps the identity. */
3372
+ worlds: readonly (Matrix4 | undefined)[];
3373
+ /** The part's root bone's world, which the mesh is drawn with. */
3374
+ root: Matrix4;
3375
+ /** Each bone's parent, by the same place, which an eye's offset is read against. */
3376
+ parents?: readonly (number | undefined)[];
3377
+ /** `Flag70` off: every bone the identity (`0x65f6cc6`), so the level draws at bind. */
3378
+ rigid?: boolean;
3379
+ }
3380
+ /** One level of a part, for a host that poses and joins its own bones. `placeCostume` is the other way in. */
3381
+ export declare const poseSkin: (level: Level, bones: readonly Bone[], { worlds, root, parents, rigid }: SkinPose) => PlacedPart;
3382
+ /**
3383
+ * Every bone's world in a costume, by part and by its place in the part's table; nothing for a part
3384
+ * with no root. The skeletons join by shared hardpoint name (`0x65f3540`, library `COSTUME.md`):
3385
+ * walking every bone, the first with a name holds it, and a later one with no parent links to it.
3386
+ * That's a part's root, placing the part, or a detached bone, placing one seam.
3387
+ */
3388
+ export declare const costumeWorlds: (models: readonly DeformableModel[], transform: Matrix4, { jointState, pose }?: Pick<SkinOptions, "jointState" | "pose">) => (Matrix4[] | undefined)[];
3389
+ /**
3390
+ * A character's parts, body first, each drawn with its root's world and skinned relative to it, its
3391
+ * bones joined as {@link costumeWorlds} joins them. A part with no root or level is skipped.
3392
+ */
3393
+ export declare const placeCostume: (models: readonly DeformableModel[], transform: Matrix4, { level, lod, skinnedLevels, jointState, pose }?: SkinOptions) => PlacedPart[];
3394
+ /** One model, as a costume of one part. */
3395
+ export declare const placeSkin: (model: DeformableModel, transform: Matrix4, options?: SkinOptions) => PlacedPart | undefined;
3396
+ interface SphereOptions {
3397
+ /** Decides the level and which faces draw. */
3398
+ camera: Camera;
3399
+ /** Of the viewport, in pixels: the level bounds the sphere's error on screen. */
3400
+ width: number;
3401
+ /** Pins a level, for a host choosing its own density. Faces still cull. */
3402
+ level?: number;
3403
+ /** Finds the seventh side's `Scale`, which its shell culls by. 1 if absent. */
3404
+ material?: (id: number) => Material | undefined;
3405
+ /** As on `SceneModel`, for that side's type. */
3406
+ materialMap?: readonly MaterialMapRule[];
3407
+ /** Over `RETAIL_SPHERE_LEVELS`, for a mod that patches them. */
3408
+ levels?: Partial<SphereLevels>;
3409
+ }
3410
+ /** `FSphereAnim::Init`'s bounds on a sphere's level, and the bias on its pixel size. */
3411
+ interface SphereLevels {
3412
+ minimum: number;
3413
+ maximum: number;
3414
+ bias: number;
3415
+ }
3416
+ export declare const RETAIL_SPHERE_LEVELS: Readonly<SphereLevels>;
3417
+ /**
3418
+ * `SolarSpheresApplyDetail` (`0x5488a0`): the maximum, 7 to 10, and the bias, 3 to 1, the
3419
+ * `level_of_detail` option gives a solar's sphere. Retail applies it only as the option changes, to the
3420
+ * solars that exist then (`SetPerfOptionValue`): at startup none do, so a system's planets keep
3421
+ * `RETAIL_SPHERE_LEVELS` until the player moves the slider in it, and lose it when it reloads.
3422
+ */
3423
+ export declare const solarSphereLevels: (detail: number) => Pick<SphereLevels, "maximum" | "bias">;
3424
+ /**
3425
+ * `UpdateLevel` (`0x6c2c430`), from `metric`, the world size of a pixel at the sphere times the bias.
3426
+ * Out of `acos`'s range, or at no distance, x87's `ftol` gives `INT_MIN`, so the lowest level.
3427
+ */
3428
+ export declare const sphereLevel: (radius: number, metric: number, { minimum, maximum }?: Pick<SphereLevels, "minimum" | "maximum">) => number;
3429
+ /**
3430
+ * A sphere's visible faces, as parts, then its shell's, which is the same patch under the seventh
3431
+ * material and culls by its own larger radius. Materials repeat by `Sides`, a retail sun naming one.
3432
+ */
3433
+ export declare const placeSphere: ({ radius, sides }: Sphere, world: Matrix4, { camera, width, level, material, materialMap, levels }: SphereOptions) => PlacedPart[];
3434
+ /** Fixed attribute locations, so every program fits every mesh. */
3435
+ export declare const ATTRIBUTE_LOCATIONS: {
3436
+ readonly position: 0;
3437
+ readonly normal: 1;
3438
+ readonly diffuse: 2;
3439
+ readonly uv0: 3;
3440
+ readonly uv1: 4;
3441
+ readonly boneIndex: 5;
3442
+ readonly boneWeight: 6;
3443
+ };
3444
+ type AttributeName = keyof typeof ATTRIBUTE_LOCATIONS;
3445
+ interface VertexAttribute {
3446
+ name: AttributeName;
3447
+ size: number;
3448
+ /** `D3DCOLOR` bytes, B G R A: a shader reading `diffuse` wants `.bgra`. */
3449
+ normalizedBytes: boolean;
3450
+ offset: number;
3451
+ }
3452
+ interface VertexLayout {
3453
+ stride: number;
3454
+ attributes: VertexAttribute[];
3455
+ }
3456
+ /** The FVF's layout: position, normal, point size, diffuse, specular, then UV sets. */
3457
+ export declare const vertexLayout: (format: VertexFormat) => VertexLayout;
3458
+ interface GroupDraw {
3459
+ /** CRC of the material name. */
3460
+ materialId: number;
3461
+ /** Into the reference's index buffer. */
3462
+ byteOffset: number;
3463
+ count: number;
3464
+ }
3465
+ interface ReferencePlan {
3466
+ /** Absolute vertices, since WebGL2 has no base vertex parameter. */
3467
+ indices: Uint16Array;
3468
+ groups: GroupDraw[];
3469
+ }
3470
+ /**
3471
+ * A reference's indices made absolute, one buffer per reference. Retail keeps `ref.vertexStart` as
3472
+ * `BaseVertexIndex`, with `group.vertexStart` already added to its indices (`li_elite`, draw 7).
3473
+ */
3474
+ export declare const planReference: (data: VMeshData, reference: VMeshRef) => ReferencePlan;
3475
+ /** A curve over `[0, 1]`: what `IParamCurve::Evaluate` returns. */
3476
+ interface Curve {
3477
+ readonly type: string;
3478
+ evaluate(t: number): number;
3479
+ }
3480
+ /** A `param_curve` as thorn makes it, or nothing for a class DACOM doesn't have. */
3481
+ declare const createCurve: (curve: ParamCurve) => Curve | undefined;
3482
+ /** The timing a property animation's progress reads. */
3483
+ interface Timing {
3484
+ /** Milliseconds. */
3485
+ start: number;
3486
+ duration: number;
3487
+ curve?: Curve;
3488
+ /** `pcurve_period`, milliseconds; positive loops the curve. */
3489
+ period: number;
3490
+ }
3491
+ /**
3492
+ * `EvaluateProgress`: 1 for an instant event, else the elapsed fraction, through the curve when
3493
+ * there is one, which a positive period loops as a sawtooth. **Not clamped.**
3494
+ */
3495
+ declare const progress: ({ start, duration, curve, period }: Timing, now: number) => number;
3496
+ type Frame$1 = Matrix4;
3497
+ type Quaternion = Vector4;
3498
+ /** An entity's `spatialprops`: `orient`'s triples are `R`'s columns, which the reader has transposed. */
3499
+ declare const placement: (pos?: Vector3, orient?: Matrix3) => Frame$1;
3500
+ /** `[Rᵀ | −Rᵀ·t]`, as thorn builds every inverse it takes. */
3501
+ declare const inverse: (frame: Frame$1) => Frame$1;
3502
+ /** `R(q)` scaled by `2/|q|²`, so a non-unit `q` is tolerated: `x86math.dll` `0x6f723c0`. */
3503
+ declare const rotationOf: ({ x, y, z, w }: Quaternion) => Frame$1;
3504
+ /** `R`'s quaternion, by the trace or else its largest diagonal: `x86math.dll` `0x6f72290`. */
3505
+ declare const quaternionOf: (frame: Frame$1) => Quaternion;
3506
+ /**
3507
+ * `x86math.dll`'s `+0x58` (`0x6f724d0`), which every "slerp" in thorn is: a normalised lerp,
3508
+ * folded onto `a`'s hemisphere.
3509
+ */
3510
+ declare const nlerp: (a: Quaternion, b: Quaternion, t: number) => Quaternion;
3511
+ /**
3512
+ * `BuildAxisFrame` (`FUN_06f2e440`): the rotation turning the local `front` axis onto `d`, its `up`
3513
+ * leaning toward the scene's up, or within the pole toward `up × front` of the scene. The two axes
3514
+ * are written as columns and the third is their cross product. Retail's scene axes are always Y and
3515
+ * Z; `6` means those. `d` must not be zero.
3516
+ */
3517
+ declare const lookAt: (d: Vector3, front: number, up: number, sceneUp?: number, sceneFront?: number) => Frame$1;
3518
+ /**
3519
+ * `ApplyAxisRotFpu` (`0x6f46eef`): a turn of `degrees` about an axis, applied to `base` in scene axes
3520
+ * for X, Y and Z and in its own for the negative names, whose sign is **not** flipped.
3521
+ */
3522
+ declare const axisTurn: (base: Frame$1, axis: number, degrees: number) => Frame$1;
3523
+ interface Path {
3524
+ /** In the path's own axes. Held at the first and last key outside `(0, 1)`. */
3525
+ position(u: number): Vector3 | undefined;
3526
+ /** The unit Catmull-Rom tangent toward increasing `u`, lerped between samples; not held at the ends. */
3527
+ tangent(u: number): Vector3 | undefined;
3528
+ /** Squad between keys' orientations; a position-only path has none. */
3529
+ orientation(u: number): Quaternion | undefined;
3530
+ }
3531
+ /**
3532
+ * A path from its `pathprops`, or nothing where thorn's load fails: a `NULL` path, or fewer than two
3533
+ * keys (`0x6f358fb`), and every animation on it does nothing.
3534
+ */
3535
+ declare const createPath: (props: PathProps | undefined) => Path | undefined;
3536
+ /** The eight `audioprops`, in key order. Only `attenuation` and `pan` reach a sound (`0x457630`). */
3537
+ interface AudioState {
3538
+ /** Decibels. */
3539
+ attenuation: number;
3540
+ pan: number;
3541
+ dmin: number;
3542
+ dmax: number;
3543
+ /** Whole degrees. */
3544
+ ain: number;
3545
+ aout: number;
3546
+ atout: number;
3547
+ rmix: number;
3548
+ }
3549
+ /** An entity's defaults (`0x6f5e198`). */
3550
+ declare const DEFAULT_AUDIO: AudioState;
3551
+ /** A block over `base`, each key given a bit in key order. */
3552
+ declare const readAudio: (props: AudioProps, base: AudioState) => {
3553
+ audio: AudioState;
3554
+ mask: number;
3555
+ };
3556
+ /** `TickAudioPropAnim`: masked keys from `from` toward `to` by `u`, the angles truncated. */
3557
+ declare const blendAudio: (current: AudioState, from: AudioState, to: AudioState, mask: number, u: number) => AudioState;
3558
+ /** One sound a script is playing, for the host to play and to keep up to date. */
3559
+ interface SoundRecord {
3560
+ entity: string;
3561
+ /** `[Sound]`, or `VoiceProfile_*`, `BackgroundAmbient`, `BackgroundMusic`, `PlayerShipSound`. */
3562
+ template: string;
3563
+ /**
3564
+ * Milliseconds into the sound to play from: `start_time` plus how late in its tick it started
3565
+ * (`0x4574d0`). The game plays it from there.
3566
+ */
3567
+ offset: number;
3568
+ loop: boolean;
3569
+ /** A 3D source at the entity, `C` not applied; otherwise 2D, panned. */
3570
+ spatial: boolean;
3571
+ /** The playback's clock when it started, milliseconds. */
3572
+ started: number;
3573
+ }
3574
+ /**
3575
+ * What the game sets on a playing sound (`0x457630`): its volume in decibels, the `[Sound]`'s
3576
+ * attenuation plus the entity's and the room's ambience level `L`, each clamped to [−96, 0], and a 2D
3577
+ * sound's pan in DirectSound's hundredths, clamped to ±10,000.
3578
+ */
3579
+ declare const soundLevel: (archetype: number, { attenuation, pan }: AudioState, room?: number) => {
3580
+ volume: number;
3581
+ pan: number;
3582
+ };
3583
+ /** `kThornLightBit*`: which of a block's keys were given. */
3584
+ declare const LIGHT_BITS: {
3585
+ readonly color: 1;
3586
+ readonly direction: 2;
3587
+ readonly range: 4;
3588
+ readonly cutoff: 8;
3589
+ readonly on: 16;
3590
+ readonly infinite: 32;
3591
+ readonly atten: 64;
3592
+ readonly theta: 128;
3593
+ readonly type: 256;
3594
+ readonly specular: 512;
3595
+ readonly ambient: 1024;
3596
+ readonly diffuse: 2048;
3597
+ };
3598
+ /** A light block: the legacy keys and the `D3DLIGHT8` beside them. Angles in radians. */
3599
+ interface LightState {
3600
+ on: boolean;
3601
+ infinite: boolean;
3602
+ /** Whole numbers, 0 to 255, which reach nothing once there is a `type`. */
3603
+ color: Vector3;
3604
+ /** In the light's own axes. */
3605
+ direction: Vector3;
3606
+ /** `D3DLIGHTTYPE`: 1 point, 2 spot, 3 directional. */
3607
+ type: number;
3608
+ diffuse: Vector3;
3609
+ specular: Vector3;
3610
+ ambient: Vector3;
3611
+ range: number;
3612
+ atten: Vector3;
3613
+ theta: number;
3614
+ /** `cutoff`, as D3D's `Phi`. */
3615
+ phi: number;
3616
+ }
3617
+ /** `FUN_06f41840`: a white spot, range 1000, theta 144°, phi 180°, `Att2` 100 over range². */
3618
+ declare const DEFAULT_LIGHT: LightState;
3619
+ /** `LoadLightProps` (`FUN_06f27fe0`): the keys a block gives, over `base`, and their mask. */
3620
+ declare const readLight: (props: LightProps, base: LightState) => {
3621
+ light: LightState;
3622
+ mask: number;
3623
+ };
3624
+ /**
3625
+ * An entity's light: the record over the defaults. Only with no `type` does thorn derive the
3626
+ * `D3DLIGHT8` from the legacy keys (`0x6f29492`, `0x6f2eb20`): infinite directional, a negative
3627
+ * cutoff a point, else a spot; diffuse the colour over 255, `Att (1, 0, 4/range²)`, twice the range.
3628
+ * Every retail light has a `type`. That rule is the extension's reading, not traced here.
3629
+ */
3630
+ declare const lightOf: (props: LightProps | undefined) => LightState;
3631
+ /**
3632
+ * `EvalLightProps`: each masked field from `from` toward `to` by `u`, unclamped; the colour truncated
3633
+ * to whole numbers; `on`, `type` and `infinite` switch only once `u` reaches 1. The direction is
3634
+ * lerped and not renormalised.
3635
+ */
3636
+ declare const blendLight: (current: LightState, from: LightState, to: LightState, mask: number, u: number) => LightState;
3637
+ /**
3638
+ * The room's light for a THN light (`Thorn_Light`, `0x452fa0`, and `GameLight::set_d3d`, `0x411120`):
3639
+ * at thorn's position, `C` not applied, shining along its frame's turn of `direction`. A point's
3640
+ * cone is a whole sphere; a phi past π is π; no attenuation at all is 0.1 constant. Its type is the
3641
+ * one it was made with: a later change is ignored. `lt_grp` picks what it lights.
3642
+ */
3643
+ declare const roomLightOf: (light: LightState, madeAs: number, frame: Frame$1, group: number) => RoomLight;
3644
+ /** `GfFog`'s one global state, which every script's `SCENE` shares. Zeroed at room setup. */
3645
+ interface FogState {
3646
+ on: boolean;
3647
+ /** `fogtable` as passed: true writes `FOGTABLEMODE`, so `Y` is table fog and `N` vertex fog. */
3648
+ table: boolean;
3649
+ /** Whole numbers, 0 to 255. */
3650
+ color: Vector3;
3651
+ /** `D3DFOGMODE`: 0 none, 1 exp, 2 exp2, 3 linear. */
3652
+ mode: number;
3653
+ start: number;
3654
+ end: number;
3655
+ density: number;
3656
+ }
3657
+ declare const NO_FOG: FogState;
3658
+ /** A `fogprops` block's values and mask (`0x6f27c50`): on 1, table 2, colour 4, mode 8, then 0x10–0x40. */
3659
+ declare const readFog: (props: FogProps) => {
3660
+ fog: FogState;
3661
+ mask: number;
3662
+ };
3663
+ /** `TickFogPropAnim`: on, table and mode switch at `u ≥ 1`; the rest lerp, the colour truncated. */
3664
+ declare const blendFog: (current: FogState, from: FogState, to: FogState, mask: number, u: number) => FogState;
3665
+ /**
3666
+ * The room's fog (`GfFogApply`, `0x454250`): when on and the `fog` option is, in D3D's mode. Per pixel
3667
+ * here whichever of vertex and table fog the script asks for.
3668
+ */
3669
+ declare const roomFogOf: (fog: FogState, enabled?: boolean) => Fog | undefined;
3670
+ /** The flag word's bits (`ThornScene.h`). Bit 16 is `q_orient`'s, read by nothing here. */
3671
+ declare const FLAGS: {
3672
+ readonly LOOP: 8;
3673
+ readonly POSITION: 2;
3674
+ readonly ORIENTATION: 4;
3675
+ readonly LOOK_AT: 8;
3676
+ readonly ENTITY_RELATIVE: 32;
3677
+ readonly PARENT_CHILD: 64;
3678
+ readonly ORIENTATION_RELATIVE: 128;
3679
+ readonly USE_SCRIPT_DURATION: 1;
3680
+ readonly PATH_POSITION: 2;
3681
+ };
3682
+ /** What an event needs of the playback it runs in. */
3683
+ interface Stage {
3684
+ /** Thorn's frame for an entity: scene space, which the host keeps. */
3685
+ frame(entity: number): Frame$1;
3686
+ setPosition(entity: number, position: Vector3): void;
3687
+ setOrientation(entity: number, orientation: Frame$1): void;
3688
+ /** Whether it has an `ICompoundControl`: a `COMPOUND` or `DEFORMABLE`. */
3689
+ compound(entity: number): boolean;
3690
+ /** `ICompoundControl +0x24` for a `target_type` and `target_part`: nothing when it writes nothing. */
3691
+ part(entity: number, type: number, name: string): Frame$1 | undefined;
3692
+ path(entity: number): Path | undefined;
3693
+ /** `ICompoundControl +0x1c`/`+0x20`, which the host makes a parent link of. */
3694
+ connect(event: ThornEvent): void;
3695
+ disconnect(event: ThornEvent): void;
3696
+ /** A camera's or monitor's `{fovh, near, far, aspect}`, as thorn caches them. */
3697
+ cameraProps(entity: number): CameraProps;
3698
+ /** `ApplyCameraProps`: stored, and on from a camera to every monitor linked to it. */
3699
+ setCameraProps(entity: number, props: CameraProps): void;
3700
+ /** `SET_CAMERA`'s link: the monitor follows the camera from now on. */
3701
+ cut(monitor: number, camera: number): void;
3702
+ /** Whether the entity is a `CAMERA` (not a `MONITOR`). */
3703
+ camera(entity: number): boolean;
3704
+ /** A `LIGHT`'s block as its host holds it, or nothing for another entity. */
3705
+ light(entity: number): LightState | undefined;
3706
+ setLight(entity: number, light: LightState): void;
3707
+ /** The host's one fog state, `on` as in effect (`0x454480`). */
3708
+ fog(): FogState;
3709
+ /** `0x6f43140`: pushed by mask. */
3710
+ setFog(fog: FogState, mask: number): void;
3711
+ /** The entity's type. */
3712
+ kind(entity: number): string | undefined;
3713
+ /** `IPSysControl +0xc`: a new instance of its effect; false where the game made no host. */
3714
+ psysStart(entity: number): boolean;
3715
+ /** `+0x10`: the effect killed outright. */
3716
+ psysStop(entity: number): void;
3717
+ /** `+0x1c`: the host's cache of the last `sparam` set, 0 at first. */
3718
+ sparam(entity: number): number;
3719
+ /** `+0x18`: cached, and handed to the effect only while one runs. */
3720
+ setSparam(entity: number, value: number): void;
3721
+ /** The clock after the tick's step, which a starting event is handed (`ClockAfterDt_`). */
3722
+ readonly now: number;
3723
+ /** `ISoundControl +0xc`: plays the entity's sound, cutting off what it was playing. */
3724
+ soundStart(entity: number, record: {
3725
+ offset: number;
3726
+ loop: boolean;
3727
+ spatial: boolean;
3728
+ }): boolean;
3729
+ /** `+0x10`: cut dead. */
3730
+ soundStop(entity: number): void;
3731
+ /** `ICompoundControl +0xc`/`+0x10`: a script started on a compound or character, and stopped. */
3732
+ motionStart(entity: number, motion: MotionRecord): boolean;
3733
+ motionStop(entity: number, motion: MotionRecord): void;
3734
+ /** `IIKControl +0xc`/`+0x14`/`+0x10`: a character's aim, its target re-sent at every update. */
3735
+ aimStart(entity: number, aim: AimRecord): boolean;
3736
+ aimUpdate(entity: number, aim: AimRecord): void;
3737
+ aimStop(entity: number, aim: AimRecord): void;
3738
+ /** `ICompoundControl +0x28`/`+0x14`: a character's floor height. */
3739
+ floor(entity: number): number | undefined;
3740
+ setFloor(entity: number, height: number): void;
3741
+ /** A `SOUND`'s `audioprops`, or nothing for another entity. */
3742
+ audio(entity: number): AudioState | undefined;
3743
+ setAudio(entity: number, audio: AudioState): void;
3744
+ readonly sceneUp: number;
3745
+ readonly sceneFront: number;
3746
+ }
3747
+ /** A `START_MOTION` as thorn hands it to the host (`ApplyMotionDesc`, parser `0x6f25a9d`). */
3748
+ interface MotionRecord {
3749
+ readonly event: Event;
3750
+ animation: string;
3751
+ /** Seconds into each channel. */
3752
+ startTime: number;
3753
+ /** The blend-in, seconds; 0 replaces. */
3754
+ transTime: number;
3755
+ timeScale: number;
3756
+ /** An amplitude, not a mix weight. */
3757
+ weight: number;
3758
+ /** Radians, or −1 for the character's own yaw. Only parsed off a `COMPOUND`. */
3759
+ heading: number;
3760
+ /** Only parsed on a `COMPOUND`: its object map's translation scale. */
3761
+ transScale: number;
3762
+ lockedBone: string | undefined;
3763
+ /** 1 backwards, 2 loop, 4 bounce, 128 keep what is beneath once blended in. */
3764
+ eventFlags: number;
3765
+ }
3766
+ /** A `START_IK` as thorn hands it on (`BuildIkPose`): the target and the look-at toward it, in scene space. */
3767
+ interface AimRecord {
3768
+ readonly event: Event;
3769
+ endEffector: string;
3770
+ countToRoot: number;
3771
+ damping: number;
3772
+ pointAt: boolean;
3773
+ moveTo: boolean;
3774
+ /** Seconds. */
3775
+ transitionDuration: number;
3776
+ eventFlags: number;
3777
+ target: Vector3;
3778
+ /** Turns the joint's `front` onto the target, its `up` toward the scene's. */
3779
+ rotation: Frame$1;
3780
+ }
3781
+ /** Thorn's four camera slots: `fovh` (half the horizontal angle, degrees), planes, and `hvaspect`. */
3782
+ type CameraProps = readonly [fovh: number, near: number, far: number, aspect: number];
3783
+ /** One event, from load to finish. */
3784
+ interface ThornEvent {
3785
+ /** The action it runs as: a `START_PATH_ANIMATION` on an entity becomes an `ATTACH_ENTITY`. */
3786
+ readonly action: EventTypeName;
3787
+ readonly type: number;
3788
+ readonly source: Event;
3789
+ /** Milliseconds. */
3790
+ readonly start: number;
3791
+ readonly duration: number;
3792
+ readonly flags: number;
3793
+ readonly primary: number;
3794
+ /** −1 for none. */
3795
+ readonly secondary: number;
3796
+ /** Set at start: −1 never ends. */
3797
+ end: number;
3798
+ /** True to join the active list. */
3799
+ begin(stage: Stage): boolean;
3800
+ /** One update at `now`; the engine then asks whether it still runs. */
3801
+ update(stage: Stage, now: number): void;
3802
+ /** Its end, or every running event's at a stop: `FThornEvent::Stop`. */
3803
+ finish?(stage: Stage): void;
3804
+ }
3805
+ /**
3806
+ * Seconds to whole milliseconds: `time · 1000` rounded to the x87's 24-bit precision, which D3D8
3807
+ * leaves it at without `FPU_PRESERVE`, then chopped (`LuaMillisecondsToRetailInt`).
3808
+ */
3809
+ declare const milliseconds: (seconds: number) => number;
3810
+ /**
3811
+ * The flag word: bit 1 when `duration` is nil (`0x6f25760`), then a `flags` key replaces it whole
3812
+ * (`0x6f257c9`). `STREAM` and `FOG_PROPS_REMOVED` have no measured bit, so count for nothing.
3813
+ */
3814
+ declare const flagWord: (event: Event) => number;
3815
+ /** The actions this library runs. The rest are scheduled and timed, and do nothing yet. */
3816
+ declare const RUN: ReadonlySet<EventTypeName>;
3817
+ /**
3818
+ * Every event the factory accepts, in script order: a first target that names an entity, and a
3819
+ * second that does when given or required. Entity ids are the script's order, first name winning.
3820
+ */
3821
+ declare const eventsOf: (script: Script) => ThornEvent[];
3822
+ /** One `rtcslider.ini` line: `set = name, t` on at `t` and above, `set_less` below `t`. */
3823
+ interface RtcSetting {
3824
+ name: string;
3825
+ threshold: number;
3826
+ less?: boolean;
3827
+ }
3828
+ /**
3829
+ * `RtcSlider::is_enabled` (`common!0x627a920`): the first line for a name, ignoring case (`CreateID`), at
3830
+ * the player's cutscene-detail `slider`. A name with no line is on.
3831
+ */
3832
+ declare const rtcEnabled: (settings: readonly RtcSetting[], slider: number, name: string) => boolean;
3833
+ /** A `userprops` value, its key matched ignoring case as thorn's map does (`_memicmp`). */
3834
+ declare const userProp: (entity: Entity, key: string) => string | undefined;
3835
+ interface SpawnOptions {
3836
+ /** `rtcslider.ini`'s lines. Every name is on when absent. */
3837
+ rtc?: readonly RtcSetting[];
3838
+ /** The cutscene-detail slider, 0 to 1. */
3839
+ slider?: number;
3840
+ }
3841
+ /**
3842
+ * Whether the game makes a host for an entity: not when its `priority` is a setting the slider has
3843
+ * off, nor a `PSYS` with no `priority` while `script_particles` is off (`0x44efd3`).
3844
+ */
3845
+ declare const spawns: (entity: Entity, { rtc, slider }?: SpawnOptions) => boolean;
3846
+ /** `ThornGetRenderType` (`common!0x627c0b0`): `usr_flg` bit 0 starsphere, bit 1 backdrop, which wins. */
3847
+ declare const renderTypeOf: (entity: Entity) => "starsphere" | "backdrop" | undefined;
3848
+ /** A compound or character the room draws, all but its model. */
3849
+ interface RoomEntity {
3850
+ entity: Entity;
3851
+ /** `template_name`, `NULL` as nothing, the entity's own name when absent (`0x6f28a60`). */
3852
+ template: string;
3853
+ /** `userprops.category`, which the host resolves `template` by, case included. */
3854
+ category: string | undefined;
3855
+ /** Where the host puts it: `C · T`. */
3856
+ world: Frame$1;
3857
+ /** Its starsphere or backdrop render type, which the host gives the model as `sky`. */
3858
+ sky: 'starsphere' | 'backdrop' | undefined;
3859
+ /** `running_lights`: whether its `always_on` lights draw. */
3860
+ runningLights: boolean;
3861
+ /** The `RoomObject` fields this decides. */
3862
+ object: Omit<RoomObject, 'model'>;
3863
+ }
3864
+ /**
3865
+ * Every `COMPOUND` and `DEFORMABLE` the game spawns, in script order, with what `SetFlags` and the
3866
+ * spawn give its room object (`0x44de85`, `FThornCompound::SetFlags`): `LIT_AMBIENT` is `visible`,
3867
+ * `LIT_DYNAMIC` is `lit`, fog unless `nofog`, `srt_grp` its signed layer, `lt_grp` its light group,
3868
+ * and its scene's ambient. A `REFERENCE` is another script's, so isn't here.
3869
+ */
3870
+ declare const roomEntities: (playback: Playback, options?: SpawnOptions) => RoomEntity[];
3871
+ /** What the host answers for its entities, by name. Every frame is in the entity's own space. */
3872
+ interface ThornHost {
3873
+ /**
3874
+ * A hardpoint's frame relative to the entity's root at its current pose, or nothing found. The game
3875
+ * matches names ignoring case, the last duplicate winning (`HardpointSummary::find_name`).
3876
+ */
3877
+ hardpoint?(entity: string, name: string): Frame$1 | undefined;
3878
+ /** A `DEFORMABLE`'s bone by name, ignoring case, relative to its root; nothing when missing. */
3879
+ bone?(entity: string, name: string): Frame$1 | undefined;
3880
+ /**
3881
+ * `ICompoundControl +0x18` and `IPSysControl +0x14`: a compound's, deformable's or effect's time
3882
+ * moves on, before the events at each update instant and once more by 0 at the end of a tick.
3883
+ */
3884
+ advance?(entity: string, milliseconds: number): void;
3885
+ }
3886
+ interface PlaybackOptions {
3887
+ loop?: boolean;
3888
+ /**
3889
+ * The host's scene frame `C`: a room character's setpoint over its actor (`ThornScriptGetCsys`),
3890
+ * the identity everywhere else. Thorn's own scene frame is always the identity in the game.
3891
+ */
3892
+ frame?: Frame$1;
3893
+ host?: ThornHost;
3894
+ /** The room's fog, which every script shares: pass one holder to them all. A fresh room's when absent. */
3895
+ fog?: {
3896
+ state: FogState;
3897
+ };
3898
+ /** The `fog` detail option, which `GfFogSetOn` ANDs into `on`. */
3899
+ fogEnabled?: boolean;
3900
+ /**
3901
+ * A new instance of a `PSYS`'s `[VisEffect]`, named by its template ignoring case, or nothing
3902
+ * where there's none. `PlayerShipEngines` is the player ship's own engines: the host's.
3903
+ */
3904
+ createEffect?: (entity: string, template: string) => EffectInstance | undefined;
3905
+ /** The detail gate, which decides which entities get a host at all. */
3906
+ spawn?: SpawnOptions;
3907
+ /**
3908
+ * What runs a compound's or character's scripts and aims: false where the game's host answers
3909
+ * other than `S_OK`. The records are kept either way, and every start succeeds without one.
3910
+ */
3911
+ characters?: CharacterHost;
3912
+ /**
3913
+ * What a `REFERENCE` binds to: a live entity of that type and name in another script, by name, or
3914
+ * where the room's set script puts it, in this script's space; nothing leaves it at its record.
3915
+ */
3916
+ resolve?: (type: Entity['type'], name: string) => {
3917
+ playback: Playback;
3918
+ name: string;
3919
+ } | {
3920
+ frame: Frame$1;
3921
+ } | undefined;
3922
+ }
3923
+ /**
3924
+ * A `CONNECT_HARDPOINTS` while it runs. The game finds the part owning each hardpoint, names
3925
+ * ignoring case, and links the child's under the parent's so the two hardpoints meet:
3926
+ * `childPart = parentPart · H_parent · inverse(H_child)` ({@link connectedFrame}, `0x44fdd0`).
3927
+ */
3928
+ interface Connection {
3929
+ readonly event: ThornEvent;
3930
+ readonly child: string;
3931
+ readonly hardpoint: string;
3932
+ readonly parent: string;
3933
+ readonly parentHardpoint: string;
3934
+ }
3935
+ /**
3936
+ * Where a connected child part goes (`engbase.dll` `0x6626690`): the parent part's world frame, its
3937
+ * hardpoint's frame on it, and the child's hardpoint on the child part. Fixed hardpoints only; a
3938
+ * revolute or prismatic child takes a branch not decoded.
3939
+ */
3940
+ declare const connectedFrame: (parentPart: Frame$1, parentHardpoint: Frame$1, childHardpoint: Frame$1) => Frame$1;
3941
+ /**
3942
+ * Thorn's first play from 0 to `milliseconds`, as the game starts a script: started, ticked by 0,
3943
+ * then one exact jump. A rewind isn't this: thorn's restores no transform.
3944
+ */
3945
+ declare const replay: (script: Script, milliseconds: number, options?: PlaybackOptions) => Playback;
3946
+ /** A camera or monitor in the world, as its host holds it. */
3947
+ interface CameraView {
3948
+ world: Frame$1;
3949
+ /** Half the horizontal angle, degrees. */
3950
+ fovh: number;
3951
+ near: number;
3952
+ far: number;
3953
+ /** `hvaspect`, which the game never reads: its aspect getter and setter are stubs (`0x454190`). */
3954
+ aspect: number;
3955
+ }
3956
+ /** `FThornScene`'s playback states, as far as a tick tells them apart. */
3957
+ type PlaybackState = 'loaded' | 'playing' | 'looping' | 'stopped' | 'done';
3958
+ /** The game's compound and character hosts, as far as thorn drives them. */
3959
+ interface CharacterHost {
3960
+ startMotion?(entity: string, motion: MotionRecord): boolean;
3961
+ stopMotion?(entity: string, motion: MotionRecord): void;
3962
+ startAim?(entity: string, aim: AimRecord): boolean;
3963
+ updateAim?(entity: string, aim: AimRecord): void;
3964
+ stopAim?(entity: string, aim: AimRecord): void;
3965
+ /** `IDeformable +0x60`: the root's floor, which each motion applies at its next evaluation. */
3966
+ setFloor?(entity: string, height: number): void;
3967
+ /**
3968
+ * A character's root (`0x452ab0`–`0x452c20`): a position write keeps `x` and `z` and discards `y`,
3969
+ * and `root` is where it stands now, root motion and all, which thorn reads back. In thorn's space.
3970
+ */
3971
+ setPosition?(entity: string, position: Vector3): void;
3972
+ setOrientation?(entity: string, orientation: Frame$1): void;
3973
+ root?(entity: string): Frame$1 | undefined;
3974
+ /** `ICompoundControl +0x18`, with the clock. */
3975
+ advance?(entity: string, milliseconds: number): void;
3976
+ }
3977
+ /**
3978
+ * VC6's `std::sort`, as thorn sorts its master list and each active group: median-of-three
3979
+ * partitions above 16 (`SortMasterPartitions`, `PartitionActiveEventTypes`), then insertion. Equal
3980
+ * keys keep their order only in a range of 16 or fewer.
3981
+ */
3982
+ declare const retailSort: <T>(items: T[], key: (item: T) => number) => T[];
3983
+ declare class Playback implements Stage {
3984
+ #private;
3985
+ readonly script: Script;
3986
+ /** Milliseconds: `trunc(duration · 1000)`. */
3987
+ readonly duration: number;
3988
+ readonly loop: boolean;
3989
+ readonly host: ThornHost;
3990
+ /** `C`. */
3991
+ readonly sceneFrame: Frame$1;
3992
+ readonly sceneUp: number;
3993
+ readonly sceneFront: number;
3994
+ /** Actions in the script that are scheduled and timed, but whose effect a later step brings. */
3995
+ readonly ignored: readonly EventTypeName[];
3996
+ constructor(script: Script, options?: PlaybackOptions);
3997
+ get state(): PlaybackState;
3998
+ /** `ClockMs_`: the last instant every active event was updated at. */
3999
+ get clock(): number;
4000
+ /** The events running now, in the order they update. */
4001
+ get active(): readonly ThornEvent[];
4002
+ /** Every `CONNECT_HARDPOINTS` running, in the order made: the host links each child part. */
4003
+ get connections(): readonly Connection[];
4004
+ /** An entity's frame in thorn's scene space, by name. */
4005
+ frameOf(name: string): Frame$1 | undefined;
4006
+ /**
4007
+ * Where the host puts an entity: `C · T` for a compound, deformable, camera, monitor or marker,
4008
+ * thorn's `T` as it is for a light, sound or effect (`Thorn_Psys` never applies `C`). A player
4009
+ * ship's `HpMount` offset after it is the host's.
4010
+ */
4011
+ world(name: string): Frame$1 | undefined;
4012
+ /** `StartPlayback` (`LAB_06f39460`), then a tick by `milliseconds`. The game starts at 0. */
4013
+ start(milliseconds?: number): void;
4014
+ /** `StopPlayback`: every running event finished, the clock back at 0. */
4015
+ stop(): void;
4016
+ /**
4017
+ * `Tick` (engine `+0x68`): forward by whole milliseconds. A negative step rewinds to 0 and replays,
4018
+ * **restoring no transform**. Past the end a loop restarts at exactly 0; a script that doesn't
4019
+ * loop finishes every running event and is done.
4020
+ */
4021
+ tick(milliseconds: number): void;
4022
+ frame(entity: number): Frame$1;
4023
+ /** A camera's write goes on to every monitor linked to it (`0x6f41490`, `0x6f414d0`). */
4024
+ setPosition(entity: number, position: Vector3): void;
4025
+ setOrientation(entity: number, orientation: Frame$1): void;
4026
+ cameraProps(entity: number): CameraProps;
4027
+ setCameraProps(entity: number, props: CameraProps): void;
4028
+ light(entity: number): LightState | undefined;
4029
+ setLight(entity: number, light: LightState): void;
4030
+ fog(): FogState;
4031
+ setFog(fog: FogState, mask: number): void;
4032
+ psysStart(entity: number): boolean;
4033
+ psysStop(entity: number): void;
4034
+ sparam(entity: number): number;
4035
+ setSparam(entity: number, value: number): void;
4036
+ /** Every running effect, in the order made: what the room draws of this script's `PSYS`es. */
4037
+ get effects(): {
4038
+ entity: string;
4039
+ instance: EffectInstance;
4040
+ }[];
4041
+ motionStart(entity: number, motion: MotionRecord): boolean;
4042
+ motionStop(entity: number, motion: MotionRecord): void;
4043
+ aimStart(entity: number, aim: AimRecord): boolean;
4044
+ aimUpdate(entity: number, aim: AimRecord): void;
4045
+ aimStop(entity: number, aim: AimRecord): void;
4046
+ floor(entity: number): number | undefined;
4047
+ setFloor(entity: number, height: number): void;
4048
+ /** The scripts running on compounds and characters, in the order started. */
4049
+ get motions(): {
4050
+ entity: string;
4051
+ motion: MotionRecord;
4052
+ }[];
4053
+ /** Every character's running aim, its target as of the last update. */
4054
+ get aims(): {
4055
+ entity: string;
4056
+ aim: AimRecord;
4057
+ }[];
4058
+ /** A character's floor height: its record's, then what `START_FLR_HEIGHT_ANIM` sets. */
4059
+ floorOf(name: string): number | undefined;
4060
+ get now(): number;
4061
+ soundStart(entity: number, { offset, loop, spatial }: {
4062
+ offset: number;
4063
+ loop: boolean;
4064
+ spatial: boolean;
4065
+ }): boolean;
4066
+ soundStop(entity: number): void;
4067
+ audio(entity: number): AudioState | undefined;
4068
+ setAudio(entity: number, audio: AudioState): void;
4069
+ /**
4070
+ * Every sound playing, in the order made, with its entity's `audioprops` now and, for a 3D one,
4071
+ * where thorn has it. The host plays, cuts and adjusts them to match.
4072
+ */
4073
+ get sounds(): (SoundRecord & {
4074
+ audio: AudioState;
4075
+ position: Vector3;
4076
+ })[];
4077
+ kind(entity: number): string | undefined;
4078
+ /** The room's fog as the frame takes it, or none. */
4079
+ get roomFog(): Fog | undefined;
4080
+ /** The `SCENE`'s `ambient`, 0 to 255, as the record writes it: nothing animates it in the game. */
4081
+ get ambient(): Vector3 | undefined;
4082
+ /**
4083
+ * Every `LIGHT` that's on and spawned, in the order made, as the room's (`0x44b450`). A
4084
+ * `REFERENCE` is another script's light.
4085
+ */
4086
+ roomLights(options?: SpawnOptions): RoomLight[];
4087
+ camera(entity: number): boolean;
4088
+ cut(monitor: number, camera: number): void;
4089
+ /** A camera's or monitor's view in the world: its placement through `C`, and its slots. */
4090
+ view(name: string): CameraView | undefined;
4091
+ /** The camera a monitor follows, by name. */
4092
+ linkedCamera(monitor: string): string | undefined;
4093
+ compound(entity: number): boolean;
4094
+ /**
4095
+ * `ICompoundControl +0x24`. A compound answers a hardpoint, or `PART` `root` or `""` with its root,
4096
+ * in **world** space (`0x451b90`: `C` is not undone, and thorn takes it as scene space); any other
4097
+ * part writes nothing. A deformable answers a hardpoint or a bone in scene space (`0x452730`).
4098
+ */
4099
+ part(entity: number, type: number, name: string): Frame$1 | undefined;
4100
+ path(entity: number): Path | undefined;
4101
+ connect(event: ThornEvent): void;
4102
+ disconnect(event: ThornEvent): void;
4103
+ }
4104
+ /** What a frame hands every sequence: whole milliseconds, and the rest carried to the next. */
4105
+ interface FrameMilliseconds {
4106
+ milliseconds: number;
4107
+ carry: number;
4108
+ }
4109
+ /**
4110
+ * `0x558ac7`–`0x558afa`: the carry plus the frame's seconds, floored to milliseconds, the remainder
4111
+ * kept as a float. At the x87's 24-bit precision, which D3D8 leaves it at.
4112
+ */
4113
+ declare const frameMilliseconds: (carry: number, seconds: number) => FrameMilliseconds;
4114
+ /**
4115
+ * One script as a sequence plays it (`FGfScript::Start`, `Update`): started and ticked by 0 before
4116
+ * the first frame, then by each frame's milliseconds. The frame that reaches the end, or skips to
4117
+ * it, ticks 100 more, so a script that doesn't loop finishes its events then.
4118
+ */
4119
+ declare class ScriptRun {
4120
+ #private;
4121
+ readonly playback: Playback;
4122
+ constructor(playback: Playback);
4123
+ get finished(): boolean;
4124
+ /** `FGfScript::Pause`, as the skip key sets it: the next update jumps to the end, exactly. */
4125
+ skip(): void;
4126
+ /**
4127
+ * `FGfScript::Update`, true once finished. `catchup` is `0x44f500`'s: how far a finished rebuff
4128
+ * voice line lets the scene jump, 0 everywhere else.
4129
+ */
4130
+ update(milliseconds: number, catchup?: number): boolean;
4131
+ }
4132
+ /** A frame's rotation as the rows a THN `orient` reads into, which a renderer `Camera` takes. */
4133
+ declare const rowsOf: ({ x, y, z }: Frame$1) => Matrix3;
4134
+ /** A script camera's view as the renderer's `Camera`. */
4135
+ declare const cameraOf: ({ world, fovh, near, far }: CameraView) => Camera;
4136
+ /**
4137
+ * `FGfCameraPool`: 256 slots. A `MONITOR` registers selectable with its script's priority (1 for
4138
+ * landing, launch and transitions, else 0); a `CAMERA` registers but never shows. A new slot takes
4139
+ * the view only over a strictly lower priority (`Allocate`, `0x450580`); freeing the shown one
4140
+ * re-elects the highest, ties to the lowest slot (`Free`, `0x450680`). None shown: the room's camera.
4141
+ */
4142
+ declare class ViewTable {
4143
+ #private;
4144
+ /** The slot shown, or nothing. */
4145
+ get active(): number | undefined;
4146
+ /** A slot for a camera or monitor, or nothing when all 256 are taken. */
4147
+ register(priority: number, selectable: boolean): number | undefined;
4148
+ release(slot: number): void;
4149
+ }
4150
+ /**
4151
+ * The letterbox: an amount from 0, no bars, to 1, a 1.85:1 picture, moving toward its target at
4152
+ * `speed` a second. A script that takes the camera aims it at 1 and lets go at 0, both at 3.
4153
+ */
4154
+ declare class Letterbox {
4155
+ amount: number;
4156
+ target: number;
4157
+ speed: number;
4158
+ animating: boolean;
4159
+ /** `SetLetterboxTarget` (`FUN_0041a0f0`): under an opaque fade it jumps there. */
4160
+ aim(target: number, speed?: number, faded?: boolean): void;
4161
+ /** The head of `UpdateRoomLetterbox` (`FUN_0041a140`): true while it was moving. */
4162
+ step(seconds: number): boolean;
4163
+ /** `GetLetterboxBarHeight`: each bar, in pixels, rounded half up. */
4164
+ bar({ width, height }: Viewport): number;
4165
+ /**
4166
+ * The picture between the bars, and its aspect, which the room camera's projection takes in
4167
+ * place of the surface's, the horizontal angle held.
4168
+ */
4169
+ view(surface: Viewport): {
4170
+ top: number;
4171
+ viewport: Viewport;
4172
+ aspect: number;
4173
+ };
4174
+ }
4175
+ /**
4176
+ * An entry's kind: 0 plain, 1 an offer's decision, 2 accept, 3 decline or reject, 4 an info box,
4177
+ * 5 a room's traffic, which repeats, 6 a group offer's other branch.
4178
+ */
4179
+ type EntryKind = 0 | 1 | 2 | 3 | 4 | 5 | 6;
4180
+ interface SequenceEntry {
4181
+ script: Script;
4182
+ kind?: EntryKind;
4183
+ /** The host's scene frame `C`: a room character's setpoint over its actor, else the identity. */
4184
+ frame?: Frame$1;
4185
+ }
4186
+ /** A sequence's flags (`0x454920`). The host acts on most of them: letterbox, ducking, fades. */
4187
+ interface SequenceFlags {
4188
+ loop?: boolean;
4189
+ /** Letterboxed, the UI hidden. */
4190
+ cinematic?: boolean;
4191
+ skippable?: boolean;
4192
+ /** Its live entities are what other scripts' `REFERENCE`s find (`+0x2f`). */
4193
+ exported?: boolean;
4194
+ /** Its monitors' camera priority: 1 for landing, launch and transitions, else 0. */
4195
+ priority?: number;
4196
+ }
4197
+ declare class Sequence {
4198
+ #private;
4199
+ readonly entries: readonly SequenceEntry[];
4200
+ readonly flags: SequenceFlags;
4201
+ /** The entry playing, or −1 once done. */
4202
+ current: number;
4203
+ run: ScriptRun | undefined;
4204
+ /** The decision's answer, which the host sets from the offer dialog. */
4205
+ answered: boolean | undefined;
4206
+ infoClosed: boolean;
4207
+ constructor(entries: readonly SequenceEntry[], flags?: SequenceFlags, options?: (frame: Frame$1 | undefined) => PlaybackOptions);
4208
+ /** `Play` (`FUN_00454f80`): the first entry starts, ticked by 0. */
4209
+ play(): void;
4210
+ get done(): boolean;
4211
+ get playback(): Playback | undefined;
4212
+ /** The skip key (`0x456840`): a cinematic, skippable sequence skips to its decision, which plays. */
4213
+ skip(): void;
4214
+ /** The offer answered: the looping decision jumps to its end, so the branch starts next update. */
4215
+ answer(accepted: boolean): void;
4216
+ /** `NextEntryIndex` (`FUN_00455fc0`), single player. */
4217
+ next(): number;
4218
+ /**
4219
+ * `FGfScriptSequence::Update` (`FUN_00456150`): skipping runs each entry out to its end, stopping
4220
+ * at a decision or info box; then the live script by `milliseconds`, and the next entry when it
4221
+ * finishes. True once done.
4222
+ */
4223
+ update(milliseconds: number, catchup?: number): boolean;
4224
+ }
4225
+ /** `FGfScriptManager`: every sequence, updated in the order made with the same milliseconds. */
4226
+ declare class SequenceManager {
4227
+ readonly sequences: Sequence[];
4228
+ add(sequence: Sequence): Sequence;
4229
+ /** `FUN_00456650`: each updated; the finished dropped. */
4230
+ update(milliseconds: number): void;
4231
+ skip(): void;
4232
+ /**
4233
+ * `FindCreatedEntity` (`FUN_00456790`): the first live entity of that type and name in the current
4234
+ * script of an exporting sequence, by the order the sequences were made.
4235
+ */
4236
+ find(type: Entity['type'], name: string): {
4237
+ playback: Playback;
4238
+ name: string;
4239
+ } | undefined;
4240
+ }
4241
+ /** A prop the set script lists: the room's own models. */
4242
+ interface SetProp {
4243
+ entity: Entity;
4244
+ template: string;
4245
+ category: string;
4246
+ frame: Frame$1;
4247
+ /** `LIT_AMBIENT`, `LIT_DYNAMIC`, fog unless `nofog`. */
4248
+ visible: boolean;
4249
+ lit: boolean;
4250
+ fog: boolean;
4251
+ /** A `Room` category or a `hardpoint` userprop: the room's main prop. */
4252
+ main: boolean;
4253
+ lightGroup: number;
4254
+ layer: number;
4255
+ sky: 'starsphere' | 'backdrop' | undefined;
4256
+ loadout: string;
4257
+ priority: string | undefined;
4258
+ runningLights: boolean;
4259
+ }
4260
+ interface SetCamera {
4261
+ name: string;
4262
+ frame: Frame$1;
4263
+ fovh: number;
4264
+ near: number;
4265
+ far: number;
4266
+ }
4267
+ /** A shadow or glare source: directional along its turned direction, else at its position. */
4268
+ interface SetSource {
4269
+ direction?: Vector3;
4270
+ position?: Vector3;
4271
+ color: Vector3;
4272
+ }
4273
+ interface SetScript {
4274
+ props: SetProp[];
4275
+ /** Every camera, which is a setpoint of the same name too. */
4276
+ cameras: SetCamera[];
4277
+ setpoints: {
4278
+ name: string;
4279
+ frame: Frame$1;
4280
+ }[];
4281
+ /** `LtShadowSource` and `LtGlareSource`. */
4282
+ shadow?: SetSource;
4283
+ glare?: SetSource;
4284
+ /** Any other light that's on: none in retail. */
4285
+ lights: RoomLight[];
4286
+ /** The `SCENE`'s, 0 to 255. */
4287
+ ambient?: Vector3;
4288
+ }
4289
+ /**
4290
+ * `RoomData::read_set_script` (`common!0x6274ea0`): the set script as data. Its compounds with a `category`
4291
+ * become props, its cameras cameras and setpoints, its markers setpoints, two lights by name the
4292
+ * shadow and glare sources, and the rest of the lights only when on. No event runs.
4293
+ */
4294
+ declare const readSetScript: (script: Script) => SetScript;
4295
+ /**
4296
+ * `ThornScriptGetCsys` (`0x627be8f`): the first `DEFORMABLE` whose `actor` isn't `player`, upright at
4297
+ * its floor height: `[R | (x, floor_height, z)]`. The identity with none.
4298
+ */
4299
+ declare const scriptAnchor: (script: Script) => Frame$1;
4300
+ /**
4301
+ * A room character's fidget (`0x445340`): `C = setpoint · G⁻¹`, so its actor lands there. A
4302
+ * conversation runs at `C = I`.
4303
+ */
4304
+ declare const anchoredFrame: (setpoint: Frame$1, script: Script) => Frame$1;
4305
+ /**
4306
+ * A room's `scene = ambient, path` line (`0x44a540`): a looping, exporting sequence at `C = I`,
4307
+ * priority 0, not cinematic.
4308
+ */
4309
+ declare const ambientSequence: (script: Script, options?: (frame: Frame$1 | undefined) => PlaybackOptions) => Sequence;
4310
+ /** Event flags a motion reads. */
4311
+ export declare const MotionFlag: {
4312
+ readonly Backwards: 1;
4313
+ readonly Loop: 2;
4314
+ readonly Bounce: 4;
4315
+ /** Keep what is beneath once the fade in completes, rather than stopping it. */
4316
+ readonly KeepBeneath: 128;
4317
+ };
4318
+ /** `MixerEase` (`FUN_0661d050`): a fade's weight at step `i` of 255, `½(1 − cos(i · step))`. */
4319
+ export declare const EASE: Float32Array;
4320
+ /**
4321
+ * A fade's weight `time` seconds into `duration`: its step is floored, and a zero-length fade is
4322
+ * whole.
4323
+ */
4324
+ export declare const fadeWeight: (time: number, duration: number) => number;
4325
+ /** One animatable joint: its name, its parent's, and the joint its constraint makes. */
4326
+ interface MixerJoint {
4327
+ name: string;
4328
+ parent: string;
4329
+ joint: Joint;
4330
+ }
4331
+ interface MixerOptions {
4332
+ /** The root bone's name, which object maps drive. */
4333
+ root: string;
4334
+ /** Every joint, a parent before its children. */
4335
+ joints: readonly MixerJoint[];
4336
+ /** The root's placement in the character's frame. */
4337
+ position?: Vector3;
4338
+ orientation?: Quat;
4339
+ }
4340
+ /** A motion's record, as `START_MOTION` hands it to `IDeformable +0x1c`. */
4341
+ interface MotionOptions {
4342
+ /** Seconds into each channel; past any channel's end the motion does not start. */
4343
+ startTime?: number;
4344
+ /** The players' speed, its sign ignored. */
4345
+ timeScale?: number;
4346
+ /** An amplitude on every channel, not a mix weight. */
4347
+ weight?: number;
4348
+ /** The fade in, per target. 0 stops whatever was on each target. */
4349
+ transTime?: number;
4350
+ eventFlags?: number;
4351
+ /** Radians about Y the root faces; negative keeps its current yaw. */
4352
+ heading?: number;
4353
+ }
4354
+ interface AimOptions {
4355
+ /** The joint's world rotation, in the space the root is placed in: the root's own turn included. */
4356
+ target: Quat;
4357
+ transitionDuration?: number;
4358
+ eventFlags?: number;
4359
+ }
4360
+ export declare class Mixer {
4361
+ #private;
4362
+ constructor({ root, joints, position, orientation }: MixerOptions);
4363
+ /** The root's placement in the character's frame, as the mixer last wrote it. */
4364
+ get root(): {
4365
+ position: Vector3;
4366
+ orientation: Quat;
4367
+ };
4368
+ /** `Offset20`. */
4369
+ get floor(): number;
4370
+ /** A joint's state as last written, its rest before anything has. */
4371
+ jointState(name: string): JointState | undefined;
4372
+ /**
4373
+ * `RegisterAnimHandle`: scripts started later leave the joint be, and an aim through it fails.
4374
+ */
4375
+ lock(name: string): void;
4376
+ /** `UnregisterAnimHandle`, as THN's Finish of a motion with a `locked_bone` does. */
4377
+ unlock(name: string): void;
4378
+ /**
4379
+ * `PlayInstanceAnim` (`FUN_065f41d0`): bind the script's maps, stand the root upright at its
4380
+ * `Root height` over the floor facing `heading`, and fade each target in. `undefined` when the
4381
+ * start time is past a channel's end (`TypedTimePlayable`), which retail destroys the script for.
4382
+ */
4383
+ start(script: AnimationScript, options?: MotionOptions): number | undefined;
4384
+ /**
4385
+ * `DestroyHandle`: THN's Finish. Every target lets go at once, with no fade out
4386
+ * (`MixerUnbindTyped`).
4387
+ */
4388
+ stop(handle: number): void;
4389
+ /** Whether any of a motion's channels is still on a target. */
4390
+ playing(handle: number): boolean;
4391
+ /**
4392
+ * `FUN_065f5400` for one joint with `point_at`: the joint's world rotation goes to `target`,
4393
+ * entering its stack as a motion would, with the transition as its fade. The joint locks;
4394
+ * `undefined` when it already was, or is not a sphere joint (on a loose one retail would write a
4395
+ * rotation over its position, which is not reproduced).
4396
+ */
4397
+ aim(joint: string, { target, transitionDuration, eventFlags }: AimOptions): number | undefined;
4398
+ /** THN re-sends an aim's target at every update. */
4399
+ setAimTarget(handle: number, target: Quat): void;
4400
+ /**
4401
+ * `StopAimNode`: the joint unlocks, and the aim ends as a channel running out does. With
4402
+ * {@link MotionFlag.KeepBeneath} it fades back to what is beneath; without, its pose holds.
4403
+ */
4404
+ stopAim(handle: number): void;
4405
+ /**
4406
+ * `MixerWalkAll` (`LAB_066157f0`): every stack ticks, then writes its target. Joints first, then
4407
+ * the root.
4408
+ */
4409
+ advance(dt: number): void;
4410
+ /**
4411
+ * `SetPosePos`: the root goes to `position`'s x and z. Its height is each motion's own: `Root
4412
+ * height` plus its stored height now plus the floor, or the resting height without one;
4413
+ * `position.y` is not read.
4414
+ */
4415
+ setRootPosition(position: Vector3): void;
4416
+ /**
4417
+ * `SetPoseOri`: the root turns to `orientation`, every motion's root turning with it about where
4418
+ * it stands.
4419
+ */
4420
+ setRootOrientation(orientation: Quat): void;
4421
+ /**
4422
+ * `SetHeight` (`0x65f50b0`): the floor, under every motion's root from the next advance. A
4423
+ * motion's root moves by what puts it at its height over the new floor.
4424
+ */
4425
+ setFloor(height: number): void;
4426
+ }
4427
+ interface MixerCharacters {
4428
+ /** A character's mixer, by entity name, or nothing for one the host doesn't run. */
4429
+ mixer(entity: string): Mixer | undefined;
4430
+ /**
4431
+ * An animation by name, from the one global table every `bodyparts.ini [Animations]` file fills,
4432
+ * ignoring case (`0x435100`). A missing one logs and still succeeds: the event does nothing.
4433
+ */
4434
+ animation(name: string): AnimationScript | undefined;
4435
+ }
4436
+ /**
4437
+ * A {@link CharacterHost} on mixers. A motion locks its `locked_bone` from its start to its Finish;
4438
+ * an aim is the single-joint `point_at` solve, its scene-space rotation handed on as it is, since the
4439
+ * solve undoes the parent's world, the character's own turn included (`IkSolveSingleJoint`); keep-beneath
4440
+ * from event flag 0x80 (`StartIk`, `0x452460`). Longer chains are left out.
4441
+ */
4442
+ declare const mixerCharacters: ({ mixer, animation }: MixerCharacters) => CharacterHost & {
4443
+ /** `ICompoundControl +0x18`: the playback's advance, in milliseconds. */
4444
+ advance(entity: string, milliseconds: number): void;
4445
+ /** Where a character's root stands now, in the character's frame: thorn reads the walk back. */
4446
+ root(entity: string): Frame$1 | undefined;
4447
+ };
4448
+ declare namespace index_d_exports {
4449
+ export { AimRecord, AudioState, CameraProps, CameraView, CharacterHost, Connection, Curve, DEFAULT_AUDIO, DEFAULT_LIGHT, EntryKind, FLAGS, FogState, Frame$1 as Frame, FrameMilliseconds, LIGHT_BITS, Letterbox, LightState, MixerCharacters, MotionRecord, NO_FOG, Path, Playback, PlaybackOptions, PlaybackState, Quaternion, RUN, RoomEntity, RtcSetting, ScriptRun, Sequence, SequenceEntry, SequenceFlags, SequenceManager, SetCamera, SetProp, SetScript, SetSource, SoundRecord, SpawnOptions, Stage, ThornEvent, ThornHost, Timing, ViewTable, ambientSequence, anchoredFrame, axisTurn, blendAudio, blendFog, blendLight, cameraOf, connectedFrame, createCurve, createPath, eventsOf, flagWord, frameMilliseconds, inverse, lightOf, lookAt, milliseconds, mixerCharacters, nlerp, placement, progress, quaternionOf, readAudio, readFog, readLight, readSetScript, renderTypeOf, replay, retailSort, roomEntities, roomFogOf, roomLightOf, rotationOf, rowsOf, rtcEnabled, scriptAnchor, soundLevel, spawns, userProp };
4450
+ }
4451
+ export { type AdvanceOptions, type AimOptions, type BackgroundLightningSettings, type BandField, type BandLighting, type BandPiece, type BandSettings, type BeamArchetype, type BeamLimits, type BeamSection, type BeamStart, type BillboardField, type BillboardLighting, type BillboardSettings, type Camera, type CloudDrawOptions, type CloudSettings, type CloudVisibility, type CockpitLimits, type CoronaView, type CubeAsteroid, type CubeDraw, type CubeExclusion, type CubeField, type CubeRotations, type DeferredNebula, type Draw, type DrawSkin, type DrawTexture, type DustEffect, type DustLimits, type DustOptions, type DustZone, type DynamicDraw, type DynamicEffect, type DynamicField, type DynamicLightningSettings, type DynamicSettings, type EffectDraw, type EffectLight, type EffectLodSizes, type EffectLookup, type EffectNode, type EffectPlacement, type EffectPools, type EffectScene, type EffectType, type EffectView, type EffectViewFrame, type EffectViewOptions, type EquipmentLight, type ExclusionShell, type Extent, type ExteriorLimits, type ExteriorSettings, type FlareBead, type FlareLight, type FlareView, type FlashlightSet, type FlashlightTextures, type Fog, type Frame, type FrameHook, type FrameHooks, type FrameLimits, type FrameRect, type Graph, type GroupDraw, type Hitbox, type HitboxObject, type HitboxSurface, type HookContext, type HookName, type InExclusion, type LensGlow, type LightAnimation, type LightSet, type LodDetail, type LodList, type MaterialMapRule, type MixerJoint, type MixerOptions, type MotionOptions, type NebulaAmbient, type NebulaExclusion, type NebulaLimits, type NebulaList, type NebulaState, type ObjectLighting, type PlaceOptions, type PlacedEffect, type PlacedPart, type PlanetRing, type PoolSizes, type Program, type Random, type RayHit, type ReferencePlan, type RenderOptions, type Renderer, type ResolveNode, type Rgb, type RingExterior, type RingOptions, type RingPiece, type RoomFrame, type RoomLight, type RoomObject, type RoomScene, type RoomShadow, type RoomShadows, type Scene, type SceneEffect, type SceneLight, type SceneModel, type ScreenQuad, type ScreenQuadBatch, type ShadowLimits, type ShadowQuad, type SkinMesh, type SkinOptions, type SkinPose, type SkyLayer, type SpaceAsteroidField, type SpaceCockpit, type SpaceEffect, type SpaceField, type SpaceFlare, type SpaceFrame, type SpaceJump, type SpaceLight, type SpaceLimits, type SpaceNebula, type SpaceObject, type SpaceScene, type SphereLevels, type SphereOptions, type Spot, type SpriteDraw, type SpriteFrameStyle, type Star, type StarGlow, type StarLensFlare, type StarSpine, type StarSpines, type SwitchTable, type TextureShape, type TractorArm, type TractorBeam, type TractorLimits, type TriangleDraw, type TunnelLayer, type TunnelSettings, type TunnelState, type UvTransform, type VertexLayout, type Viewport, type ViewportRect, type Within, type WrittenZone, type Zone, index_d_exports as thorn };
4452
+ //# sourceMappingURL=index.d.ts.map