brs-js 3.0.1 → 3.2.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/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # brs.js
2
2
 
3
- Read and write Brickadia save files (.brs, .brz)
4
-
5
- Currently supports all BRS save versions. BRS Files are no longer supported as of Brickadia EA3.
3
+ Read and write Brickadia save files (.brz, .brdb, .brs)
6
4
 
7
5
  **Warning:** **Unreal Engine uses numbers potentially larger than Javascript can handle.**
8
6
 
@@ -28,7 +26,217 @@ Script: `window.BRS`
28
26
 
29
27
  ### Examples
30
28
 
31
- Examples are available in the `examples/` directory. All `.js` examples are for node, `.html` are for web.
29
+ Examples are available in the `examples/` directory. All `.mjs` examples are for node, `.html` are for web.
30
+
31
+ ## Brickadia Worlds (.brz, .brdb)
32
+
33
+ brs-js reads and writes both Brickadia World formats — the `.brz` archive
34
+ and the live `.brdb` SQLite database ([see below](#brdb-sqlite-worlds)) —
35
+ covering bricks, components, wires, entities, multiple grids
36
+ (microchips), and prefab metadata. There are two writers, and each can
37
+ target either format:
38
+
39
+ - the `World` builder — author worlds from scratch, including sub-grids,
40
+ entities, microchips, and prefabs (`w.toBrz()` or `db.save(desc, w)`);
41
+ - `writeBrzLegacy` — convert the same legacy `WriteSaveObject` that
42
+ `write()` takes, except that `components` on bricks is a modern typed
43
+ array (the legacy .brs component map is not converted) and `wires` on
44
+ the save uses modern component and port names.
45
+
46
+ ```js
47
+ import { writeBrzLegacy, WorldReader } from 'brs-js';
48
+
49
+ const brz = writeBrzLegacy({
50
+ description: 'my world',
51
+ bricks: [{ size: [5, 5, 6], position: [0, 0, 6], color: [255, 0, 0] }],
52
+ });
53
+
54
+ // WorldReader is lazy: schemas are parsed once and cached, and chunk
55
+ // payloads are only decoded when you ask for them.
56
+ const world = WorldReader.from(brz);
57
+ world.bundle(); // Meta/Bundle.json
58
+ world.brickAssets(); // basic asset names followed by procedural
59
+ world.brickOwners(); // owner rows (PUBLIC excluded)
60
+ world.gridIds(); // [1] plus any entity sub-grids
61
+ for (const brick of world.bricks()) {
62
+ // decoded chunk by chunk
63
+ }
64
+ for (const entity of world.entities()) {
65
+ // dynamic grids, microchip grids, ...
66
+ }
67
+ ```
68
+
69
+ ### World builder
70
+
71
+ `World` mirrors the reference Rust crate's `wrapper::World`: add bricks,
72
+ components, wires, entities, and microchip grids, then serialize with
73
+ `toBrz()`. Brick assets and materials are referenced by name, and the
74
+ generated `COMPONENTS`/`BRICK_ASSETS` catalogs provide typo-safe names
75
+ with editor completion.
76
+
77
+ ```js
78
+ import { Brdb, World, brdb } from 'brs-js';
79
+ const { COMPONENTS, BRICK_ASSETS } = brdb;
80
+
81
+ const w = new World();
82
+
83
+ // Main-grid bricks. addBrick returns a handle used by wires and links.
84
+ const light = w.addBrick({
85
+ position: [0, 0, 6], // default asset is a 1x1 PB_DefaultBrick
86
+ color: [255, 255, 255],
87
+ components: [
88
+ // omitted data fields take the game's default values
89
+ { type: COMPONENTS.PointLight.NAME, data: { Brightness: 200 } },
90
+ ],
91
+ });
92
+ w.addBrick({
93
+ asset: BRICK_ASSETS.B_2x2F_Round,
94
+ position: [20, 0, 2],
95
+ });
96
+
97
+ // serialize to a .brz archive...
98
+ const bytes = w.toBrz({ bundle: { description: 'My World' } });
99
+
100
+ // ...or write the same World into a .brdb as a revision
101
+ const db = await Brdb.openOrCreate('My World.brdb');
102
+ db.save('built with brs-js', w);
103
+ ```
104
+
105
+ Wires connect component ports by name; endpoints may be in different
106
+ grids or chunks (the writer emits remote wire rows automatically):
107
+
108
+ ```js
109
+ const TARGET = COMPONENTS.Target;
110
+ const PRINT = COMPONENTS.Exec_PrintToConsole;
111
+
112
+ const sensor = w.addBrick({
113
+ asset: TARGET.BRICK,
114
+ position: [0, 0, 6],
115
+ components: [{ type: TARGET.NAME }],
116
+ });
117
+ const printer = w.addBrick({
118
+ asset: PRINT.BRICK,
119
+ position: [20, 0, 2],
120
+ components: [{ type: PRINT.NAME, data: { Text: 'hit!' } }],
121
+ });
122
+ w.addWire(
123
+ { brick: sensor, component_type: TARGET.NAME, port: TARGET.PORTS.bJustHit },
124
+ { brick: printer, component_type: PRINT.NAME, port: PRINT.PORTS.Exec }
125
+ );
126
+ ```
127
+
128
+ Microchips place an outer shell brick linked to an inner brick grid.
129
+ `makePrefab` turns the bundle into a prefab (`Meta/Prefab.json` with
130
+ pivots/bounds from the main-grid bricks) so the `.brz` pastes like a
131
+ native copied selection — see `examples/writeMicrochip.mjs`:
132
+
133
+ ```js
134
+ const CHIP_IN = COMPONENTS.MicrochipInput;
135
+ const CHIP_OUT = COMPONENTS.MicrochipOutput;
136
+
137
+ const w = new World();
138
+ const { grid } = w.addMicrochip({ position: [0, 0, 2] });
139
+
140
+ // chip contents live in the inner grid (positions are grid-local)
141
+ const input = w.addBrick(
142
+ {
143
+ asset: CHIP_IN.BRICK,
144
+ position: [0, 0, 2],
145
+ components: [{ type: CHIP_IN.NAME, data: { PortLabel: 'In' } }],
146
+ },
147
+ grid
148
+ );
149
+ const output = w.addBrick(
150
+ {
151
+ asset: CHIP_OUT.BRICK,
152
+ position: [20, 0, 2],
153
+ components: [{ type: CHIP_OUT.NAME, data: { PortLabel: 'Out' } }],
154
+ },
155
+ grid
156
+ );
157
+ w.addWire(
158
+ { brick: input, component_type: CHIP_IN.NAME, port: CHIP_IN.PORTS.RER_Output },
159
+ { brick: output, component_type: CHIP_OUT.NAME, port: CHIP_OUT.PORTS.RER_Input }
160
+ );
161
+
162
+ w.makePrefab({ isMicrochipPrefab: true });
163
+ writeFileSync('chip.brz', w.toBrz());
164
+ ```
165
+
166
+ Standalone entities and dynamic sub-grids:
167
+
168
+ ```js
169
+ const owner = w.addOwner({ id: '...uuid...', name: 'cake' });
170
+
171
+ // a frozen dynamic brick grid floating at Z 40, holding one brick
172
+ const grid = w.addBrickGrid({ frozen: true, location: { X: 0, Y: 0, Z: 40 } });
173
+ w.addBrick({ position: [0, 0, 3], owner_index: owner }, grid);
174
+
175
+ // or a bare entity
176
+ w.addEntity({ type: 'Entity_DynamicBrickGrid', frozen: true });
177
+ ```
178
+
179
+ ### Compression
180
+
181
+ Blobs are stored uncompressed by default, which is valid and matches the
182
+ reference implementation byte for byte. To compress with zstd (Node 22.15
183
+ or newer):
184
+
185
+ ```js
186
+ import { zstdCompressSync, constants } from 'node:zlib';
187
+ const brz = writeBrzLegacy(save, {
188
+ compress: data =>
189
+ zstdCompressSync(data, {
190
+ params: { [constants.ZSTD_c_compressionLevel]: 14 },
191
+ }),
192
+ });
193
+ ```
194
+
195
+ ### Reading notes
196
+
197
+ Reading applies a few normalizations:
198
+
199
+ - Brick order round-trips per chunk rather than in global save order.
200
+ - `collision.tool` always reads `true` because the format does not store it.
201
+ - Palette color indices are resolved to `[r, g, b]` at write time.
202
+ - `save.map` is not mapped; pass `{ environment }` in the options instead.
203
+
204
+ The cross-language fixture tests skip unless fixtures have been generated.
205
+ To enable them, run `just fixtures` with a crate checkout at `../brdb` (or
206
+ set `BRDB_CRATE`).
207
+
208
+ ### .brdb (SQLite worlds)
209
+
210
+ `.brdb` is the game's live world format: the same virtual filesystem as
211
+ `.brz`, stored in SQLite with full revision history. Reading and writing
212
+ requires `better-sqlite3` (an optional peer dependency, node only):
213
+
214
+ npm install better-sqlite3
215
+
216
+ ```js
217
+ import { Brdb } from 'brs-js';
218
+
219
+ const db = await Brdb.open('MyWorld.brdb');
220
+ const reader = db.worldReader(); // same surface as WorldReader.from(brz)
221
+ for (const brick of reader.bricks()) console.log(brick.position);
222
+
223
+ const out = await Brdb.create('New.brdb');
224
+ // save() takes a World builder instance or a legacy save object
225
+ out.save('initial save', {
226
+ bricks: [{ size: [5, 5, 6], position: [0, 0, 6] }],
227
+ });
228
+ ```
229
+
230
+ Every `save`/`writePending` call is one revision: unchanged files are shared,
231
+ changed files are soft-deleted and reinserted, and blobs are deduplicated by
232
+ BLAKE3 hash. Blobs compress with zstd level 14 when node's zlib supports it
233
+ (pass `compress: null` to store raw). Advanced: `new Brdb(db)` wraps any
234
+ SQLite handle with a compatible synchronous API, with no dependency at all.
235
+
236
+ ## Legacy saves (.brs)
237
+
238
+ Supports all BRS save versions. BRS files are no longer supported by the
239
+ game as of Brickadia EA3 — use `.brz`/`.brdb` above for current builds.
32
240
 
33
241
  ### Save Object
34
242
 
@@ -339,88 +547,6 @@ Notes:
339
547
  | B_1x1_Gate_Equal | |
340
548
  | B_1x1_Gate_NOT_Bitwise | |
341
549
 
342
- ## Brickadia Worlds (.brz)
343
-
344
- brs-js can write and read the Brickadia World archive format (`.brz`).
345
- Writing covers bricks, components, and wires; entities can be read but not
346
- yet written. `writeBrzLegacy` converts the same legacy `WriteSaveObject`
347
- that `write()` takes, except that `components` on bricks is a modern typed
348
- array (the legacy .brs component map is not converted) and `wires` on the
349
- save uses modern component and port names. A builder-style world API for
350
- authoring saves from scratch is planned:
351
-
352
- ```js
353
- import { writeBrzLegacy, WorldReader } from 'brs-js';
354
-
355
- const brz = writeBrzLegacy({
356
- description: 'my world',
357
- bricks: [{ size: [5, 5, 6], position: [0, 0, 6], color: [255, 0, 0] }],
358
- });
359
-
360
- // WorldReader is lazy: schemas are parsed once and cached, and chunk
361
- // payloads are only decoded when you ask for them.
362
- const world = WorldReader.from(brz);
363
- world.bundle(); // Meta/Bundle.json
364
- world.brickAssets(); // basic asset names followed by procedural
365
- world.brickOwners(); // owner rows (PUBLIC excluded)
366
- world.gridIds(); // [1] plus any entity sub-grids
367
- for (const brick of world.bricks()) {
368
- // decoded chunk by chunk
369
- }
370
- ```
371
-
372
- Blobs are stored uncompressed by default, which is valid and matches the
373
- reference implementation byte for byte. To compress with zstd (Node 22.15
374
- or newer):
375
-
376
- ```js
377
- import { zstdCompressSync, constants } from 'node:zlib';
378
- const brz = writeBrzLegacy(save, {
379
- compress: data =>
380
- zstdCompressSync(data, {
381
- params: { [constants.ZSTD_c_compressionLevel]: 14 },
382
- }),
383
- });
384
- ```
385
-
386
- Reading applies a few normalizations:
387
-
388
- - Brick order round-trips per chunk rather than in global save order.
389
- - `collision.tool` always reads `true` because the format does not store it.
390
- - Palette color indices are resolved to `[r, g, b]` at write time.
391
- - `save.map` is not mapped; pass `{ environment }` in the options instead.
392
-
393
- The cross-language fixture tests skip unless fixtures have been generated.
394
- To enable them, run `just fixtures` with a crate checkout at `../brdb` (or
395
- set `BRDB_CRATE`).
396
-
397
- ### .brdb (SQLite worlds)
398
-
399
- `.brdb` is the game's live world format: the same virtual filesystem as
400
- `.brz`, stored in SQLite with full revision history. Reading and writing
401
- requires `better-sqlite3` (an optional peer dependency, node only):
402
-
403
- npm install better-sqlite3
404
-
405
- ```js
406
- import { Brdb } from 'brs-js';
407
-
408
- const db = await Brdb.open('MyWorld.brdb');
409
- const reader = db.worldReader(); // same surface as WorldReader.from(brz)
410
- for (const brick of reader.bricks()) console.log(brick.position);
411
-
412
- const out = await Brdb.create('New.brdb');
413
- out.save('initial save', {
414
- bricks: [{ size: [5, 5, 6], position: [0, 0, 6] }],
415
- });
416
- ```
417
-
418
- Every `save`/`writePending` call is one revision: unchanged files are shared,
419
- changed files are soft-deleted and reinserted, and blobs are deduplicated by
420
- BLAKE3 hash. Blobs compress with zstd level 14 when node's zlib supports it
421
- (pass `compress: null` to store raw). Advanced: `new Brdb(db)` wraps any
422
- SQLite handle with a compatible synchronous API, with no dependency at all.
423
-
424
550
  ## Development
425
551
 
426
552
  NPM Scripts (`npm run <cmd>`)