@hyprune/schema 0.7.0 → 0.9.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
@@ -298,3 +298,139 @@ Object manifests use the world format numbering: a world may use objects whose
298
298
  ## @hyprune/schema 0.7.0 (2026-10-12)
299
299
 
300
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.
393
+
394
+ ## Unreleased: object manifest 0.10 draft additions
395
+
396
+ Draft only; no version changes. `v0.10/object.schema.json` and the validator:
397
+
398
+ - Mappings take an `average` modifier `{windowS? (1–3600, default 60),
399
+ override?}`: the running average of a numeric value, alone or under `scale`,
400
+ `thresholds`, `cases` or `format`; never with `becomes` or `counter`. The
401
+ validator requires an `int` or `number` input, and in drives an `override`
402
+ that names another non-event input of the machine (recipes take no override).
403
+ A direct average of an `int` is fractional, so counts need a rounded scale.
404
+ - New slot kind `levels` (at most 8): `{id, node, mode: fill|slide, axis?
405
+ (unit vector, default [0, 1, 0]), length (> 0, ≤ 10), surface? (fill only),
406
+ settle?: ease|spring}` and drive target `level:<id>` (outputs 0–1). With the
407
+ glTF: `node` and `surface` exist in the scene, the surface is not inside the
408
+ fill node's subtree, no node is moved by two slots, and the collision proxy
409
+ is never moved.
410
+ - `stacks`: `cap` up to 100 (was 64) and an optional `rate` (copies per second,
411
+ 0–1000). `layout.spacing` components may be negative (−10 to 10 m; copies fill
412
+ from the node towards the sign) and must be non-zero where that axis's grid is
413
+ above 1.
414
+
415
+ ## IPC 0.24: machine inputs in the catalog and live machine state (RFC-0013)
416
+
417
+ - `objects.catalog` entries gain required `machine`: null for an entry without
418
+ the machine trait, else `{primary: input id|null, inputs[1..16]}`. Each input
419
+ is the object 0.10 declaration as authored: `{id, type (enum|int|number|bool|
420
+ text|event|items), label, explain, default?, values? (enum), min?/max?
421
+ (int, number), maxLength? (text), repeat? (2–16, not expanded), states?
422
+ (items)}`.
423
+ - Required state key `live {instances[<=64]}`, published on change and never
424
+ per frame. An instance is `{id, asset, ref|null, source|null (the source
425
+ every bound input reads, null when they differ), status (ok|error|null, the
426
+ source's _status), error (<=256|null, its _error), stale, inputs}`; `inputs`
427
+ maps up to 16 input ids, repeated inputs expanded (`health.1`), to `{source|null,
428
+ key|null, value, raw, condition (fresh|stale|missing|error), publishedAt}`.
429
+ Values are scalars (strings <=256) or null; `publishedAt` is the wall-clock
430
+ Unix ms of the publication core holds, null when missing.
431
+ - Everything else is inherited from 0.23 unchanged (no new method, grant or
432
+ event); `session.hello` reports `0.24`. Inheritance test included.
433
+
434
+ ## @hyprune/schema 0.9.0 (2026-10-13)
435
+
436
+ Publishes IPC 0.24: `objects.catalog` entries carry a placed machine's declared inputs (`machine`), and a new `live` state key reports each placed machine's source, status and per-input values (published only on change). Inherited from 0.23 otherwise; no new grant. Also the object 0.10 draft additions for gauges: the `average` mapping modifier, `levels` slots (fill/slide with spring) and stack rate, cap 100 and signed spacing.
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.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/).
5
+ Versioned contracts for Hyprune, a world-driven Hyprland desktop: world packages (formats 0.1–0.9), the IPC protocol (0.1–0.24), 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.
@@ -182,6 +193,25 @@ new `editor.gizmo` carries the tool 4 axis space, pivot and snapping. Error
182
193
  `data` may name the refusing placement `rule`. Every other 0.21 definition is
183
194
  unchanged. See the RFC-0011 amendment "P1 in IPC 0.22".
