@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Yuriy Alexeev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,149 @@
1
- # Temporary Holding Version
1
+ # @treewyrm/freelancer-webgl2
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript library rendering **Freelancer** (Digital Anvil, 2003) through WebGL2. It aims for the
4
+ picture the game draws, checked against frames captured from the retail game. Built on
5
+ [`@treewyrm/freelancer`](https://www.npmjs.com/package/@treewyrm/freelancer), which reads the game's
6
+ formats, and written first for `freelancer-vscode`, the Freelancer extension for VS Code.
7
+
8
+ Not affiliated with or endorsed by Digital Anvil or Microsoft. No game data ships in this package:
9
+ the host supplies the bytes of its own install.
10
+
11
+ ## Screenshots
12
+
13
+ Frames drawn by this library from a retail install, at the game's 1024×768.
14
+
15
+ | | |
16
+ | --- | --- |
17
+ | ![The main menu's planet chunks scene](docs/images/menu-planetchunks.png) | ![Trent on Manhattan, three men talking behind him, from the opening cutscene](docs/images/cutscene-s002xe.png) |
18
+ | Main menu, `intro_planetchunks.thn` | Opening cutscene on Manhattan, `s002xe` at 0:20.8 |
19
+ | ![Planet Manhattan and its station in New York](docs/images/system-li01.png) | ![Freeport 10, Tau-37](docs/images/system-ew01.png) |
20
+ | Fort Bush, New York (LI01) | Freeport 10, Tau-37 (EW01) |
21
+ | ![A strange place far away](docs/images/system-ew06.png) | ![Juni and the Liberty diplomat at a table in Manhattan's bar, Trent standing](docs/images/cutscene-s004x.png) |
22
+ | Unknown System (EW06) | Juni and the diplomat in Manhattan's bar, `s004x` at 0:20.5 |
23
+
24
+ ## Status
25
+
26
+ These render and match retail:
27
+
28
+ - **Models**: rigid models with every registered material type, level of detail, UV animation,
29
+ environment maps, cloaking and Nomad surfaces. The canopy glass's picture is still open; see
30
+ [VALIDATION.md](docs/VALIDATION.md#open-questions).
31
+ - **Characters**: skinned bodies, heads and hands, posed by animation.
32
+ - **Planets, starspheres and backdrops.**
33
+ - **Alchemy effects**: particles, beams, and their fields.
34
+ - **Whole systems**, framed as the game frames space: suns with their coronas and flares, nebulae,
35
+ asteroid fields, rings, space dust, effects, running lights, laser shots and tractor beams.
36
+ - **Base rooms** with their lights and shadows.
37
+ - **Cutscenes**: THN playback, its clock, cameras, lights and paths.
38
+
39
+ The extension's model viewer, character editor, material preview, effect editor, system editor,
40
+ Loadouts and Beams editors draw with it; its own renderer is down to the camera, the pick and the
41
+ overlays.
42
+
43
+ Not drawn yet: the HUD and 2D interface. Wanted beyond retail, as an option: keeping a Targa's 24
44
+ bits, where retail narrows it to 16 and bands a starsphere.
45
+
46
+ ## Use
47
+
48
+ The host reads the bytes with `@treewyrm/freelancer`, describes a scene as plain data, and collects a
49
+ frame from it. Collecting touches no GL: a frame is an ordered list of draws, which the renderer then
50
+ executes.
51
+
52
+ ```ts
53
+ import {
54
+ collectFrame,
55
+ createContext,
56
+ createRenderer,
57
+ placeParts,
58
+ type Scene,
59
+ } from '@treewyrm/freelancer-webgl2'
60
+ import { getMaterial, readMaterialLibrary } from '@treewyrm/freelancer/material'
61
+ import { Matrix3, Matrix4 } from '@treewyrm/freelancer/math'
62
+ import { readRigidModel } from '@treewyrm/freelancer/rigid'
63
+ import { readTextureLibrary } from '@treewyrm/freelancer/texture'
64
+ import { Directory } from '@treewyrm/freelancer/utf'
65
+ import { getMesh, readVMeshLibrary } from '@treewyrm/freelancer/vmesh'
66
+
67
+ // A .cmp that carries its own meshes, materials and textures.
68
+ const root = Directory.read(bytes)
69
+ const meshes = readVMeshLibrary(root)
70
+ const materials = [...readMaterialLibrary(root)]
71
+ const textures = [...readTextureLibrary(root)]
72
+
73
+ const scene: Scene = {
74
+ camera: {
75
+ position: { x: 0, y: 0, z: 50 },
76
+ orientation: Matrix3.identity,
77
+ fovh: 35,
78
+ near: 1,
79
+ far: 10000,
80
+ },
81
+ aspect: canvas.width / canvas.height,
82
+ ambient: { x: 0.2, y: 0.2, z: 0.2 },
83
+ lights: [{ color: { x: 1, y: 1, z: 1 }, direction: { x: 0, y: -1, z: -1 } }],
84
+ models: [
85
+ {
86
+ parts: placeParts(readRigidModel(root), Matrix4.identity),
87
+ mesh: (id) => getMesh(meshes, id),
88
+ material: (id) => getMaterial(materials, id),
89
+ texture: (name) => textures.find((entry) => entry.name.toLowerCase() === name.toLowerCase()),
90
+ },
91
+ ],
92
+ }
93
+
94
+ const renderer = createRenderer(createContext(canvas))
95
+ renderer.render(collectFrame(scene))
96
+ ```
97
+
98
+ Each kind of picture has its own collector, since each orders, lights and fogs differently:
99
+
100
+ | Collector | Draws |
101
+ | ------------------- | -------------------------------------------------------------- |
102
+ | `collectFrame` | A preview: models and effects, under lights the host chooses |
103
+ | `collectSpaceFrame` | A system as the game frames space, from a `SpaceScene` |
104
+ | `collectRoomFrame` | A base room, with its lights, flashlights and shadows |
105
+
106
+ `thorn` plays THN scripts: their clock, events, placement and paths. It draws nothing itself; a room
107
+ or space frame draws what it places.
108
+
109
+ **The host owns** the canvas, scheduling, file loading and time. Nothing reads a clock,
110
+ a filesystem or a network, and nothing needs a DOM beyond the canvas or context handed in, so it
111
+ runs in a worker. What rolls dice takes the host's `random`, falling back to `Math.random` when
112
+ none is given. GPU resources are cached on object identity, so pass raw objects,
113
+ not reactive proxies.
114
+
115
+ **Editor visuals** draw through hooks: `renderer.render(frame, { begin, opaque, end })`. A hook gets
116
+ the depth buffer the game left and the view-projection, and may change any GL state; the renderer
117
+ sets its own back afterwards. A hook that draws nothing leaves the frame pixel for pixel as it was.
118
+
119
+ **What retail hardcodes is a default**, where a mod can patch it: `RETAIL_SPACE_LIMITS`,
120
+ `RETAIL_POOLS`, `RETAIL_DUST_LIMITS` and the other `RETAIL_*` exports. Pass your own to match a mod.
121
+
122
+ It needs WebGL2, and `WEBGL_compressed_texture_s3tc` for DXT textures.
123
+
124
+ Every export, by area, with recipes for effects, systems and rooms, is in [docs/API.md](docs/API.md).
125
+
126
+ ## Develop
127
+
128
+ ```sh
129
+ npm install
130
+ npx playwright install chromium # once, for the GPU tests
131
+ npm test # pure tests in Node
132
+ npm run test:gpu # headless Chromium on the hardware GPU, never software
133
+ npm run typecheck
134
+ npm run build
135
+ ```
136
+
137
+ Tests that read retail assets look in `$FREELANCER_DATA`, then `~/Downloads/Freelancer/DATA`, and
138
+ skip when neither is there. `npm run view`, `capture`, `reference` and `bench` work against a retail
139
+ install; [CLAUDE.md](CLAUDE.md) lists them.
140
+
141
+ | Doc | Covers |
142
+ | ------------------------------------------ | --------------------------------------------------------- |
143
+ | [CLAUDE.md](CLAUDE.md) | Sources in order of authority, rules, layout, code style |
144
+ | [docs/API.md](docs/API.md) | What's what, every export by area, and recipes |
145
+ | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | The frame as data, materials, the host boundary, hooks |
146
+ | [docs/VALIDATION.md](docs/VALIDATION.md) | Checking against retail: reference scenes and captures |
147
+ | [docs/COVERAGE.md](docs/COVERAGE.md) | Each material's status, by evidence |
148
+ | [docs/retail/](docs/retail/README.md) | How retail renders, where it isn't obvious, each claim checked or marked |
149
+ | [docs/DECOMP-ERRORS.md](docs/DECOMP-ERRORS.md) | Where the decomp disagrees with the retail binary |