@hyprune/schema 0.5.2 → 0.7.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
@@ -142,3 +142,159 @@ definition exists in 0.16 and the graphics properties differ only by the new key
142
142
  action it would take (`""|click|grab|pocket|place|mount|select|path|drop`).
143
143
  - Everything else is inherited from 0.18 unchanged, including the offload cap
144
144
  and device keys; `session.hello` reports `0.19`.
145
+
146
+ ## IPC 0.20: pocket actions and blueprint map levels
147
+
148
+ - `surface.pocket {surfaceId}` and `surface.unpocket {surfaceId}` (surface.control).
149
+ Pocket: the window leaves the world for the Placement queue, its placement kept;
150
+ idempotent. Unpocket: placed at a free spot in front of the player
151
+ (`placed: true`) or left in the pocket, selected for the Placement tool
152
+ (`placed: false`). Results `{revision}` / `{revision, placed}`.
153
+ - `map.levels` (optional, 1..6): `{id, name, minY, maxY, svg, sha256}` per
154
+ blueprint level, sorted by `minY`, in the frame of the map (`worldToMap`).
155
+ - New `extensions/blueprint.schema.json`: the `com.hyprune.blueprint` 0.1 world
156
+ extension (levels with package-relative SVG paths; byte budgets, ordering and
157
+ the `class` vocabulary are stated in its description and enforced by consumers).
158
+ - Everything else is inherited from 0.19 unchanged; `session.hello` reports
159
+ `0.20` and lists the two methods. Inheritance test included.
160
+
161
+ ## World 0.8: placed world objects
162
+
163
+ - World format **0.8** adds `objectAssets` (at most 32) and `objects` (at most
164
+ 256). An asset is a package-relative glTF (`scene`, same extension rules as the
165
+ world's scene.gltf; never lightmapped) with optional declarative node animation
166
+ (at most 16: `spin` or `orbit` about `pivot`/`axis` in asset space, or `bob`
167
+ along `axis` by `amplitude` metres; `period` 0.5–3600 s, optional `phase`), an
168
+ optional static `collision` proxy node and `lighting.mode` (`probe` default, or
169
+ `unlit`). Nodes are referenced by unique glTF node name. `library` (`id@version`)
170
+ is reserved for shared object libraries and rejected in 0.8. An object places an
171
+ asset with a `pose`, uniform `scale` (0.01–100), animation `speed` (0–10, default
172
+ 1), `phase` (0–1) and an optional `ambient` RGB override (0–16).
173
+ - `validate('world')` (from 0.8): unique asset and object ids, known asset
174
+ references, normalised poses, unit axes, per-type fields (spin/orbit need a
175
+ pivot and take no amplitude; bob the reverse), one animation per node, collision
176
+ node not animated, library references rejected. `validate('scene', gltf,
177
+ undefined, '0.8', {world, asset: id})` checks an asset's glTF: extension rules,
178
+ referenced node names exist exactly once, no animated node inside another
179
+ animated subtree, the collision node has a mesh and is outside every animated
180
+ subtree.
181
+ - `v0.8/common.schema.json` is the 0.7 common definitions under the 0.8 `$id`
182
+ (the directory already held IPC 0.8, which keeps its own references).
183
+ Everything else is inherited from 0.7 unchanged. Inheritance test included.
184
+
185
+ ## @hyprune/schema 0.6.0 (2026-10-11)
186
+
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.
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.7), 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.8), the IPC protocol (0.1–0.22), 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.7 # a world package manifest
12
- npx hyprune-validate scene scene.gltf - 0.7 world.json # its glTF against the world format
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
13
13
  ```
14
14
 
15
15
  ```js
16
16
  import { validate, worldFormats } from '@hyprune/schema';
17
- import schema from '@hyprune/schema/v0.7/world.schema.json' with { type: 'json' };
17
+ import schema from '@hyprune/schema/v0.8/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'));
@@ -118,6 +118,15 @@ optimised scene assets: `KHR_texture_basisu` (KTX2, material textures only),
118
118
  `extensionsRequired` and lightmap textures against the world format. Version
119
119
  gates use `atLeast()`; see RFC-0004 "world 0.7".
120
120
 
121
+ World format **0.8** adds placed world objects: `objectAssets` (package glTF
122
+ assets with optional `spin`/`orbit`/`bob` node animation, a static collision
123
+ proxy and `probe`/`unlit` lighting) and `objects` (instances with pose, scale,
124
+ speed, phase and an optional ambient override). Objects are render-only apart
125
+ from the proxy. Check an asset's glTF with
126
+ `validate('scene', gltf, undefined, '0.8', {world, asset: 'armillary'})` (CLI:
127
+ `npm run validate -- scene objects/armillary.gltf - 0.8 world.json armillary`).
128
+ See RFC-0004 "world 0.8 placed world objects".
129
+
121
130
  IPC **0.16** adds the `upscaler` graphics key (`"fsr1"`, `"bilinear"` or `null` to
122
131
  inherit the preset) to `graphicsConfig`/`graphicsPatch`. Every 0.15 definition is
