@hyprune/schema 0.6.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -185,3 +185,208 @@ definition exists in 0.16 and the graphics properties differ only by the new key
185
185
  ## @hyprune/schema 0.6.0 (2026-10-11)
186
186
 
187
187
  Publishes IPC 0.20 and world format 0.8 to npm.
188
+
189
+ ## Object manifests 0.9, libraries and the library release record (RFC-0011)
190
+
191
+ New contract files; no IPC or world format change (world formats stay 0.1–0.8).
192
+ Object manifests use the world format numbering: a world may use objects whose
193
+ `formatVersion` is at most its own.
194
+
195
+ - `v0.9/object.schema.json` (`object.json`): `id` (slug, equals the directory
196
+ name), SemVer `version`, `name`, `category`, `tags`, a directory-relative
197
+ `scene`, `license` + `attribution`, rest-pose `bounds`, optional `thumbnail`
198
+ and `lighting`, `traits` and optional `slots`. Traits: `decorative` (0.8 node
199
+ animations), `collidable` (0.8 proxy), `movable` (`surfaces`, `upright`,
200
+ `scale`), `character` (measured `clips` with `walk` and `idle`, `height`,
201
+ `radius`, `accent`, asset-level `say`/`panel` interactions) and `seat`
202
+ (anchors, `capacity`). `mover`, `machine`, `mount` and `interactive` are
203
+ reserved and rejected with a reason. Slots `states`, `labels`, `spawns` and
204
+ `effects` (at most 8 each) are validated and inert.
205
+ - `v0.9/library.schema.json` (`library.json`): reverse-domain `id`, `version`,
206
+ `name`, `license` + `attribution` and 1–256 listed `objects`.
207
+ - `v0.9/library-releases.schema.json`: the meta repository's
208
+ `libraries/releases.json`, one record per `id@version` (`id`, `version`,
209
+ `formatVersion`, `url`, `sha256`, `nixHash`, `published`).
210
+ - **Draft** `v0.10/object.schema.json` and `v0.10/library.schema.json`: 0.9 plus
211
+ the `mover` trait, RFC-0013's `machine` trait (typed `inputs` with `repeat`,
212
+ `drives` in the mapping language, `primary`, `positions`, `recipes`) and the
213
+ slot additions (`labels` font/align/colour/plate, `spawns` track/items/maxRate,
214
+ `effects` kind/colour/durationMs, new `tracks`, `stacks`, `toggles`, `ports`).
215
+ It may change until world format 0.10 is defined.
216
+ - `validate('object', manifest, undefined, format?, options)`: trait
217
+ combinations, origin conventions (floor, wall and ceiling within 1 cm; a
218
+ character's rest-pose feet within 5 cm), slot
219
+ ids, poses inside the bounds grown by 0.5 m, palettes, seat anchors, track
220
+ points and arcs, stack layouts, machine input references, drive targets and
221
+ output domains. With `options.gltf`: the format's glTF extension rules, node
222
+ names (unique, in the scene; the proxy may sit outside it), the 0.8 animation
223
+ and proxy rules, states-slot materials and nodes (one slot each),
224
+ `KHR_materials_variants` mappings (variant names of a states slot are palette
225
+ ids), character skins, clips and `extras.speed` (2%), triangle budgets
226
+ (50,000; characters 16,000; items 2,000; proxy 2,000) and bounds (exact for
227
+ axis-aligned meshes, else a containing box). `options.files`, `thumbnail`,
228
+ `measured` and `directory` add the directory limits, attribution coverage, the
229
+ 256 x 256 thumbnail, the 1 cm bounds check and texture and GPU budgets.
230
+ Results carry `warnings`.
231
+ - `validate('library', manifest, undefined, format?, {objects, files})` and
232
+ `validate('library-releases', record)`. `objectFormats` and `objectLimits` are
233
+ exported. The CLI accepts `object` (with an optional glTF/GLB), `library` and
234
+ `library-releases`.
235
+
236
+ ## IPC 0.21: markers, personal quests and focus sessions
237
+
238
+ - New grant `quest.control` (hello capabilities): every quest and focus mutation
239
+ and `guidance.set`.
240
+ - Required state keys: `markers {reveal, persistent, calm, coreDraw, list}`
241
+ (reveal `holdMs` 0 = toggle or 1000..15000, `fadeMs` 0..600; at most 64
242
+ markers with tier 1..4, labels up to 48 characters, one target of position,
243
+ zone, area, place or workspace), `quests {enabled, tracked, active[<=16],
244
+ counts, world: null}` and `focus {state, pausedState, sessionId,
245
+ phaseStartedAtMs, phaseEndsAtMs, phaseMs, remainingMs, creditedMs, crediting,
246
+ cycle, cycles, breakKind, countMode, target, calm}`. Times are monotonic ms.
247
+ - Methods: `markers.reveal {durationMs?}` (shell.control); `guidance.get`,
248
+ `guidance.set {patch}` returning `{config, defaults, overrides, ranges,
249
+ options, path, error}` over 21 flat dotted keys; `quests.list {states?,
250
+ sources?}`, `quests.get {questRef}`, `quests.track {questRef|null}`,
251
+ `quests.accept/abandon/resume/delete {questRef}`, `quests.create {quest}`
252
+ (`{revision, questRef}`), `quests.update {questRef, quest}`, `quests.mark
253
+ {questRef, stepId, objectiveId}`; `focus.start {target?, questRef?,
254
+ objectiveId?, overrides?}` (`{revision, sessionId}`),
255
+ `focus.pause/resume/skip/stop {}`, `focus.history {days?}` (at most 512
256
+ intervals). Personal quests: 1..16 steps, 1..8 `manual`, `reach` or `focus`
257
+ objectives per step.
258
+ - Events: `quest.progress` and `focus.phase` (`focusSessionId`; every event
259
+ carries the IPC `sessionId` and `revision`).
260
+ - `inputConfig` world mode gains `markers.reveal`; `overlayStyle` gains
261
+ required `questColor` (optional in the patch); `spatial.projection` markers
262
+ gain optional `markerId`.
263
+ - File schemas: `v0.21/quests-personal.schema.json`,
264
+ `v0.21/guidance.schema.json`, `v0.21/focus-history.schema.json`.
265
+ - Everything else is inherited from 0.20 unchanged; `session.hello` reports
266
+ `0.21`. Inheritance test included.
267
+
268
+ ## IPC 0.22: object catalog, user placements and libraries (RFC-0011 P1)
269
+
270
+ - New grant `objects.control` (hello capabilities): every overlay change and
271
+ library install/remove.
272
+ - Required state key `objects {catalogRevision, instances[<=512], pocket[<=16],
273
+ usage, limits}`. An instance is `{id, source (authored|user), ref, name,
274
+ areaId, pose, scale, state (active|hidden|missing|blocked|loading|suspended),
275
+ movable, locked, rule, path, activity, seat}`; `path`, `activity` and `seat`
276
+ are always null until P2. `usage`/`limits` count this world's user
277
+ placements (placements, assets, triangles, gpuBytes, collisionTriangles).
278
+ - Methods: `objects.catalog {}` (`{revision, entries[<=1024]}`; an entry carries
279
+ ref, source, id, version, formatVersion, name, description, category, tags,
280
+ traits, surfaces, scale range, bounds, triangles, gpuBytes,
281
+ collisionTriangles, an absolute thumbnail path, license, library, other
282
+ versions, state, placeable, reason and up to 8 errors);
283
+ `objects.pocket {ref}` (`{revision, itemId}`); `objects.place {ref, pose?,
284
+ scale?}` (`{revision, id}`); `objects.transform {id, pose, scale?}`;
285
+ `objects.remove {id}|{all:true}`; `objects.hide {id, hidden}`;
286
+ `objects.reset {id}|{all:true}`; `objects.configure {id, patch:{locked}}`;
287
+ `libraries.list {}`; `libraries.install {archive, sha256}` (`{revision, id,
288
+ version}`); `libraries.remove {id, version}`. Refs are
289
+ `package/<asset>`, `library/<id>@<semver>/<object>` or `user/<object>`.
290
+ - `editor.selectedKind` gains `node`; `editor.submode` gains `universal`;
291
+ `editor.unplaced` gains `objects` (pocket items, `count` up to 32); new
292
+ required `editor.gizmo {space, pivot, snap}`.
293
+ - Error `data` may carry `rule` (the placement rule or limit that refused a
294
+ method).
295
+ - Everything else is inherited from 0.21 unchanged; `session.hello` reports
296
+ `0.22`. Inheritance test included.
297
+
298
+ ## @hyprune/schema 0.7.0 (2026-10-12)
299
+
300
+ Publishes IPC 0.21 (markers, personal quests, focus sessions) and IPC 0.22 (object catalog, user placements, libraries), plus the object manifest (`object.json` 0.9 and the 0.10 draft), library and library release-record schemas.
301
+
302
+ ## World 0.9: characters, paths, seats and keepClear (RFC-0011 P2)
303
+
304
+ - `objectAssets[]` (at most 32, ids unique) take exactly one source: inline
305
+ `{id, scene, animations?, collision?, lighting?, traits?, slots?}` (the 0.8
306
+ asset plus object.json 0.9 `traits` and `slots`; `decorative`, `collidable`,
307
+ `movable`, `character` and `seat` are allowed, `mover`, `mount`, `machine` and
308
+ `interactive` rejected), `dir {id, dir}` (a package directory holding
309
+ `object.json`, object format at most the world's) or `library {id, library,
310
+ object}` (`library` is `<reverse-domain id>@<semver>`, `object` a slug).
311
+ `animations` is shorthand for `traits.decorative.animations` and `collision`
312
+ for `traits.collidable`; giving both forms of one is invalid.
313
+ - `objects[]` gain `path` (a `route` path; replaces `pose`), `reverse` (only
314
+ with `path`), `seat` (a `seats[]` id, `<objectId>/<anchorId>` or
315
+ `<objectId>`; characters without a path), `accent` (linear RGB 0..1),
316
+ `interactions` (1..8, replacing the asset's list) and `locked` (movable
317
+ assets). `pose` is required unless `path` is set and forbidden with it. A
318
+ character's `scale` must be 1; at most 16 character instances and 8 per path.
319
+ - Instance interactions `{id, label (1..32), prompt? (1..64), action}` with
320
+ `say {lines[1..8]}`, `waypoint {target: {zoneId}|{position}|{anchor}|{object}}`,
321
+ `guide {path}` (a route) or `panel {panel}`. `travel` and `quest` are
322
+ reserved and rejected. Asset-level interactions stay `say`/`panel`.
323
+ - `paths[]` (at most 64, 1,024 points in total): `{id, name?, use?
324
+ (route|track|generic, default generic), closed?, points[2..256] {position,
325
+ type: line|curve|arc}, speed? (0.1..30), stops?[<=32]}`. A stop is `{point,
326
+ seconds? (0.5..600), clip? (look_around|wave|nod|idle_fidget), face?
327
+ ({position}|{anchor}|{object}), seat?}` with at most one of clip, face and
328
+ seat, one stop per point and the point below the point count. Track stops
329
+ take only `point` and `seconds`; generic paths have no stops. An `arc` needs
330
+ a horizontal chord of at least 0.1 m.
331
+ - `seats[]` (at most 64): `{id, pose, height (0.2..1), use? (["sit"]),
332
+ approach? (0.3..2)}`. `keepClear[]` (at most 64): `{id, min, max}`, min
333
+ below max on every axis.
334
+ - `validate('world', manifest, undefined, '0.9', {assets})` adds unique ids,
335
+ asset, path, seat, waypoint and face references, route-only bindings,
336
+ character-only and movable-only fields, pose/path exclusivity, normalised
337
+ seat poses, arc chords, the point total, keepClear ordering and the
338
+ character limits. `assets` maps a dir or library asset id to its parsed
339
+ `object.json`, so its traits are checked like an inline asset's; without it
340
+ those checks skip the asset. Reserved traits and actions are reported with a
341
+ plain reason. `worldLimits` exports the limits.
342
+ - `validate('scene', gltf, undefined, '0.9', {world, asset})` reads an inline
343
+ asset's animations and proxy from either form and checks an inline
344
+ character's skin and clips; dir and library assets are checked as objects.
345
+ - `v0.9/common.schema.json` is the 0.8 common definitions under the 0.9 `$id`
346
+ (the directory's IPC, input, object and library schemas are untouched).
347
+ `examples/world-npcs.json` is a synthetic 0.9 manifest. Everything else is
348
+ inherited from 0.8 unchanged. Inheritance test included.
349
+
350
+ ## IPC 0.23: characters, paths and interactions (RFC-0011 P2)
351
+
352
+ - Required state key `npcs {motion (auto|always|still|off), state
353
+ (none|animating|paused|still|off), reason (null or up to 64 characters, e.g.
354
+ battery), usage {authored, user}, limits {authored (<=16), user (<=8)},
355
+ drawing, aimed, interaction}`. `drawing` is null or `{pathId, use, spawn:
356
+ null|{ref}, bind: null|objectId}` (a `paths.draw` in progress); `aimed` is null
357
+ or `{objectId, name, prompt, interactions[<=8] {id, label, type}}`;
358
+ `interaction` is null or `{objectId, interactionId|null, type
359
+ (choose|say|waypoint|guide|panel), label, choices[<=8] {id, label},
360
+ line|null, lineIndex, lineCount, panel|null}`.
361
+ - `objects.instances[]` gain required `character` (boolean) and `accent`
362
+ (null or linear RGB); `path` is null or a qualified path ref
363
+ (`package/<id>`, `user/<id>`), `activity` null or
364
+ `idle|walking|running|stopped|sitting|guiding|interacting|suspended`, `seat`
365
+ null or a seat ref (up to 192 characters).
366
+ - `editor.paths[]` gain required `use` (route|track|generic), `name`
367
+ (string|null), `speed` (number|null) and `stops` (at most 32, the world 0.9
368
+ stop shape).
369
+ - `interaction.reticle.action` gains `interact`. `graphicsConfig` and
370
+ `graphicsPatch` gain `npc_motion` (`auto`, `always`, `still`, `off`).
371
+ - Methods: `npcs.spawn {ref, path?, pose?, phase?, speed? (0.25..2), accent?,
372
+ reverse?}` (`{revision, id}`; path and pose exclusive, reverse needs a path);
373
+ `npcs.bind {id, path?, pose?, phase?, speed?, accent?, reverse?}` (at least one
374
+ setting); `npcs.disable {id, disabled}`; `npcs.remove {id}|{all:true}`;
375
+ `paths.list {}` (`{paths[<=192]}`: `{ref, source (package|user), id, name,
376
+ use, closed, points, speed, stops, walkable (null for non-routes),
377
+ problems[<=8], bound[<=8]}`); `paths.set {path: {id?, name?, use, closed,
378
+ points[2..256], speed?, stops?}}` (`{revision, id}`); `paths.remove {id,
379
+ unbind?}`; `paths.draw {use?, name?, spawn? {ref, phase?, speed?, accent?},
380
+ bind?}` (`{revision, id}`; spawn and bind exclusive); `paths.finish {}`
381
+ (`{revision, id|null, bound|null}`); `interaction.choose {objectId,
382
+ interactionId|null}`. Path params take a qualified ref or a bare id
383
+ (`package/<id>`); `paths.set/remove/draw/finish` ids are user path ids.
384
+ Grants: `npcs.*` and `paths.set/remove/draw/finish` need `objects.control`,
385
+ `paths.list` needs `state.read`, `interaction.choose` needs `world.control`.
386
+ - Everything else is inherited from 0.22 unchanged (no new grant or event);
387
+ `session.hello` reports `0.23` and lists the ten methods. Inheritance test
388
+ included.
389
+
390
+ ## @hyprune/schema 0.8.0 (2026-10-12)
391
+
392
+ Publishes IPC 0.23 and world format 0.9: characters and NPC instances, authored paths (route/track/generic) with stops, seats, keepClear volumes and NPC interactions.
package/README.md CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  > **Hyprune** is an explorable, world-driven desktop environment for Hyprland: your workspaces become places you can walk through, with live windows on the walls. This repository holds the versioned **schemas** for world packages, IPC and manifests, and their validator. Install Hyprune through the [hyprune](https://github.com/hyprune/hyprune) repository (starter kits, Nix, Arch); learn more at [hyprune.com](https://hyprune.com). Licence: MIT.
4
4
 
5
- Versioned contracts for Hyprune, a world-driven Hyprland desktop: world packages (formats 0.1–0.8), the IPC protocol (0.1–0.19), extension and screen-type manifests and the input keymap. JSON Schema dialect: **2020-12**. Every version is frozen once published; core, the shell and the SDK pin these exact files. Readable reference: [hyprune.com/reference/schema](https://hyprune.com/reference/schema/).
5
+ Versioned contracts for Hyprune, a world-driven Hyprland desktop: world packages (formats 0.1–0.9), the IPC protocol (0.1–0.23), object and library manifests (0.9, draft 0.10), extension and screen-type manifests and the input keymap. JSON Schema dialect: **2020-12**. Every version is frozen once published; core, the shell and the SDK pin these exact files. Readable reference: [hyprune.com/reference/schema](https://hyprune.com/reference/schema/).
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```sh
10
10
  npm i -D @hyprune/schema
11
- npx hyprune-validate world world.json - 0.8 # a world package manifest
12
- npx hyprune-validate scene scene.gltf - 0.8 world.json # its glTF against the world format
11
+ npx hyprune-validate world world.json - 0.9 # a world package manifest
12
+ npx hyprune-validate scene scene.gltf - 0.9 world.json # its glTF against the world format
13
13
  ```
14
14
 
15
15
  ```js
16
16
  import { validate, worldFormats } from '@hyprune/schema';
17
- import schema from '@hyprune/schema/v0.8/world.schema.json' with { type: 'json' };
17
+ import schema from '@hyprune/schema/v0.9/world.schema.json' with { type: 'json' };
18
18
 
19
19
  const result = validate('world', manifest, undefined, worldFormats.at(-1));
20
20
  if (!result.valid) console.error(result.errors.join('\n'));
@@ -127,6 +127,17 @@ from the proxy. Check an asset's glTF with
127
127
  `npm run validate -- scene objects/armillary.gltf - 0.8 world.json armillary`).