184
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
+ IPC **0.24** adds live machine state (RFC-0013): `objects.catalog` entries gain
207
+ a required `machine` (null, or the machine trait's `primary` and its input
208
+ declarations as authored, repeat not expanded) and a required `live` state key
209
+ lists at most 64 bound machine instances with each input's binding, mapped and
210
+ raw value, condition (`fresh`, `stale`, `missing`, `error`) and `publishedAt`
211
+ (wall-clock Unix ms of the held publication, or null). No new method, event or
212
+ grant; every other 0.23 definition is unchanged. See the RFC-0013 amendment
213
+ "Live state in IPC 0.24".
214
+
185
215
  ## Objects and libraries (RFC-0011)
186
216
 
187
217
  Object manifests (`object.json`) and library manifests (`library.json`) use the
@@ -0,0 +1,493 @@
1
+ {
2
+ "formatVersion": "0.9",
3
+ "id": "com.hyprune.example.courtyard",
4
+ "version": "0.1.0",
5
+ "name": "Courtyard",
6
+ "description": "Synthetic world 0.9 manifest: characters on routes, seats, interactions and keep-clear volumes. Referenced files are not supplied.",
7
+ "license": "CC-BY-4.0",
8
+ "scene": "scene.gltf",
9
+ "defaultSpawn": "arrival",
10
+ "spawns": [
11
+ {
12
+ "id": "arrival",
13
+ "pose": {
14
+ "position": [
15
+ 0,
16
+ 1,
17
+ 0
18
+ ],
19
+ "rotation": [
20
+ 0,
21
+ 0,
22
+ 0,
23
+ 1
24
+ ]
25
+ }
26
+ }
27
+ ],
28
+ "attribution": [
29
+ {
30
+ "path": ".",
31
+ "author": "Hyprune contributors",
32
+ "license": "CC-BY-4.0"
33
+ }
34
+ ],
35
+ "zones": [
36
+ {
37
+ "id": "work",
38
+ "name": "Work",
39
+ "bounds": {
40
+ "min": [
41
+ -4,
42
+ 0,
43
+ -4
44
+ ],
45
+ "max": [
46
+ 4,
47
+ 4,
48
+ 4
49
+ ]
50
+ }
51
+ }
52
+ ],
53
+ "anchors": [
54
+ {
55
+ "id": "desk",
56
+ "pose": {
57
+ "position": [
58
+ 0,
59
+ 1.4,
60
+ -3
61
+ ],
62
+ "rotation": [
63
+ 0,
64
+ 0,
65
+ 0,
66
+ 1
67
+ ]
68
+ },
69
+ "size": [
70
+ 1.6,
71
+ 0.9
72
+ ],
73
+ "screenType": "com.hyprune.window"
74
+ }
75
+ ],
76
+ "collision": [
77
+ {
78
+ "node": 0,
79
+ "shape": "mesh",
80
+ "layer": "static"
81
+ }
82
+ ],
83
+ "navigation": {
84
+ "kind": "mesh",
85
+ "node": 1,
86
+ "agentRadius": 0.3,
87
+ "agentHeight": 1.8
88
+ },
89
+ "lightmaps": [
90
+ {
91
+ "material": 0,
92
+ "texture": 1,
93
+ "texCoord": 1,
94
+ "encoding": "linear",
95
+ "intensity": 1
96
+ }
97
+ ],
98
+ "audio": [
99
+ {
100
+ "id": "air",
101
+ "path": "audio/air.ogg",
102
+ "position": [
103
+ 0,
104
+ 2,
105
+ 0
106
+ ],
107
+ "radius": 10,
108
+ "gain": 0.2,
109
+ "loop": true
110
+ }
111
+ ],
112
+ "objectAssets": [
113
+ {
114
+ "id": "resident",
115
+ "dir": "characters/resident-blue"
116
+ },
117
+ {
118
+ "id": "porter",
119
+ "library": "com.hyprune.starter@0.1.0",
120
+ "object": "porter"
121
+ },
122
+ {
123
+ "id": "guide",
124
+ "scene": "characters/guide.glb",
125
+ "traits": {
126
+ "character": {
127
+ "clips": {
128
+ "walk": {
129
+ "speed": 1.1
130
+ },
131
+ "idle": {},
132
+ "wave": {},
133
+ "nod": {},
134
+ "sit": {
135
+ "seatHeight": 0.46
136
+ },
137
+ "sit_idle": {}
138
+ },
139
+ "height": 1.72,
140
+ "radius": 0.3,
141
+ "accent": true,
142
+ "interactions": [
143
+ {
144
+ "id": "talk",
145
+ "label": "Talk",
146
+ "action": {
147
+ "type": "say",
148
+ "lines": [
149
+ "Welcome to the courtyard."
150
+ ]
151
+ }
152
+ }
153
+ ]
154
+ }
155
+ }
156
+ },
157
+ {
158
+ "id": "court-bench",
159
+ "scene": "objects/bench.glb",
160
+ "collision": {
161
+ "node": "proxy"
162
+ },
163
+ "traits": {
164
+ "movable": {},
165
+ "seat": {
166
+ "capacity": 2,
167
+ "anchors": [
168
+ {
169
+ "id": "left",
170
+ "position": [
171
+ -0.4,
172
+ 0,
173
+ 0.55
174
+ ],
175
+ "yaw": 0,
176
+ "height": 0.45
177
+ },
178
+ {
179
+ "id": "right",
180
+ "position": [
181
+ 0.4,
182
+ 0,
183
+ 0.55
184
+ ],
185
+ "yaw": 0,
186
+ "height": 0.45
187
+ }
188
+ ]
189
+ }
190
+ }
191
+ },
192
+ {
193
+ "id": "fountain",
194
+ "scene": "objects/fountain.glb",
195
+ "animations": [
196
+ {
197
+ "node": "spray",
198
+ "type": "spin",
199
+ "axis": [
200
+ 0,
201
+ 1,
202
+ 0
203
+ ],
204
+ "pivot": [
205
+ 0,
206
+ 0,
207
+ 0
208
+ ],
209
+ "period": 12
210
+ }
211
+ ],
212
+ "lighting": {
213
+ "mode": "probe"
214
+ }
215
+ }
216
+ ],
217
+ "objects": [
218
+ {
219
+ "id": "fountain-1",
220
+ "asset": "fountain",
221
+ "pose": {
222
+ "position": [
223
+ 1.5,
224
+ 0,
225
+ 0
226
+ ],
227
+ "rotation": [
228
+ 0,
229
+ 0,
230
+ 0,
231
+ 1
232
+ ]
233
+ }
234
+ },
235
+ {
236
+ "id": "bench-east",
237
+ "asset": "court-bench",
238
+ "pose": {
239
+ "position": [
240
+ 3.6,
241
+ 0,
242
+ -2
243
+ ],
244
+ "rotation": [
245
+ 0,
246
+ -0.7071068,
247
+ 0,
248
+ 0.7071068
249
+ ]
250
+ },
251
+ "locked": true
252
+ },
253
+ {
254
+ "id": "porter-1",
255
+ "asset": "porter",
256
+ "path": "court-loop",
257
+ "phase": 0.25,
258
+ "speed": 1,
259
+ "accent": [
260
+ 0.9,
261
+ 0.5,
262
+ 0.1
263
+ ],
264
+ "interactions": [
265
+ {
266
+ "id": "talk",
267
+ "label": "Talk",
268
+ "action": {
269
+ "type": "say",
270
+ "lines": [
271
+ "The desk is on the north side.",
272
+ "Mind the fountain."
273
+ ]
274
+ }
275
+ },
276
+ {
277
+ "id": "where",
278
+ "label": "Where is work?",
279
+ "prompt": "Mark it",
280
+ "action": {
281
+ "type": "waypoint",
282
+ "target": {
283
+ "zoneId": "work"
284
+ }
285
+ }
286
+ },
287
+ {
288
+ "id": "show-desk",
289
+ "label": "Show me the desk",
290
+ "prompt": "Follow me",
291
+ "action": {
292
+ "type": "guide",
293
+ "path": "to-desk"
294
+ }
295
+ },
296
+ {
297
+ "id": "map",
298
+ "label": "Open the map",
299
+ "action": {
300
+ "type": "panel",
301
+ "panel": "map"
302
+ }
303
+ }
304
+ ]
305
+ },
306
+ {
307
+ "id": "guide-1",
308
+ "asset": "guide",
309
+ "path": "court-loop",
310
+ "phase": 0.75,
311
+ "reverse": true,
312
+ "accent": [
313
+ 0.2,
314
+ 0.4,
315
+ 0.9
316
+ ]
317
+ },
318
+ {
319
+ "id": "reader",
320
+ "asset": "resident",
321
+ "pose": {
322
+ "position": [
323
+ 1.5,
324
+ 0,
325
+ 1.8
326
+ ],
327
+ "rotation": [
328
+ 0,
329
+ 1,
330
+ 0,
331
+ 0
332
+ ]
333
+ },
334
+ "seat": "fountain-ledge",
335
+ "interactions": [
336
+ {
337
+ "id": "fountain",
338
+ "label": "The fountain?",
339
+ "action": {
340
+ "type": "waypoint",
341
+ "target": {
342
+ "object": "fountain-1"
343
+ }
344
+ }
345
+ }
346
+ ]
347
+ }
348
+ ],
349
+ "paths": [
350
+ {
351
+ "id": "court-loop",
352
+ "name": "Court loop",
353
+ "use": "route",
354
+ "closed": true,
355
+ "speed": 1.1,
356
+ "points": [
357
+ {
358
+ "position": [
359
+ -3.5,
360
+ 0,
361
+ 3
362
+ ],
363
+ "type": "line"
364
+ },
365
+ {
366
+ "position": [
367
+ -3.5,
368
+ 0,
369
+ -2
370
+ ],
371
+ "type": "line"
372
+ },
373
+ {
374
+ "position": [
375
+ 3,
376
+ 0,
377
+ -2
378
+ ],
379
+ "type": "curve"
380
+ },
381
+ {
382
+ "position": [
383
+ 3,
384
+ 0,
385
+ 3
386
+ ],
387
+ "type": "arc"
388
+ }
389
+ ],
390
+ "stops": [
391
+ {
392
+ "point": 1,
393
+ "seconds": 3,
394
+ "clip": "look_around"
395
+ },
396
+ {
397
+ "point": 2,
398
+ "seconds": 20,
399
+ "seat": "bench-east/left"
400
+ },
401
+ {
402
+ "point": 3,
403
+ "seconds": 4,
404
+ "face": {
405
+ "anchor": "desk"
406
+ }
407
+ }
408
+ ]
409
+ },
410
+ {
411
+ "id": "to-desk",
412
+ "name": "To the desk",
413
+ "use": "route",
414
+ "points": [
415
+ {
416
+ "position": [
417
+ 0,
418
+ 0,
419
+ 3
420
+ ],
421
+ "type": "line"
422
+ },
423
+ {
424
+ "position": [
425
+ 0,
426
+ 0,
427
+ -2
428
+ ],
429
+ "type": "line"
430
+ }
431
+ ]
432
+ },
433
+ {
434
+ "id": "garden-walk",
435
+ "points": [
436
+ {
437
+ "position": [
438
+ -2,
439
+ 0,
440
+ 2
441
+ ],
442
+ "type": "line"
443
+ },
444
+ {
445
+ "position": [
446
+ 2,
447
+ 0,
448
+ 2
449
+ ],
450
+ "type": "curve"
451
+ }
452
+ ]
453
+ }
454
+ ],
455
+ "seats": [
456
+ {
457
+ "id": "fountain-ledge",
458
+ "pose": {
459
+ "position": [
460
+ 1.5,
461
+ 0,
462
+ 1.2
463
+ ],
464
+ "rotation": [
465
+ 0,
466
+ 1,
467
+ 0,
468
+ 0
469
+ ]
470
+ },
471
+ "height": 0.5,
472
+ "use": [
473
+ "sit"
474
+ ],
475
+ "approach": 0.6
476
+ }
477
+ ],
478
+ "keepClear": [
479
+ {
480
+ "id": "north-door",
481
+ "min": [
482
+ -0.8,
483
+ 0,
484
+ -4.2
485
+ ],
486
+ "max": [
487
+ 0.8,
488
+ 2.4,
489
+ -3.6
490
+ ]
491
+ }
492
+ ]
493
+ }