@rickyzhangca/fpsr 0.0.1
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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/abort-CrgARaZd.js +43 -0
- package/dist/abort-CrgARaZd.js.map +1 -0
- package/dist/assets-Dy4fqZBV.d.ts +85 -0
- package/dist/assets-Dy4fqZBV.d.ts.map +1 -0
- package/dist/canvas.d.ts +98 -0
- package/dist/canvas.d.ts.map +1 -0
- package/dist/canvas.js +3 -0
- package/dist/execute-CRZV5zw1.js +963 -0
- package/dist/execute-CRZV5zw1.js.map +1 -0
- package/dist/host-DdX4g1YM.d.ts +34 -0
- package/dist/host-DdX4g1YM.d.ts.map +1 -0
- package/dist/index.d.ts +463 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1776 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +16 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +101 -0
- package/dist/node.js.map +1 -0
- package/dist/planner.d.ts +418 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +96 -0
- package/dist/planner.js.map +1 -0
- package/dist/profile-CTN1Q5SI.d.ts +365 -0
- package/dist/profile-CTN1Q5SI.d.ts.map +1 -0
- package/dist/profile-DR4i6HDp.js +610 -0
- package/dist/profile-DR4i6HDp.js.map +1 -0
- package/dist/render-db-BFQIspDB.d.ts +426 -0
- package/dist/render-db-BFQIspDB.d.ts.map +1 -0
- package/dist/render-db.d.ts +2 -0
- package/dist/render-db.js +1 -0
- package/dist/tiled-draw-list-Dc-5Ledi.js +3990 -0
- package/dist/tiled-draw-list-Dc-5Ledi.js.map +1 -0
- package/dist/types-CHgZBSra.d.ts +105 -0
- package/dist/types-CHgZBSra.d.ts.map +1 -0
- package/dist/upgrade-planner-CNjnMHPJ.d.ts +385 -0
- package/dist/upgrade-planner-CNjnMHPJ.d.ts.map +1 -0
- package/package.json +86 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.1
|
|
4
|
+
|
|
5
|
+
First public release.
|
|
6
|
+
|
|
7
|
+
- ESM library for browser and Node.js targeting Factorio **2.1.11** (vanilla + Space Age + Elevated Rails + Quality)
|
|
8
|
+
- Entry points: `@rickyzhangca/fpsr`, `@rickyzhangca/fpsr/planner`, `@rickyzhangca/fpsr/canvas`, `@rickyzhangca/fpsr/render-db`, `@rickyzhangca/fpsr/node`
|
|
9
|
+
- Decode/encode blueprint strings, resolve connectivity, plan draw lists, and render to Canvas2D
|
|
10
|
+
- Sprite atlases and render database are **not** bundled — load via `cdnAssets` or `localAssets` from your own pipeline output
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ricky Zhang
|
|
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
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# fpsr
|
|
2
|
+
|
|
3
|
+
Headless **Factorio 2.1.11** blueprint-string renderer for browser and Node.js.
|
|
4
|
+
Decodes compressed blueprint strings, resolves connectivity-aware sprite variants
|
|
5
|
+
(belts, pipes, walls, rails, trains, wires), plans a serializable draw list, and
|
|
6
|
+
executes it on a Canvas2D backend.
|
|
7
|
+
|
|
8
|
+
Supports vanilla plus **Space Age**, **Elevated Rails**, and **Quality** content
|
|
9
|
+
from the official data packages.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @rickyzhangca/fpsr
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For Node.js rendering, add the optional peer dependency:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install skia-canvas
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The npm package ships **code only** — sprite atlases and the render database are
|
|
24
|
+
not included (see [Asset hosting](#asset-hosting)).
|
|
25
|
+
|
|
26
|
+
## Quick start (browser)
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { cdnAssets, createRenderer, decode } from "@rickyzhangca/fpsr";
|
|
30
|
+
|
|
31
|
+
const doc = decode(blueprintString);
|
|
32
|
+
const renderer = await createRenderer({
|
|
33
|
+
assets: cdnAssets("https://your-cdn.example.com/2.1.11"),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const { canvas } = await renderer.render(doc);
|
|
37
|
+
document.body.appendChild(canvas as HTMLCanvasElement);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`createRenderer` loads the render database once, plans the draw list, sizes a
|
|
41
|
+
canvas from blueprint bounds, and paints atlas sprites. Pass `blueprintPath` when
|
|
42
|
+
rendering a nested blueprint book entry.
|
|
43
|
+
|
|
44
|
+
### Large PNG export
|
|
45
|
+
|
|
46
|
+
For full-resolution images that may exceed browser canvas limits, use the tiled
|
|
47
|
+
PNG exporter. It renders bounded temporary canvases and feeds their rows into an
|
|
48
|
+
incremental PNG encoder, so it never creates one canvas at the final dimensions:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const { blob, width, height } = await renderer.renderTiledPng(doc, {
|
|
52
|
+
pixelsPerTile: 64,
|
|
53
|
+
tileSize: 2048,
|
|
54
|
+
onProgress(event) {
|
|
55
|
+
console.log(event);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const link = document.createElement("a");
|
|
60
|
+
link.href = URL.createObjectURL(blob);
|
|
61
|
+
link.download = `blueprint-${width}x${height}.png`;
|
|
62
|
+
link.click();
|
|
63
|
+
URL.revokeObjectURL(link.href);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The exporter always produces lossless PNG. Canvas-based WebP export remains
|
|
67
|
+
available through `render(...).toImageBlob({ type: "image/webp" })` for outputs
|
|
68
|
+
that fit safely in one canvas.
|
|
69
|
+
|
|
70
|
+
## Quick start (Node.js)
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { writeFile } from "node:fs/promises";
|
|
74
|
+
import { Canvas } from "skia-canvas";
|
|
75
|
+
import { createRenderer, decode } from "@rickyzhangca/fpsr";
|
|
76
|
+
import { localAssets } from "@rickyzhangca/fpsr/node";
|
|
77
|
+
|
|
78
|
+
const doc = decode(blueprintString);
|
|
79
|
+
const renderer = await createRenderer({
|
|
80
|
+
assets: localAssets("assets-out/2.1.11"),
|
|
81
|
+
createCanvas: (w, h) => new Canvas(w, h),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const { toPngBuffer } = await renderer.render(doc);
|
|
85
|
+
await writeFile("out.png", await toPngBuffer());
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`localAssets` reads schema-2 `manifest.json` and the selected `1x` or `2x`
|
|
89
|
+
content-addressed render DB and atlas files from a pipeline output directory on
|
|
90
|
+
disk. Asset sources default to `2x`; callers can set `assetTier` when creating a
|
|
91
|
+
renderer. The render DB's `assetDensity` must match the selected tier or
|
|
92
|
+
`createRenderer` throws `AssetDensityMismatchError`.
|
|
93
|
+
|
|
94
|
+
## Package entry points
|
|
95
|
+
|
|
96
|
+
| Import | Contents |
|
|
97
|
+
| ---------------- | ------------------------------------------------------------------------ |
|
|
98
|
+
| `@rickyzhangca/fpsr` | Decode/encode, books, migrate, `createRenderer`, `cdnAssets`, core types |
|
|
99
|
+
| `@rickyzhangca/fpsr/planner` | `resolve`, `planDrawList`, draw-list helpers, belt/train/wire utilities |
|
|
100
|
+
| `@rickyzhangca/fpsr/canvas` | `executeDrawList`, background painters, silhouette helpers |
|
|
101
|
+
| `@rickyzhangca/fpsr/render-db` | Full `RenderDb` / entity / terrain type surface |
|
|
102
|
+
| `@rickyzhangca/fpsr/node` | `localAssets` (filesystem; requires `skia-canvas`) |
|
|
103
|
+
|
|
104
|
+
## Blueprint books
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { decode, listBlueprints, selectBlueprint } from "@rickyzhangca/fpsr";
|
|
108
|
+
|
|
109
|
+
const doc = decode(bookString);
|
|
110
|
+
const refs = listBlueprints(doc); // flattened tree with paths
|
|
111
|
+
|
|
112
|
+
const bp = selectBlueprint(doc, [0, 2]); // third child of first entry
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`selectBlueprint(doc)` without a path follows the book's `active_index` chain.
|
|
116
|
+
|
|
117
|
+
## Pure planning (no assets)
|
|
118
|
+
|
|
119
|
+
For tests, diff tooling, or custom backends, call the pure pipeline stages
|
|
120
|
+
directly — no images or network required beyond loading the render DB named by
|
|
121
|
+
`manifest.json`:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { decode, migrateTo2x, selectBlueprint } from "@rickyzhangca/fpsr";
|
|
125
|
+
import { planDrawList, resolve, serializeDrawList } from "@rickyzhangca/fpsr/planner";
|
|
126
|
+
import renderDb from "./render-db.json" with { type: "json" };
|
|
127
|
+
|
|
128
|
+
const bp = migrateTo2x(selectBlueprint(decode(source))); // optional; plan/resolve also migrate
|
|
129
|
+
const { entities, warnings } = resolve(bp, renderDb);
|
|
130
|
+
const list = planDrawList(bp, renderDb, { altMode: true });
|
|
131
|
+
const snapshot = serializeDrawList(list); // stable JSON for fixtures
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`serializeDrawList` rounds coordinates to four decimal places with stable key
|
|
135
|
+
order for reviewable text diffs.
|
|
136
|
+
|
|
137
|
+
## Backgrounds
|
|
138
|
+
|
|
139
|
+
`RenderOptions.background` is a discriminated union:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
await renderer.render(doc, { background: { type: "auto" } });
|
|
143
|
+
await renderer.render(doc, { background: { type: "checkerboard" } });
|
|
144
|
+
await renderer.render(doc, { background: { type: "space", planet: true, planetName: "nauvis" } });
|
|
145
|
+
await renderer.render(doc, { background: { type: "terrain", name: "dirt" } });
|
|
146
|
+
await renderer.render(doc, { background: { type: "none" } });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Unknown terrain names throw `UnknownTerrainBackgroundError` (they do not render
|
|
150
|
+
as transparent).
|
|
151
|
+
|
|
152
|
+
## AbortSignal
|
|
153
|
+
|
|
154
|
+
`renderer.render` / `measure` / `renderTiledPng` honor `signal`. Asset loads via
|
|
155
|
+
`cdnAssets` / `localAssets` accept `loadAtlasImage(i, tier, { signal })`.
|
|
156
|
+
|
|
157
|
+
**Semantics:** aborting rejects only the waiting caller with `AbortError`. Shared
|
|
158
|
+
in-flight fetches/decodes continue for other concurrent consumers of the same
|
|
159
|
+
cache key. Aborted waits never poison a successful cache entry; genuine load
|
|
160
|
+
failures clear the shared promise so a later call can retry.
|
|
161
|
+
|
|
162
|
+
## API reference
|
|
163
|
+
|
|
164
|
+
### Decoding
|
|
165
|
+
|
|
166
|
+
| Export | Kind | Description |
|
|
167
|
+
| ----------------------- | -------- | ----------------------------------------------------------------- |
|
|
168
|
+
| `decode` | function | Parse a blueprint string or raw JSON string → `BlueprintDocument` |
|
|
169
|
+
| `encode` | function | Serialize a `BlueprintDocument` back to a blueprint string |
|
|
170
|
+
| `migrateTo2x` | function | Migrate a 1.x `Blueprint` to 2.x shape (idempotent) |
|
|
171
|
+
| `migrateDocumentTo2x` | function | Migrate every nested blueprint in a document / book |
|
|
172
|
+
| `BLUEPRINT_ADAPTERS` | const | Ordered 1.x→2.x adapter registry |
|
|
173
|
+
| `BlueprintAdapter` | type | `{ id, apply }` adapter entry |
|
|
174
|
+
| `BlueprintDecodeError` | class | Thrown when `decode` fails |
|
|
175
|
+
| `BlueprintDecodeReason` | type | Error reason codes for decode failures |
|
|
176
|
+
| `decodeVersion` | function | Split encoded `version` into major/minor/patch |
|
|
177
|
+
|
|
178
|
+
### Blueprint books
|
|
179
|
+
|
|
180
|
+
| Export | Kind | Description |
|
|
181
|
+
| ----------------------- | -------- | ------------------------------------------------------- |
|
|
182
|
+
| `listBlueprints` | function | Flatten a book tree → `BlueprintRef[]` with index paths |
|
|
183
|
+
| `selectBlueprint` | function | Select a nested blueprint by optional `number[]` path |
|
|
184
|
+
| `BlueprintSelectError` | class | Thrown when book navigation fails |
|
|
185
|
+
| `BlueprintSelectReason` | type | Error reason codes for selection failures |
|
|
186
|
+
|
|
187
|
+
### Planner (`@rickyzhangca/fpsr/planner`)
|
|
188
|
+
|
|
189
|
+
| Export | Kind | Description |
|
|
190
|
+
| ------------------------- | -------- | -------------------------------------------------------------------------- |
|
|
191
|
+
| `resolve` | function | `(bp, renderDb, opts?)` → `{ entities, warnings }` |
|
|
192
|
+
| `planDrawList` | function | `(bp, renderDb, opts?)` → `DrawList` |
|
|
193
|
+
| `planDrawListWithOptions` | function | Same as plan; returns `{ drawList, profile? }` when `profile: true` |
|
|
194
|
+
| `analyzePlan` | function | `(bp, drawList, renderDb)` → resolve coverage + draw-list integrity checks |
|
|
195
|
+
| `PlanOptions` | type | `altMode`, `beltEndings`, optional `profile` |
|
|
196
|
+
| `serializeDrawList` | function | Stable JSON serialization for fixtures |
|
|
197
|
+
|
|
198
|
+
### Rendering (`@rickyzhangca/fpsr`)
|
|
199
|
+
|
|
200
|
+
| Export | Kind | Description |
|
|
201
|
+
| ------------------------------- | -------- | -------------------------------------------------------------------- |
|
|
202
|
+
| `createRenderer` | function | High-level async renderer (plan + load + paint) |
|
|
203
|
+
| `cdnAssets` / `localAssets` | function | HTTP / filesystem `AssetSource` |
|
|
204
|
+
| `MeasureOptions` | type | Layout-only options for `measure` |
|
|
205
|
+
| `RenderOptions` | type | Public paint options (`background`, `altMode`, `showCoordinates`, …) |
|
|
206
|
+
| `TiledPngOptions` | type | Full-resolution PNG options (no `profile` / canvas / maxOutputSize) |
|
|
207
|
+
| `RenderBackground` | type | Discriminated background mode |
|
|
208
|
+
| `AssetDensityMismatchError` | class | Thrown when render-db density does not match `assetTier` |
|
|
209
|
+
| `UnknownTerrainBackgroundError` | class | Thrown for unknown `background: { type: "terrain", name }` |
|
|
210
|
+
| `Renderer` | type | `{ measure, render, renderTiledPng, dispose }` |
|
|
211
|
+
|
|
212
|
+
`Renderer.dispose()` clears renderer-owned icon/silhouette caches only. It does
|
|
213
|
+
**not** call `AssetSource.dispose()` or close atlas images owned by the asset
|
|
214
|
+
source — those remain the caller's responsibility.
|
|
215
|
+
|
|
216
|
+
### Canvas (`@rickyzhangca/fpsr/canvas`)
|
|
217
|
+
|
|
218
|
+
| Export | Kind | Description |
|
|
219
|
+
| ----------------------- | -------- | --------------------------------- |
|
|
220
|
+
| `executeDrawList` | function | Paint a `DrawList` onto a context |
|
|
221
|
+
| `Canvas2DContextLike` | type | Minimal Canvas2D context |
|
|
222
|
+
| `drawTileCheckerboard` | function | Tile-aligned checkerboard fill |
|
|
223
|
+
| `drawSpaceBackground` | function | Procedural starfield |
|
|
224
|
+
| `drawTerrainBackground` | function | Named terrain patches |
|
|
225
|
+
|
|
226
|
+
### Blueprint / render-db types
|
|
227
|
+
|
|
228
|
+
| Export | Kind | Description |
|
|
229
|
+
| -------------------- | ---- | ---------------------------------------------- |
|
|
230
|
+
| `BlueprintDocument` | type | Exactly-one top-level wrapper (`blueprint`, …) |
|
|
231
|
+
| `BlueprintBookEntry` | type | Exactly-one book-slot content key |
|
|
232
|
+
| `RenderDb` | type | Pipeline-produced sprite/layer database |
|
|
233
|
+
| `FrameId` | type | Numeric frame index into `RenderDb.frames` |
|
|
234
|
+
| `FrameMeta` | type | Atlas frame trim and scale metadata |
|
|
235
|
+
|
|
236
|
+
## Asset hosting
|
|
237
|
+
|
|
238
|
+
Rendering requires a content-addressed **render database** and packed **sprite
|
|
239
|
+
atlases** in deterministic `1x` and `2x` tiers described by a schema-2
|
|
240
|
+
`manifest.json`. These are generated from **your own game files** using the offline pipeline in this repository
|
|
241
|
+
([`@fpsr/pipeline`](https://github.com/rickyzhangca/fpsr/tree/main/tools/pipeline)).
|
|
242
|
+
|
|
243
|
+
They are **never redistributed** with the npm package or committed to git. Host
|
|
244
|
+
them on private infrastructure (`cdnAssets`) or load from disk (`localAssets`).
|
|
245
|
+
|
|
246
|
+
## Versioning
|
|
247
|
+
|
|
248
|
+
- **npm package**: semver; breaking changes may land in 0.x.
|
|
249
|
+
- **Render database schema**: version `2` (see `RenderDb` in source).
|
|
250
|
+
- **Game target**: Factorio **2.1.11** exactly (vanilla + Space Age + Elevated
|
|
251
|
+
Rails + Quality).
|
|
252
|
+
|
|
253
|
+
A new game version requires regenerating assets and may require a matching
|
|
254
|
+
library release.
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
**Source code** in this package is released under the [MIT License](LICENSE).
|
|
259
|
+
|
|
260
|
+
**Factorio game assets** — all sprites, icons, and atlases are the property of
|
|
261
|
+
**Wube Software Ltd.** They are not included in this package and must not be
|
|
262
|
+
redistributed. Generate assets locally from a licensed Factorio installation via
|
|
263
|
+
`@fpsr/pipeline`. The monorepo commits a metadata-only render-db test fixture (no
|
|
264
|
+
pixel data); golden PNG test renders are not redistributed.
|
|
265
|
+
|
|
266
|
+
_Factorio is a trademark of Wube Software Ltd. This project is not affiliated
|
|
267
|
+
with or endorsed by Wube Software._
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/abort.ts
|
|
2
|
+
/**
|
|
3
|
+
* Abort helpers shared by asset sources and the renderer atlas cache.
|
|
4
|
+
*
|
|
5
|
+
* Semantics: aborting rejects only the waiting caller. Shared underlying work
|
|
6
|
+
* continues for other concurrent consumers and is never cancelled by a waiter.
|
|
7
|
+
*/
|
|
8
|
+
function abortError(message = "Operation aborted") {
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.name = "AbortError";
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
function throwIfAborted(signal, message) {
|
|
14
|
+
if (!signal?.aborted) return;
|
|
15
|
+
if (typeof signal.throwIfAborted === "function") signal.throwIfAborted();
|
|
16
|
+
throw abortError(message);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Race a shared promise against an AbortSignal without cancelling the shared work.
|
|
20
|
+
*/
|
|
21
|
+
function raceWithAbort(promise, signal) {
|
|
22
|
+
if (!signal) return promise;
|
|
23
|
+
throwIfAborted(signal);
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const onAbort = () => {
|
|
26
|
+
signal.removeEventListener("abort", onAbort);
|
|
27
|
+
reject(abortError());
|
|
28
|
+
};
|
|
29
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
30
|
+
promise.then((value) => {
|
|
31
|
+
signal.removeEventListener("abort", onAbort);
|
|
32
|
+
if (signal.aborted) reject(abortError());
|
|
33
|
+
else resolve(value);
|
|
34
|
+
}, (error) => {
|
|
35
|
+
signal.removeEventListener("abort", onAbort);
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { throwIfAborted as n, raceWithAbort as t };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=abort-CrgARaZd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-CrgARaZd.js","names":[],"sources":["../src/abort.ts"],"sourcesContent":["/**\n * Abort helpers shared by asset sources and the renderer atlas cache.\n *\n * Semantics: aborting rejects only the waiting caller. Shared underlying work\n * continues for other concurrent consumers and is never cancelled by a waiter.\n */\n\nexport function abortError(message = \"Operation aborted\"): Error {\n const error = new Error(message);\n error.name = \"AbortError\";\n return error;\n}\n\nexport function throwIfAborted(signal?: AbortSignal, message?: string): void {\n if (!signal?.aborted) return;\n if (typeof signal.throwIfAborted === \"function\") signal.throwIfAborted();\n throw abortError(message);\n}\n\n/**\n * Race a shared promise against an AbortSignal without cancelling the shared work.\n */\nexport function raceWithAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) return promise;\n throwIfAborted(signal);\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(abortError());\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n promise.then(\n (value) => {\n signal.removeEventListener(\"abort\", onAbort);\n if (signal.aborted) reject(abortError());\n else resolve(value);\n },\n (error) => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(error);\n },\n );\n });\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,WAAW,UAAU,qBAA4B;CAC/D,MAAM,QAAQ,IAAI,MAAM,OAAO;CAC/B,MAAM,OAAO;CACb,OAAO;AACT;AAEA,SAAgB,eAAe,QAAsB,SAAwB;CAC3E,IAAI,CAAC,QAAQ,SAAS;CACtB,IAAI,OAAO,OAAO,mBAAmB,YAAY,OAAO,eAAe;CACvE,MAAM,WAAW,OAAO;AAC1B;;;;AAKA,SAAgB,cAAiB,SAAqB,QAAkC;CACtF,IAAI,CAAC,QAAQ,OAAO;CACpB,eAAe,MAAM;CACrB,OAAO,IAAI,SAAY,SAAS,WAAW;EACzC,MAAM,gBAAgB;GACpB,OAAO,oBAAoB,SAAS,OAAO;GAC3C,OAAO,WAAW,CAAC;EACrB;EACA,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EACxD,QAAQ,MACL,UAAU;GACT,OAAO,oBAAoB,SAAS,OAAO;GAC3C,IAAI,OAAO,SAAS,OAAO,WAAW,CAAC;QAClC,QAAQ,KAAK;EACpB,IACC,UAAU;GACT,OAAO,oBAAoB,SAAS,OAAO;GAC3C,OAAO,KAAK;EACd,CACF;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { c as ImageSource, t as AssetLoadOptions } from "./host-DdX4g1YM.js";
|
|
2
|
+
import { v as RenderDb } from "./render-db-BFQIspDB.js";
|
|
3
|
+
import { t as AssetEvent } from "./profile-CTN1Q5SI.js";
|
|
4
|
+
|
|
5
|
+
//#region src/assets.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Pluggable atlas / render-db loader. Browser uses ImageBitmap; node uses
|
|
8
|
+
* skia-canvas Image (via fpsr/node).
|
|
9
|
+
*
|
|
10
|
+
* ## AbortSignal semantics
|
|
11
|
+
*
|
|
12
|
+
* `loadRenderDb` / `loadAtlasImage` accept an optional `signal` on the load
|
|
13
|
+
* options. Aborting rejects **only the waiting caller** with an `AbortError`.
|
|
14
|
+
* Shared in-flight network/decode work is **not** cancelled for other
|
|
15
|
+
* concurrent consumers of the same cache key. Aborted waits never write into
|
|
16
|
+
* or delete a successful cache entry; only genuine load failures clear the
|
|
17
|
+
* shared promise slot so a later call can retry.
|
|
18
|
+
*/
|
|
19
|
+
interface AssetSource {
|
|
20
|
+
loadRenderDb(tier?: AssetTier, options?: AssetLoadOptions): Promise<RenderDb>;
|
|
21
|
+
loadAtlasImage(index: number, tier?: AssetTier, options?: AssetLoadOptions): Promise<ImageSource>;
|
|
22
|
+
/**
|
|
23
|
+
* Optional: register canvas text fonts listed in the asset manifest (e.g. DejaVu).
|
|
24
|
+
* Missing fonts are ignored so renders still work with CSS fallbacks.
|
|
25
|
+
*/
|
|
26
|
+
ensureFonts?(options?: AssetLoadOptions): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: release retained decoded images / clear caches.
|
|
29
|
+
* Ownership stays with the AssetSource caller — Renderer.dispose never invokes this.
|
|
30
|
+
*/
|
|
31
|
+
dispose?(): void;
|
|
32
|
+
}
|
|
33
|
+
type AssetTier = "1x" | "2x";
|
|
34
|
+
interface ManifestAtlas {
|
|
35
|
+
file: string;
|
|
36
|
+
w: number;
|
|
37
|
+
h: number;
|
|
38
|
+
sha256?: string;
|
|
39
|
+
}
|
|
40
|
+
interface ManifestFont {
|
|
41
|
+
file: string;
|
|
42
|
+
family: string;
|
|
43
|
+
sha256?: string;
|
|
44
|
+
bytes?: number;
|
|
45
|
+
}
|
|
46
|
+
interface AssetManifest {
|
|
47
|
+
schema: 2;
|
|
48
|
+
gameVersion: string;
|
|
49
|
+
mods: string[];
|
|
50
|
+
tiers: Record<AssetTier, AssetTierManifest>;
|
|
51
|
+
/** Optional bundled TTFs for canvas text (e.g. DejaVu Sans). */
|
|
52
|
+
fonts?: ManifestFont[];
|
|
53
|
+
}
|
|
54
|
+
interface AssetTierManifest {
|
|
55
|
+
density: 1 | 2;
|
|
56
|
+
atlases: ManifestAtlas[];
|
|
57
|
+
renderDb: {
|
|
58
|
+
file: string;
|
|
59
|
+
sha256: string;
|
|
60
|
+
bytes?: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
interface CdnAssetsOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Decode an atlas blob when `createImageBitmap` is unavailable (e.g. Node 22
|
|
66
|
+
* with fetch but no ImageBitmap). Required for node consumers of cdnAssets;
|
|
67
|
+
* browser builds ignore this when createImageBitmap exists.
|
|
68
|
+
*/
|
|
69
|
+
decodeImage?: (blob: Blob, signal?: AbortSignal) => Promise<ImageSource>;
|
|
70
|
+
fetchImpl?: typeof fetch;
|
|
71
|
+
/** Maximum simultaneous bitmap decodes. Fetches remain concurrent. Default: 2. */
|
|
72
|
+
maxConcurrentDecodes?: number;
|
|
73
|
+
/** Optional sink for fetch/decode timing events (profiling). */
|
|
74
|
+
onAssetEvent?: (event: AssetEvent) => void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Fetch-based AssetSource for CDN (or any HTTP) layouts:
|
|
78
|
+
* {baseUrl}/manifest.json
|
|
79
|
+
* {baseUrl}/{manifest.tiers[tier].renderDb.file}
|
|
80
|
+
* {baseUrl}/{atlas.file}
|
|
81
|
+
*/
|
|
82
|
+
declare function cdnAssets(baseUrl: string, options?: CdnAssetsOptions): AssetSource;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { CdnAssetsOptions as a, AssetTierManifest as i, AssetSource as n, ManifestFont as o, AssetTier as r, cdnAssets as s, AssetManifest as t };
|
|
85
|
+
//# sourceMappingURL=assets-Dy4fqZBV.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets-Dy4fqZBV.d.ts","names":[],"sources":["../src/assets.ts"],"mappings":";;;;;;;AAkBA;;;;;;;;;;;UAAiB,WAAA;EACf,YAAA,CAAa,IAAA,GAAO,SAAA,EAAW,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,QAAA;EACpE,cAAA,CAAe,KAAA,UAAe,IAAA,GAAO,SAAA,EAAW,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,WAAA;EAKpC;;;;EAAjD,WAAA,EAAa,OAAA,GAAU,gBAAA,GAAmB,OAAA;EANX;;;;EAW/B,OAAA;AAAA;AAAA,KAGU,SAAA;AAAA,UAEK,aAAA;EACf,IAAA;EACA,CAAA;EACA,CAAA;EACA,MAAA;AAAA;AAAA,UAGe,YAAA;EACf,IAAA;EACA,MAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAA,UAGe,aAAA;EACf,MAAA;EACA,WAAA;EACA,IAAA;EACA,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,iBAAA;EAlBG;EAoB5B,KAAA,GAAQ,YAAA;AAAA;AAAA,UAGO,iBAAA;EACf,OAAA;EACA,OAAA,EAAS,aAAa;EACtB,QAAA;IACE,IAAA;IACA,MAAA;IACA,KAAA;EAAA;AAAA;AAAA,UAIa,gBAAA;EAzBf;;;;;EA+BA,WAAA,IAAe,IAAA,EAAM,IAAA,EAAM,MAAA,GAAS,WAAA,KAAgB,OAAA,CAAQ,WAAA;EAC5D,SAAA,UAAmB,KAAA;EA1BS;EA4B5B,oBAAA;EAxBc;EA0Bd,YAAA,IAAgB,KAAA,EAAO,UAAA;AAAA;;;;;;;iBAoFT,SAAA,CAAU,OAAA,UAAiB,OAAA,GAAU,gBAAA,GAAmB,WAAW"}
|
package/dist/canvas.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { c as ImageSource, s as ImageDataLike } from "./host-DdX4g1YM.js";
|
|
2
|
+
import { h as FrameMeta, w as TerrainPatchBackground } from "./render-db-BFQIspDB.js";
|
|
3
|
+
import { n as ExecuteDrawListOptions, r as ExecuteDrawListStats, t as Canvas2DContextLike } from "./types-CHgZBSra.js";
|
|
4
|
+
import { c as perfMark, l as perfMeasure, p as DrawList, s as nowMs, u as TileFrame } from "./profile-CTN1Q5SI.js";
|
|
5
|
+
|
|
6
|
+
//#region src/canvas2d/execute.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Execute a sorted draw list against a Canvas2D context.
|
|
9
|
+
* `images[i]` is the atlas image for atlas index i.
|
|
10
|
+
*/
|
|
11
|
+
declare function executeDrawList(ctx: Canvas2DContextLike, list: DrawList, images: ImageSource[], opts: ExecuteDrawListOptions): void;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/icon-silhouette.d.ts
|
|
14
|
+
/** Pixel radius for feMorphology-style alpha dilation on entity-info icons. */
|
|
15
|
+
declare const ENTITY_INFO_SILHOUETTE_RADIUS_PX = 12;
|
|
16
|
+
/** Soft blur applied after dilation to feather the silhouette edge. */
|
|
17
|
+
declare const ENTITY_INFO_SILHOUETTE_BLUR_PX = 16;
|
|
18
|
+
/** Total source-pixel padding around icon crops for spread + blur. */
|
|
19
|
+
declare function entityInfoSilhouettePadPx(dilateRadius?: number, blurRadius?: number): number;
|
|
20
|
+
interface ImageDataContext {
|
|
21
|
+
drawImage(image: ImageSource, dx: number, dy: number): void;
|
|
22
|
+
createImageData?(sw: number, sh: number): ImageDataLike;
|
|
23
|
+
getImageData(sx: number, sy: number, sw: number, sh: number): ImageDataLike;
|
|
24
|
+
putImageData(data: ImageDataLike, dx: number, dy: number): void;
|
|
25
|
+
}
|
|
26
|
+
interface SilhouetteCanvasLike {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
getContext(type: "2d"): ImageDataContext | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Dilate and blur an isolated icon crop into a padded black silhouette image.
|
|
33
|
+
* Returns undefined when the canvas context cannot read/write ImageData.
|
|
34
|
+
*/
|
|
35
|
+
declare function bakeEntityInfoSilhouette(iconImage: ImageSource, iconWidth: number, iconHeight: number, createCanvas: (width: number, height: number) => SilhouetteCanvasLike, dilateRadius?: number, blurRadius?: number): ImageSource | undefined;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/coordinate-overlay.d.ts
|
|
38
|
+
/**
|
|
39
|
+
* Draw map-space tile grid lines and coordinate labels over a rendered preview.
|
|
40
|
+
* Canvas (0,0) corresponds to `tileFrame.minX/minY`.
|
|
41
|
+
*/
|
|
42
|
+
declare function drawCoordinateOverlay(ctx: Canvas2DContextLike, tileFrame: TileFrame, pixelsPerTile: number, width: number, height: number, outputTileFrame?: TileFrame): void;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/checkerboard.d.ts
|
|
45
|
+
/** Fill `width`×`height` with an axis-aligned checkerboard; one cell = `pixelsPerTile` px. */
|
|
46
|
+
declare function drawTileCheckerboard(ctx: Canvas2DContextLike, width: number, height: number, pixelsPerTile: number, tileOffsetX?: number, tileOffsetY?: number): void;
|
|
47
|
+
/** Paint an image on top of a tile-aligned checkerboard (for external reference PNGs). */
|
|
48
|
+
declare function blitWithTileCheckerboard(canvas: {
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
getContext(type: "2d"): Canvas2DContextLike | null;
|
|
52
|
+
}, image: ImageSource, width: number, height: number, pixelsPerTile: number): void;
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/space-background.d.ts
|
|
55
|
+
interface SpacePlanetDecoration {
|
|
56
|
+
frame: FrameMeta;
|
|
57
|
+
image: ImageSource;
|
|
58
|
+
}
|
|
59
|
+
interface DrawSpaceBackgroundOptions {
|
|
60
|
+
/** Optional starmap planet drawn bottom-left after the starfield. */
|
|
61
|
+
planet?: SpacePlanetDecoration;
|
|
62
|
+
/** Stable salt; selected planets use different skies while rerenders remain reproducible. */
|
|
63
|
+
seed?: number;
|
|
64
|
+
/** Full output dimensions and this viewport's offset within it (for tiled rendering). */
|
|
65
|
+
viewport?: {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
fullWidth: number;
|
|
69
|
+
fullHeight: number;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Draw the space-platform planet peeking from the bottom-left, matching
|
|
74
|
+
* Factorio's backdrop offset ratios scaled to the canvas.
|
|
75
|
+
*/
|
|
76
|
+
declare function drawSpacePlanet(ctx: Canvas2DContextLike, width: number, height: number, planet: SpacePlanetDecoration, viewport?: DrawSpaceBackgroundOptions["viewport"]): void;
|
|
77
|
+
/** Fill `width`×`height` with a layered deterministic starfield. */
|
|
78
|
+
declare function drawSpaceBackground(ctx: Canvas2DContextLike, width: number, height: number, options?: DrawSpaceBackgroundOptions): void;
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/terrain-background.d.ts
|
|
81
|
+
interface DrawTerrainBackgroundOptions {
|
|
82
|
+
tileFrame: TileFrame;
|
|
83
|
+
pixelsPerTile: number;
|
|
84
|
+
frames: FrameMeta[];
|
|
85
|
+
images: ImageSource[];
|
|
86
|
+
background?: TerrainPatchBackground;
|
|
87
|
+
fallbackColor: [number, number, number, number];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Paint an infinite terrain plane anchored to absolute map coordinates.
|
|
91
|
+
* Only complete, untransformed Factorio patches are drawn. Larger regions may
|
|
92
|
+
* be subdivided into complete smaller authored patches, so every destination is
|
|
93
|
+
* covered exactly once and every texture edge keeps its intended orientation.
|
|
94
|
+
*/
|
|
95
|
+
declare function drawTerrainBackground(ctx: Canvas2DContextLike, width: number, height: number, options: DrawTerrainBackgroundOptions): void;
|
|
96
|
+
//#endregion
|
|
97
|
+
export { type Canvas2DContextLike, type DrawSpaceBackgroundOptions, type DrawTerrainBackgroundOptions, ENTITY_INFO_SILHOUETTE_BLUR_PX, ENTITY_INFO_SILHOUETTE_RADIUS_PX, type ExecuteDrawListOptions, type ExecuteDrawListStats, type SpacePlanetDecoration, bakeEntityInfoSilhouette, blitWithTileCheckerboard, drawCoordinateOverlay, drawSpaceBackground, drawSpacePlanet, drawTerrainBackground, drawTileCheckerboard, entityInfoSilhouettePadPx, executeDrawList, nowMs, perfMark, perfMeasure };
|
|
98
|
+
//# sourceMappingURL=canvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas.d.ts","names":[],"sources":["../src/canvas2d/execute.ts","../src/icon-silhouette.ts","../src/coordinate-overlay.ts","../src/checkerboard.ts","../src/space-background.ts","../src/terrain-background.ts"],"mappings":";;;;;;;;;AAyBA;iBAAgB,eAAA,CACd,GAAA,EAAK,mBAAA,EACL,IAAA,EAAM,QAAA,EACN,MAAA,EAAQ,WAAA,IACR,IAAA,EAAM,sBAAA;;;;cC1BK,gCAAA;;cAEA,8BAAA;;iBAGG,yBAAA,CACd,YAAA,WACA,UAA2C;AAAA,UAK5B,gBAAA;EACf,SAAA,CAAU,KAAA,EAAO,WAAA,EAAa,EAAA,UAAY,EAAA;EAC1C,eAAA,EAAiB,EAAA,UAAY,EAAA,WAAa,aAAA;EAC1C,YAAA,CAAa,EAAA,UAAY,EAAA,UAAY,EAAA,UAAY,EAAA,WAAa,aAAA;EAC9D,YAAA,CAAa,IAAA,EAAM,aAAA,EAAe,EAAA,UAAY,EAAA;AAAA;AAAA,UAuK/B,oBAAA;EACf,KAAA;EACA,MAAA;EACA,UAAA,CAAW,IAAA,SAAa,gBAAgB;AAAA;;;AA1L1C;;iBAiOgB,wBAAA,CACd,SAAA,EAAW,WAAA,EACX,SAAA,UACA,UAAA,UACA,YAAA,GAAe,KAAA,UAAe,MAAA,aAAmB,oBAAA,EACjD,YAAA,WACA,UAAA,YACC,WAAA;;;;;;;iBCzNa,qBAAA,CACd,GAAA,EAAK,mBAAA,EACL,SAAA,EAAW,SAAA,EACX,aAAA,UACA,KAAA,UACA,MAAA,UACA,eAAA,GAAiB,SAAA;;;;iBCjBH,oBAAA,CACd,GAAA,EAAK,mBAAmB,EACxB,KAAA,UACA,MAAA,UACA,aAAA,UACA,WAAA,WACA,WAAA;;iBAoBc,wBAAA,CACd,MAAA;EACE,KAAA;EACA,MAAA;EACA,UAAA,CAAW,IAAA,SAAa,mBAAA;AAAA,GAE1B,KAAA,EAAO,WAAW,EAClB,KAAA,UACA,MAAA,UACA,aAAA;;;UCOe,qBAAA;EACf,KAAA,EAAO,SAAA;EACP,KAAA,EAAO,WAAW;AAAA;AAAA,UAGH,0BAAA;;EAEf,MAAA,GAAS,qBAAqB;EJ7BxB;EI+BN,IAAA;EJ7BM;EI+BN,QAAA;IACE,CAAA;IACA,CAAA;IACA,SAAA;IACA,UAAA;EAAA;AAAA;;;;;iBAQY,eAAA,CACd,GAAA,EAAK,mBAAA,EACL,KAAA,UACA,MAAA,UACA,MAAA,EAAQ,qBAAA,EACR,QAAA,GAAW,0BAAA;;iBAiFG,mBAAA,CACd,GAAA,EAAK,mBAAA,EACL,KAAA,UACA,MAAA,UACA,OAAA,GAAU,0BAA0B;;;UC9BrB,4BAAA;EACf,SAAA,EAAW,SAAA;EACX,aAAA;EACA,MAAA,EAAQ,SAAA;EACR,MAAA,EAAQ,WAAA;EACR,UAAA,GAAa,sBAAA;EACb,aAAA;AAAA;;;;;;;iBASc,qBAAA,CACd,GAAA,EAAK,mBAAA,EACL,KAAA,UACA,MAAA,UACA,OAAA,EAAS,4BAA4B"}
|
package/dist/canvas.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { d as bakeEntityInfoSilhouette, l as ENTITY_INFO_SILHOUETTE_BLUR_PX, n as perfMark, p as entityInfoSilhouettePadPx, r as perfMeasure, t as nowMs, u as ENTITY_INFO_SILHOUETTE_RADIUS_PX } from "./profile-DR4i6HDp.js";
|
|
2
|
+
import { a as drawCoordinateOverlay, i as drawSpacePlanet, n as drawTerrainBackground, o as blitWithTileCheckerboard, r as drawSpaceBackground, s as drawTileCheckerboard, t as executeDrawList } from "./execute-CRZV5zw1.js";
|
|
3
|
+
export { ENTITY_INFO_SILHOUETTE_BLUR_PX, ENTITY_INFO_SILHOUETTE_RADIUS_PX, bakeEntityInfoSilhouette, blitWithTileCheckerboard, drawCoordinateOverlay, drawSpaceBackground, drawSpacePlanet, drawTerrainBackground, drawTileCheckerboard, entityInfoSilhouettePadPx, executeDrawList, nowMs, perfMark, perfMeasure };
|