128
128
  See RFC-0004 "world 0.8 placed world objects".
129
129
 
130
+ World format **0.9** adds characters, paths, seats and keep-clear volumes
131
+ (RFC-0011 P2). An object asset is an inline package glTF (now with optional
132
+ object.json `traits` and `slots`), a package object directory (`dir`) or a
133
+ library object (`library` + `object`). Instances gain `path` (a `route`; it
134
+ replaces `pose`), `reverse`, `seat`, `accent`, `interactions` (`say`,
135
+ `waypoint`, `guide`, `panel`) and `locked`. New top-level `paths` (route,
136
+ track or generic; stops with a clip, facing or seat), `seats` and `keepClear`.
137
+ Pass the object.json of dir and library assets to check their traits too:
138
+ `validate('world', manifest, undefined, '0.9', {assets: {resident: objectJson}})`.
139
+ `examples/world-npcs.json` is a synthetic example. See RFC-0011 "World format 0.9".
140
+
130
141
  IPC **0.16** adds the `upscaler` graphics key (`"fsr1"`, `"bilinear"` or `null` to
131
142
  inherit the preset) to `graphicsConfig`/`graphicsPatch`. Every 0.15 definition is
132
143
  otherwise unchanged; earlier schemas stay frozen. See RFC-0003/0008 amendments.