123
132
  otherwise unchanged; earlier schemas stay frozen. See RFC-0003/0008 amendments.
@@ -139,3 +148,63 @@ bob, reticle style/size/colour and reach limits), widens graphics `fov` to 30-12
139
148
  adds `reticleColor`/`validColor`/`invalidColor` to the overlay style and the
140
149
  required `interaction.reticle {state, action}`. Every other 0.18 definition is
141
150
  unchanged. See the RFC-0006 amendment "IPC 0.19 player settings and reticle".
151
+
152
+ IPC **0.20** adds `surface.pocket`/`surface.unpocket` (surface.control): put a
153
+ world window into the pocket (the Placement queue) and take it out again, placed
154
+ in front of the player or selected for the Placement tool (`placed`). Pocket state
155
+ stays `editor.unplaced.windows`. `spatial.map` gains optional `levels` (1-6
156
+ per-level blueprint SVGs). `extensions/blueprint.schema.json` defines the world
157
+ extension `com.hyprune.blueprint` 0.1 that supplies them. Every other 0.19
158
+ definition is unchanged. See the RFC-0004 and RFC-0005 amendments for IPC 0.20.
159
+
160
+ IPC **0.21** adds markers, personal quests and focus sessions (RFC-0012 P1). Three
161
+ new required state keys: `markers` (the marker reveal, the persistent-marker mode
162
+ and up to 64 resolved markers), `quests` (tracked and up to 16 active quests,
163
+ counts) and `focus` (the focus session phase, monotonic phase times, credited
164
+ time, cycles and target). New methods `markers.reveal` (shell.control),
165
+ `guidance.get`/`guidance.set` (marker, quest and focus settings, the `player.get`
166
+ pattern), `quests.list/get/track/accept/abandon/resume/create/update/delete/mark`
167
+ and `focus.start/pause/resume/skip/stop/history`; mutations need the new
168
+ `quest.control` grant. Events `quest.progress` and `focus.phase`. The world keymap
169
+ gains `markers.reveal`, the overlay style a required `questColor`, and
170
+ `spatial.projection` markers an optional `markerId`. File schemas in `v0.21/`:
171
+ `quests-personal.schema.json` (`quests/personal.json`), `guidance.schema.json`
172
+ (`guidance.json`) and `focus-history.schema.json` (one `focus/history.jsonl`
173
+ line). Every other 0.20 definition is unchanged. See RFC-0012.
174
+
175
+ IPC **0.22** adds the object catalog and user placements (RFC-0011 P1): the
176
+ `objects.control` grant, a required `objects` state key (this world's authored
177
+ and user instances, object pocket items, user-object usage and limits),
178
+ `objects.catalog/pocket/place/transform/remove/hide/reset/configure` and
179
+ `libraries.list/install/remove`. `editor.selectedKind` gains `node`,
180
+ `editor.submode` gains `universal`, `editor.unplaced` gains `objects` and the
181
+ new `editor.gizmo` carries the tool 4 axis space, pivot and snapping. Error
182
+ `data` may name the refusing placement `rule`. Every other 0.21 definition is
183
+ unchanged. See the RFC-0011 amendment "P1 in IPC 0.22".
184
+
185
+ ## Objects and libraries (RFC-0011)
186
+
187
+ Object manifests (`object.json`) and library manifests (`library.json`) use the
188
+ world format numbering and start at **0.9** (`v0.9/object.schema.json`,
189
+ `v0.9/library.schema.json`); `v0.9/library-releases.schema.json` describes the
190
+ meta repository's `libraries/releases.json`. `v0.10/` holds a **draft** that adds
191
+ the `mover` trait, RFC-0013's `machine` trait and the extra slot kinds; it may
192
+ change until world format 0.10 is defined.
193
+
194
+ ```js
195
+ import { validate, objectFormats } from '@hyprune/schema';
196
+ const result = validate('object', manifest, undefined, undefined, { gltf, directory: 'street-lantern' });
197
+ // result.errors, result.warnings, result.triangles
198
+ ```
199
+
200
+ ```sh
201
+ npm run validate -- object examples/object.json
202
+ npm run validate -- object objects/bench/object.json - 0.9 objects/bench/object.glb
203
+ npm run validate -- library examples/library.json
204
+ npm run validate -- library-releases examples/library-releases.json
205
+ ```
206
+
207
+ The validator is pure: it never reads files. Pass `gltf` (the parsed glTF or a
208
+ GLB's JSON chunk) for the scene cross-checks, and `files`, `thumbnail` and
209
+ `measured` (exact rest bounds, largest texture, GPU bytes) when you have them;
210
+ `@hyprune/sdk`'s `hyprune object check <dir>` gathers all of them for a directory.
package/bin/validate.mjs CHANGED
@@ -1,18 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { validate } from '../lib/validate.mjs';
4
- const [kind, file, method, version, worldFile, ...extra] = process.argv.slice(2);
5
- if (!kind || !file || extra.length || (worldFile && kind !== 'scene')) {
4
+ const [kind, file, method, version, worldFile, asset, ...extra] = process.argv.slice(2);
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>\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 });
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
+ }