@@ -156,3 +167,65 @@ stays `editor.unplaced.windows`. `spatial.map` gains optional `levels` (1-6
156
167
  per-level blueprint SVGs). `extensions/blueprint.schema.json` defines the world
157
168
  extension `com.hyprune.blueprint` 0.1 that supplies them. Every other 0.19
158
169
  definition is unchanged. See the RFC-0004 and RFC-0005 amendments for IPC 0.20.
170
+
171
+ IPC **0.21** adds markers, personal quests and focus sessions (RFC-0012 P1). Three
172
+ new required state keys: `markers` (the marker reveal, the persistent-marker mode
173
+ and up to 64 resolved markers), `quests` (tracked and up to 16 active quests,
174
+ counts) and `focus` (the focus session phase, monotonic phase times, credited
175
+ time, cycles and target). New methods `markers.reveal` (shell.control),
176
+ `guidance.get`/`guidance.set` (marker, quest and focus settings, the `player.get`
177
+ pattern), `quests.list/get/track/accept/abandon/resume/create/update/delete/mark`
178
+ and `focus.start/pause/resume/skip/stop/history`; mutations need the new
179
+ `quest.control` grant. Events `quest.progress` and `focus.phase`. The world keymap
180
+ gains `markers.reveal`, the overlay style a required `questColor`, and
181
+ `spatial.projection` markers an optional `markerId`. File schemas in `v0.21/`:
182
+ `quests-personal.schema.json` (`quests/personal.json`), `guidance.schema.json`
183
+ (`guidance.json`) and `focus-history.schema.json` (one `focus/history.jsonl`
184
+ line). Every other 0.20 definition is unchanged. See RFC-0012.
185
+
186
+ IPC **0.22** adds the object catalog and user placements (RFC-0011 P1): the
187
+ `objects.control` grant, a required `objects` state key (this world's authored
188
+ and user instances, object pocket items, user-object usage and limits),
189
+ `objects.catalog/pocket/place/transform/remove/hide/reset/configure` and
190
+ `libraries.list/install/remove`. `editor.selectedKind` gains `node`,
191
+ `editor.submode` gains `universal`, `editor.unplaced` gains `objects` and the
192
+ new `editor.gizmo` carries the tool 4 axis space, pivot and snapping. Error
193
+ `data` may name the refusing placement `rule`. Every other 0.21 definition is
194
+ unchanged. See the RFC-0011 amendment "P1 in IPC 0.22".
195
+
196
+ IPC **0.23** adds characters, paths and interactions (RFC-0011 P2): a required
197
+ `npcs` state key (motion setting and effective state, character budgets, the
198
+ path being drawn, the aimed character and the running interaction),
199
+ `character` and `accent` on object instances with path, activity and seat
200
+ values, `use`/`name`/`speed`/`stops` on editor paths, the reticle action
201
+ `interact` and the graphics key `npc_motion`. New methods
202
+ `npcs.spawn/bind/disable/remove` and `paths.set/remove/draw/finish`
203
+ (objects.control), `paths.list` (state.read) and `interaction.choose`
204
+ (world.control). Every other 0.22 definition is unchanged.
205
+
206
+ ## Objects and libraries (RFC-0011)
207
+
208
+ Object manifests (`object.json`) and library manifests (`library.json`) use the
209
+ world format numbering and start at **0.9** (`v0.9/object.schema.json`,
210
+ `v0.9/library.schema.json`); `v0.9/library-releases.schema.json` describes the
211
+ meta repository's `libraries/releases.json`. `v0.10/` holds a **draft** that adds
212
+ the `mover` trait, RFC-0013's `machine` trait and the extra slot kinds; it may
213
+ change until world format 0.10 is defined.
214
+
215
+ ```js
216
+ import { validate, objectFormats } from '@hyprune/schema';
217
+ const result = validate('object', manifest, undefined, undefined, { gltf, directory: 'street-lantern' });
218
+ // result.errors, result.warnings, result.triangles
219
+ ```
220
+
221
+ ```sh
222
+ npm run validate -- object examples/object.json
223
+ npm run validate -- object objects/bench/object.json - 0.9 objects/bench/object.glb
224
+ npm run validate -- library examples/library.json
225
+ npm run validate -- library-releases examples/library-releases.json
226
+ ```
227
+
228
+ The validator is pure: it never reads files. Pass `gltf` (the parsed glTF or a
229
+ GLB's JSON chunk) for the scene cross-checks, and `files`, `thumbnail` and
230
+ `measured` (exact rest bounds, largest texture, GPU bytes) when you have them;
231
+ `@hyprune/sdk`'s `hyprune object check <dir>` gathers all of them for a directory.
package/bin/validate.mjs CHANGED
@@ -2,17 +2,27 @@
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { validate } from '../lib/validate.mjs';
4
4
  const [kind, file, method, version, worldFile, asset, ...extra] = process.argv.slice(2);
5
- if (!kind || !file || extra.length || ((worldFile || asset) && kind !== 'scene')) {
5
+ // For kind `object` the fifth argument is the object's glTF (.gltf or .glb) for the cross-checks.
6
+ const gltfJson = path => {
7
+ const bytes = readFileSync(path);
8
+ if (!/\.glb$/i.test(path)) return JSON.parse(bytes.toString('utf8'));
9
+ if (bytes.length < 20 || bytes.readUInt32LE(0) !== 0x46546c67 || bytes.readUInt32LE(16) !== 0x4e4f534a) throw new Error('Invalid GLB header/JSON chunk');
10
+ return JSON.parse(bytes.subarray(20, 20 + bytes.readUInt32LE(12)).toString('utf8'));
11
+ };
12
+ if (!kind || !file || extra.length || ((worldFile || asset) && kind !== 'scene' && kind !== 'object') || (asset && kind === 'object')) {
6
13
  console.error('Usage: hyprune-validate <world|extension|screen-type|ipc> <file.json|-> [response-method|-] [contract-version]\n' +
7
- ' hyprune-validate scene <scene.gltf> - <world-format> <world.json> [object-asset-id]\n(in this repository: npm run validate -- ...)');
14
+ ' hyprune-validate scene <scene.gltf> - <world-format> <world.json> [object-asset-id]\n' +
15
+ ' hyprune-validate <object|library|library-releases> <file.json|-> [-] [format] [object scene.gltf|.glb]\n(in this repository: npm run validate -- ...)');
8
16
  process.exitCode = 2;
9
17
  } else {
10
18
  try {
11
19
  const bytes = readFileSync(file === '-' ? 0 : file);
12
20
  if (bytes.length > 1_048_576) throw new Error('Document exceeds 1 MiB');
13
21
  const value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
14
- const world = worldFile ? JSON.parse(readFileSync(worldFile, 'utf8')) : undefined;
15
- const result = validate(kind, value, method === '-' ? undefined : method, version, { world, asset });
22
+ const options = kind === 'object' ? { gltf: worldFile ? gltfJson(worldFile) : undefined }
23
+ : { world: worldFile ? JSON.parse(readFileSync(worldFile, 'utf8')) : undefined, asset };
24
+ const result = validate(kind, value, method === '-' ? undefined : method, version || undefined, options);
25
+ for (const warning of result.warnings || []) console.error(`warning: ${warning}`);
16
26
  if (result.valid) console.log(`VALID ${kind}: ${file}`);
17
27
  else { console.error(result.errors.join('\n')); process.exitCode = 1; }
18
28
  } catch (error) { console.error(error.message); process.exitCode = 2; }
@@ -0,0 +1,11 @@
1
+ {
2
+ "com.example.street@1.2.0": {
3
+ "id": "com.example.street",
4
+ "version": "1.2.0",
5
+ "formatVersion": "0.9",
6
+ "url": "https://example.com/releases/street-v1.2.0/com.example.street-1.2.0.tar.zst",
7
+ "sha256": "f06838d7b0725080016c84ae4f47aa01367ee665e3844637b14ed7f23706c894",
8
+ "nixHash": "sha256-8Gg417ByUIABbISuT0eqATZ+5mXjhEY3sU7X8jcGyJQ=",
9
+ "published": false
10
+ }
11
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "formatVersion": "0.9",
3
+ "id": "com.example.street",
4
+ "version": "1.2.0",
5
+ "name": "Street kit",
6
+ "license": "CC-BY-4.0",
7
+ "attribution": [
8
+ {
9
+ "path": ".",
10
+ "author": "Example Author",
11
+ "license": "CC-BY-4.0"
12
+ }
13
+ ],
14
+ "objects": [
15
+ "street-lantern",
16
+ "bench"
17
+ ]
18
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "formatVersion": "0.9",
3
+ "id": "resident",
4
+ "version": "1.0.0",
5
+ "name": "Resident",
6
+ "category": "characters",
7
+ "scene": "object.glb",
8
+ "license": "CC-BY-4.0",
9
+ "attribution": [
10
+ {
11
+ "path": ".",
12
+ "author": "Example Author",
13
+ "license": "CC-BY-4.0"
14
+ }
15
+ ],
16
+ "bounds": {
17
+ "min": [
18
+ -0.45,
19
+ 0,
20
+ -0.2
21
+ ],
22
+ "max": [
23
+ 0.45,
24
+ 1.69,
25
+ 0.22
26
+ ]
27
+ },
28
+ "thumbnail": "thumbnail.png",
29
+ "traits": {
30
+ "character": {
31
+ "clips": {
32
+ "walk": {
33
+ "speed": 1.12
34
+ },
35
+ "run": {
36
+ "speed": 2.9
37
+ },
38
+ "idle": {},
39
+ "sit": {
40
+ "seatHeight": 0.46
41
+ },
42
+ "sit_idle": {}
43
+ },
44
+ "height": 1.69,
45
+ "radius": 0.3,
46
+ "accent": false,
47
+ "interactions": [
48
+ {
49
+ "id": "talk",
50
+ "label": "Talk",
51
+ "action": {
52
+ "type": "say",
53
+ "lines": [
54
+ "The lifts run every few minutes."
55
+ ]
56
+ }
57
+ },
58
+ {
59
+ "id": "where",
60
+ "label": "Where am I?",
61
+ "action": {
62
+ "type": "panel",
63
+ "panel": "map"
64
+ }
65
+ }
66
+ ]
67
+ }
68
+ }
69
+ }