@origingame/engine 0.4.0 → 0.5.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.
Files changed (78) hide show
  1. package/dist/web/origin-engine.d.ts +8 -5
  2. package/dist/web/origin-engine.js +39 -19
  3. package/dist/web/origin-engine_bg.wasm +0 -0
  4. package/dist/web/origin-engine_bg.wasm.d.ts +6 -5
  5. package/dist/web-publish/origin-engine-lanes.js +49 -29
  6. package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
  7. package/dist/web-publish/origin-engine.d.ts +8 -5
  8. package/dist/web-publish/origin-engine.js +39 -19
  9. package/dist/web-publish/origin-engine_bg.wasm +0 -0
  10. package/dist/web-publish/origin-engine_bg.wasm.d.ts +6 -5
  11. package/dist/web-publish-2d/origin-engine-lanes.js +49 -29
  12. package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
  13. package/dist/web-publish-2d/origin-engine.d.ts +8 -5
  14. package/dist/web-publish-2d/origin-engine.js +39 -19
  15. package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
  16. package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +6 -5
  17. package/knowledge/looks/catalogue.json +123 -2
  18. package/knowledge/package.json +1 -1
  19. package/knowledge/scaffold/origin-engine.json +2 -2
  20. package/knowledge/scaffold/template/package.json +1 -1
  21. package/knowledge/scaffold/template/project.ogt +1 -1
  22. package/knowledge/scripts/validate.ts +69 -6
  23. package/knowledge/skills/og-audio/SKILL.md +31 -5
  24. package/knowledge/skills/og-build/SKILL.md +44 -4
  25. package/knowledge/skills/og-core/SKILL.md +4 -3
  26. package/knowledge/skills/og-feel/SKILL.md +9 -1
  27. package/knowledge/skills/og-game/SKILL.md +199 -16
  28. package/knowledge/skills/og-platform-sdk/SKILL.md +6 -0
  29. package/knowledge/skills/og-taste/SKILL.md +7 -0
  30. package/knowledge/skills/og-ui/SKILL.md +24 -0
  31. package/knowledge/wiki/ai.md +20 -20
  32. package/knowledge/wiki/anim.md +526 -60
  33. package/knowledge/wiki/atmosphere.md +13 -13
  34. package/knowledge/wiki/audio.md +160 -20
  35. package/knowledge/wiki/camera.md +13 -13
  36. package/knowledge/wiki/commands.md +11 -11
  37. package/knowledge/wiki/debug.md +13 -13
  38. package/knowledge/wiki/game.md +8 -8
  39. package/knowledge/wiki/i18n.md +7 -7
  40. package/knowledge/wiki/index.md +25 -1
  41. package/knowledge/wiki/input.md +47 -47
  42. package/knowledge/wiki/load.md +76 -50
  43. package/knowledge/wiki/math.md +102 -102
  44. package/knowledge/wiki/nav.md +34 -34
  45. package/knowledge/wiki/net.md +10 -10
  46. package/knowledge/wiki/path.md +14 -14
  47. package/knowledge/wiki/physics.md +111 -100
  48. package/knowledge/wiki/platform.md +44 -44
  49. package/knowledge/wiki/probe.md +86 -86
  50. package/knowledge/wiki/reflect.md +17 -17
  51. package/knowledge/wiki/render.md +245 -176
  52. package/knowledge/wiki/rng.md +14 -14
  53. package/knowledge/wiki/shapes.md +26 -26
  54. package/knowledge/wiki/stream.md +15 -15
  55. package/knowledge/wiki/system.md +8 -8
  56. package/knowledge/wiki/systems.md +2 -2
  57. package/knowledge/wiki/terrain.md +19 -19
  58. package/knowledge/wiki/tilemap.md +13 -13
  59. package/knowledge/wiki/time.md +4 -4
  60. package/knowledge/wiki/timeline.md +14 -14
  61. package/knowledge/wiki/transform.md +8 -8
  62. package/knowledge/wiki/tween.md +17 -17
  63. package/knowledge/wiki/ui.md +243 -243
  64. package/knowledge/wiki/video.md +5 -5
  65. package/knowledge/wiki/world.md +36 -36
  66. package/package.json +4 -4
  67. package/schema/api.schema.json +639 -11
  68. package/shell/page.ts +4 -0
  69. package/src/generated/abi.ts +3 -0
  70. package/src/index.ts +11 -0
  71. package/src/node/device.ts +5 -2
  72. package/src/node/polish.ts +34 -15
  73. package/src/node/run.ts +23 -6
  74. package/src/runtime.ts +1 -0
  75. package/src/web/gl-state.ts +907 -0
  76. package/src/web/worker.ts +6 -0
  77. package/types/bindings.json +823 -349
  78. package/types/index.d.ts +650 -15
@@ -37,6 +37,10 @@
37
37
  "preset": "templates/looks/expressionist-painting.ogt",
38
38
  "presetReady": true,
39
39
  "support": "content",
40
+ "profiles": [
41
+ "2d",
42
+ "3d"
43
+ ],
40
44
  "primitives": [
41
45
  "Stylise.paintRadius",
42
46
  "Stylise.paintCanvas",
@@ -88,6 +92,10 @@
88
92
  "preset": "templates/looks/bold-comic-illustration.ogt",
89
93
  "presetReady": true,
90
94
  "support": "full",
95
+ "profiles": [
96
+ "2d",
97
+ "3d"
98
+ ],
91
99
  "primitives": [
92
100
  "StandardMaterialDesc.toon",
93
101
  "PostEffects.outline",
@@ -137,6 +145,10 @@
137
145
  "preset": "templates/looks/watercolor-wash.ogt",
138
146
  "presetReady": true,
139
147
  "support": "full",
148
+ "profiles": [
149
+ "2d",
150
+ "3d"
151
+ ],
140
152
  "primitives": [
141
153
  "Stylise.paintEdgeDarkening",
142
154
  "Stylise.paintBleed",
@@ -186,6 +198,9 @@
186
198
  "preset": "templates/looks/pixel-3d-hybrid.ogt",
187
199
  "presetReady": true,
188
200
  "support": "full",
201
+ "profiles": [
202
+ "3d"
203
+ ],
189
204
  "primitives": [
190
205
  "PostEffects.depthOfField",
191
206
  "PostEffects.ao",
@@ -237,6 +252,9 @@
237
252
  "preset": "templates/looks/faceted-low-poly.ogt",
238
253
  "presetReady": true,
239
254
  "support": "full",
255
+ "profiles": [
256
+ "3d"
257
+ ],
240
258
  "primitives": [
241
259
  "StandardMaterialDesc.flatShading",
242
260
  "Fog",
@@ -276,8 +294,8 @@
276
294
  "Animation holds poses for 2 or more ticks (stepped, not interpolated)."
277
295
  ],
278
296
  "premise": {
279
- "genre": "adventure, platformer, comedy",
280
- "mood": "warm, odd, handmade",
297
+ "genre": "adventure, platformer, comedy, party games",
298
+ "mood": "warm, odd, playful, handmade",
281
299
  "era": "stop-motion film",
282
300
  "audience": "all ages, art-house"
283
301
  },
@@ -285,6 +303,9 @@
285
303
  "preset": "templates/looks/sculpted-clay.ogt",
286
304
  "presetReady": true,
287
305
  "support": "full",
306
+ "profiles": [
307
+ "3d"
308
+ ],
288
309
  "primitives": [
289
310
  "StandardMaterialDesc.roughness",
290
311
  "StandardMaterialDesc.sheen",
@@ -336,6 +357,9 @@
336
357
  "preset": "templates/looks/8bit-pixel.ogt",
337
358
  "presetReady": true,
338
359
  "support": "full",
360
+ "profiles": [
361
+ "2d"
362
+ ],
339
363
  "primitives": [
340
364
  "Stylise.pixelHeight",
341
365
  "Stylise.palette",
@@ -386,6 +410,9 @@
386
410
  "preset": "templates/looks/voxel-worlds.ogt",
387
411
  "presetReady": true,
388
412
  "support": "full",
413
+ "profiles": [
414
+ "3d"
415
+ ],
389
416
  "primitives": [
390
417
  "StandardMaterialDesc.flatShading",
391
418
  "PostEffects.ao",
@@ -434,6 +461,9 @@
434
461
  "preset": "templates/looks/illuminated-manuscript.ogt",
435
462
  "presetReady": true,
436
463
  "support": "content",
464
+ "profiles": [
465
+ "2d"
466
+ ],
437
467
  "primitives": [
438
468
  "Stylise.paintCanvas",
439
469
  "Stylise.ink",
@@ -485,6 +515,9 @@
485
515
  "preset": "templates/looks/rubber-hose-animation.ogt",
486
516
  "presetReady": true,
487
517
  "support": "content",
518
+ "profiles": [
519
+ "2d"
520
+ ],
488
521
  "primitives": [
489
522
  "PostEffects.filmGrain",
490
523
  "PostEffects.vignette",
@@ -536,6 +569,9 @@
536
569
  "preset": "templates/looks/pencil-hatching.ogt",
537
570
  "presetReady": true,
538
571
  "support": "full",
572
+ "profiles": [
573
+ "3d"
574
+ ],
539
575
  "primitives": [
540
576
  "Stylise.hatching",
541
577
  "Stylise.hatchingLevels",
@@ -585,6 +621,9 @@
585
621
  "preset": "templates/looks/gothic-contour.ogt",
586
622
  "presetReady": true,
587
623
  "support": "content",
624
+ "profiles": [
625
+ "2d"
626
+ ],
588
627
  "primitives": [
589
628
  "Fog",
590
629
  "PostEffects.glow",
@@ -637,6 +676,10 @@
637
676
  "preset": "templates/looks/lush-painted-worlds.ogt",
638
677
  "presetReady": true,
639
678
  "support": "content",
679
+ "profiles": [
680
+ "2d",
681
+ "3d"
682
+ ],
640
683
  "primitives": [
641
684
  "Stylise.paintRadius",
642
685
  "Stylise.paintCanvas",
@@ -688,6 +731,10 @@
688
731
  "preset": "templates/looks/decorative-storybook.ogt",
689
732
  "presetReady": true,
690
733
  "support": "content",
734
+ "profiles": [
735
+ "2d",
736
+ "3d"
737
+ ],
691
738
  "primitives": [
692
739
  "Stylise.ink",
693
740
  "Stylise.paintCanvas",
@@ -738,6 +785,9 @@
738
785
  "preset": "templates/looks/classical-painting-collage.ogt",
739
786
  "presetReady": true,
740
787
  "support": "content",
788
+ "profiles": [
789
+ "2d"
790
+ ],
741
791
  "primitives": [
742
792
  "sprites with transforms",
743
793
  "PostEffects.vignette",
@@ -788,6 +838,9 @@
788
838
  "preset": "templates/looks/detailed-doodle.ogt",
789
839
  "presetReady": true,
790
840
  "support": "content",
841
+ "profiles": [
842
+ "2d"
843
+ ],
791
844
  "primitives": [
792
845
  "Stylise.ink",
793
846
  "unlit materials",
@@ -838,6 +891,10 @@
838
891
  "preset": "templates/looks/black-figure-pottery.ogt",
839
892
  "presetReady": true,
840
893
  "support": "content",
894
+ "profiles": [
895
+ "2d",
896
+ "3d"
897
+ ],
841
898
  "primitives": [
842
899
  "Stylise.palette",
843
900
  "Stylise.posterize",
@@ -888,6 +945,9 @@
888
945
  "preset": "templates/looks/heavy-ink-shadows.ogt",
889
946
  "presetReady": true,
890
947
  "support": "full",
948
+ "profiles": [
949
+ "3d"
950
+ ],
891
951
  "primitives": [
892
952
  "Stylise.posterize",
893
953
  "Stylise.hatching",
@@ -937,6 +997,9 @@
937
997
  "preset": "templates/looks/monochrome-pixel.ogt",
938
998
  "presetReady": true,
939
999
  "support": "full",
1000
+ "profiles": [
1001
+ "2d"
1002
+ ],
940
1003
  "primitives": [
941
1004
  "Stylise.pixelHeight",
942
1005
  "Stylise.palette",
@@ -986,6 +1049,9 @@
986
1049
  "preset": "templates/looks/16bit-pixel.ogt",
987
1050
  "presetReady": true,
988
1051
  "support": "full",
1052
+ "profiles": [
1053
+ "2d"
1054
+ ],
989
1055
  "primitives": [
990
1056
  "Stylise.pixelHeight",
991
1057
  "Stylise.pixelSnap",
@@ -1035,6 +1101,9 @@
1035
1101
  "preset": "templates/looks/detailed-pixel.ogt",
1036
1102
  "presetReady": true,
1037
1103
  "support": "full",
1104
+ "profiles": [
1105
+ "2d"
1106
+ ],
1038
1107
  "primitives": [
1039
1108
  "Stylise.pixelHeight",
1040
1109
  "Stylise.pixelSnap",
@@ -1085,6 +1154,9 @@
1085
1154
  "preset": "templates/looks/luminous-pixel.ogt",
1086
1155
  "presetReady": true,
1087
1156
  "support": "full",
1157
+ "profiles": [
1158
+ "2d"
1159
+ ],
1088
1160
  "primitives": [
1089
1161
  "Stylise.pixelHeight",
1090
1162
  "Lighting2D",
@@ -1135,6 +1207,9 @@
1135
1207
  "preset": "templates/looks/topdown-tile-pixel.ogt",
1136
1208
  "presetReady": true,
1137
1209
  "support": "full",
1210
+ "profiles": [
1211
+ "2d"
1212
+ ],
1138
1213
  "primitives": [
1139
1214
  "tilemap layers",
1140
1215
  "Sprite.sortY",
@@ -1184,6 +1259,9 @@
1184
1259
  "preset": "templates/looks/low-resolution-3d.ogt",
1185
1260
  "presetReady": true,
1186
1261
  "support": "full",
1262
+ "profiles": [
1263
+ "3d"
1264
+ ],
1187
1265
  "primitives": [
1188
1266
  "Stylise.pixelHeight",
1189
1267
  "Stylise.vertexSnap",
@@ -1235,6 +1313,9 @@
1235
1313
  "preset": "templates/looks/coarse-pixel.ogt",
1236
1314
  "presetReady": true,
1237
1315
  "support": "full",
1316
+ "profiles": [
1317
+ "2d"
1318
+ ],
1238
1319
  "primitives": [
1239
1320
  "Stylise.pixelHeight",
1240
1321
  "Stylise.palette",
@@ -1283,6 +1364,9 @@
1283
1364
  "preset": "templates/looks/anime-cel-3d.ogt",
1284
1365
  "presetReady": true,
1285
1366
  "support": "full",
1367
+ "profiles": [
1368
+ "3d"
1369
+ ],
1286
1370
  "primitives": [
1287
1371
  "StandardMaterialDesc.toon",
1288
1372
  "StandardMaterialDesc.gradientMap",
@@ -1334,6 +1418,9 @@
1334
1418
  "preset": "templates/looks/inked-comic-3d.ogt",
1335
1419
  "presetReady": true,
1336
1420
  "support": "full",
1421
+ "profiles": [
1422
+ "3d"
1423
+ ],
1337
1424
  "primitives": [
1338
1425
  "PostEffects.outline",
1339
1426
  "StandardMaterialDesc.toon",
@@ -1385,6 +1472,9 @@
1385
1472
  "preset": "templates/looks/paper-craft.ogt",
1386
1473
  "presetReady": true,
1387
1474
  "support": "full",
1475
+ "profiles": [
1476
+ "3d"
1477
+ ],
1388
1478
  "primitives": [
1389
1479
  "StandardMaterialDesc.flatShading",
1390
1480
  "MaterialStyle.flatShading",
@@ -1435,6 +1525,9 @@
1435
1525
  "preset": "templates/looks/miniature-diorama.ogt",
1436
1526
  "presetReady": true,
1437
1527
  "support": "full",
1528
+ "profiles": [
1529
+ "3d"
1530
+ ],
1438
1531
  "primitives": [
1439
1532
  "PostEffects.depthOfField",
1440
1533
  "PostEffects.ao",
@@ -1485,6 +1578,9 @@
1485
1578
  "preset": "templates/looks/painterly-3d.ogt",
1486
1579
  "presetReady": true,
1487
1580
  "support": "full",
1581
+ "profiles": [
1582
+ "3d"
1583
+ ],
1488
1584
  "primitives": [
1489
1585
  "Stylise.paintRadius",
1490
1586
  "Stylise.paintAnisotropy",
@@ -1535,6 +1631,9 @@
1535
1631
  "preset": "templates/looks/cinematic-realism.ogt",
1536
1632
  "presetReady": true,
1537
1633
  "support": "full",
1634
+ "profiles": [
1635
+ "3d"
1636
+ ],
1538
1637
  "primitives": [
1539
1638
  "StandardMaterial (PBR)",
1540
1639
  "Environment",
@@ -1587,6 +1686,9 @@
1587
1686
  "preset": "templates/looks/muted-cinematic-3d.ogt",
1588
1687
  "presetReady": true,
1589
1688
  "support": "full",
1689
+ "profiles": [
1690
+ "3d"
1691
+ ],
1590
1692
  "primitives": [
1591
1693
  "PostEffects.godRays",
1592
1694
  "Fog",
@@ -1638,6 +1740,10 @@
1638
1740
  "preset": "templates/looks/layered-silhouette.ogt",
1639
1741
  "presetReady": true,
1640
1742
  "support": "full",
1743
+ "profiles": [
1744
+ "2d",
1745
+ "3d"
1746
+ ],
1641
1747
  "primitives": [
1642
1748
  "Stylise.depthBands",
1643
1749
  "Fog",
@@ -1687,6 +1793,10 @@
1687
1793
  "preset": "templates/looks/minimal-geometric.ogt",
1688
1794
  "presetReady": true,
1689
1795
  "support": "full",
1796
+ "profiles": [
1797
+ "2d",
1798
+ "3d"
1799
+ ],
1690
1800
  "primitives": [
1691
1801
  "unlit materials",
1692
1802
  "boxGeometry/sphereGeometry/planeGeometry",
@@ -1736,6 +1846,10 @@
1736
1846
  "preset": "templates/looks/neon-vector.ogt",
1737
1847
  "presetReady": true,
1738
1848
  "support": "full",
1849
+ "profiles": [
1850
+ "2d",
1851
+ "3d"
1852
+ ],
1739
1853
  "primitives": [
1740
1854
  "RenderSettings.bloom",
1741
1855
  "emissive unlit materials",
@@ -1787,6 +1901,9 @@
1787
1901
  "preset": "templates/looks/monochrome-dither-3d.ogt",
1788
1902
  "presetReady": true,
1789
1903
  "support": "full",
1904
+ "profiles": [
1905
+ "3d"
1906
+ ],
1790
1907
  "primitives": [
1791
1908
  "Stylise.posterize",
1792
1909
  "Stylise.palette",
@@ -1836,6 +1953,10 @@
1836
1953
  "preset": "templates/looks/luminous-layered-painting.ogt",
1837
1954
  "presetReady": true,
1838
1955
  "support": "full",
1956
+ "profiles": [
1957
+ "2d",
1958
+ "3d"
1959
+ ],
1839
1960
  "primitives": [
1840
1961
  "sprite layers",
1841
1962
  "Fog",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@origingame/engine-knowledge",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Origin Engine knowledge for agents: the API wiki generated from api.ogt, the eleven og-* skills, the guides (three-to-og), the style catalogue and the project scaffold description, laid out for the OriginGame platform's knowledge packs. Not published on its own: @origingame/engine carries it as knowledge/ (docs/release.md §2).",
5
5
  "license": "MIT",
6
6
  "private": true,
@@ -4,13 +4,13 @@
4
4
  "substrate": "origin-engine-ts",
5
5
  "engine": {
6
6
  "package": "@origingame/engine",
7
- "version": "0.4.0",
7
+ "version": "0.5.0",
8
8
  "repository": "origingame/origin-engine"
9
9
  },
10
10
  "template": {
11
11
  "source": "templates/game",
12
12
  "create": "@origingame/create-game",
13
- "version": "0.4.0"
13
+ "version": "0.5.0"
14
14
  },
15
15
  "probe": {
16
16
  "protocol": 5,
@@ -13,6 +13,6 @@
13
13
  "test": "og test ."
14
14
  },
15
15
  "dependencies": {
16
- "@origingame/engine": "0.4.0"
16
+ "@origingame/engine": "0.5.0"
17
17
  }
18
18
  }
@@ -7,7 +7,7 @@ version 1
7
7
  name "Origin Game"
8
8
  id "origin-game"
9
9
  # The engine this game is written against (design §9: upgrades are explicit).
10
- engine "0.4.0"
10
+ engine "0.5.0"
11
11
  entry "src/main.ts"
12
12
  # 2d (orthographic camera, sprites, 2D physics) or 3d.
13
13
  profile "3d"
@@ -98,6 +98,7 @@ export function validate(roots: Roots = defaultRoots()): string[] {
98
98
  apiNames(roots, files, problems);
99
99
  looks(roots, files, problems);
100
100
  grammar(roots, files, problems);
101
+ problems.push(...ogtShape(skillTexts(files)));
101
102
  scaffold(roots, rules, manifest.overlay, files, byPath, problems);
102
103
  return problems;
103
104
  }
@@ -517,7 +518,7 @@ interface Style {
517
518
  descriptor: string;
518
519
  checks: string[];
519
520
  premise: Record<string, string>;
520
- look: { preset: string; presetReady: boolean; support: string; primitives: string[]; missing: string[] };
521
+ look: { preset: string; presetReady: boolean; support: string; profiles: string[]; primitives: string[]; missing: string[] };
521
522
  ui: string;
522
523
  palette: { size: string; rules: string };
523
524
  assets: Record<string, string>;
@@ -526,6 +527,8 @@ interface Style {
526
527
 
527
528
  /** What `look.support` may say: full (every primitive draws), partial or planned (`missing` names what is still to land), content (the look is authored art). */
528
529
  export const SUPPORT = ["full", "partial", "planned", "content"] as const;
530
+ /** What `look.profiles` may name: the project profiles a style draws for (create's premise match never offers a style outside the game's). */
531
+ export const PROFILES = ["2d", "3d"] as const;
529
532
  /** The placeholders the art-bible template may use. */
530
533
  export const BIBLE_FIELDS = ["title", "premise", "camera", "platform", "name", "descriptor", "direction", "palette", "assets", "ui", "avoid", "game", "slug", "checks"] as const;
531
534
  const PREMISE = ["genre", "mood", "era", "audience"];
@@ -537,7 +540,7 @@ const ASSET_SPECS = ["logicalResolution", "spriteScale", "faceBudget", "outlineP
537
540
  * bible direction), its preset path `templates/looks/<slug>.ogt` (a
538
541
  * `type look` file when presetReady), its support honest (`missing` names
539
542
  * the primitives a style that is not `full` lacks, and is empty for one
540
- * that is), every `Resource.field` it names a member of the generated API,
543
+ * that is), the profiles it draws for (`look.profiles`, 2d and/or 3d), every `Resource.field` it names a member of the generated API,
541
544
  * og-taste naming every slug, and no text phrased as an imitation.
542
545
  */
543
546
  function looks(roots: Roots, files: readonly PayloadFile[], problems: string[]): void {
@@ -591,6 +594,10 @@ function looks(roots: Roots, files: readonly PayloadFile[], problems: string[]):
591
594
  }
592
595
  if (look.preset !== `templates/looks/${s.slug}.ogt`) problems.push(`${id}: look.preset is ${look.preset}, expected templates/looks/${s.slug}.ogt`);
593
596
  if (!(SUPPORT as readonly string[]).includes(look.support)) problems.push(`${id}: look.support ${look.support} is none of ${SUPPORT.join(", ")}`);
597
+ const profiles = Array.isArray(look.profiles) ? look.profiles : [];
598
+ if (profiles.length === 0 || new Set(profiles).size !== profiles.length || !profiles.every((p) => (PROFILES as readonly string[]).includes(p))) {
599
+ problems.push(`${id}: look.profiles must name the profiles the style draws for, one or both of ${PROFILES.join(", ")} (create matches a premise only among the game's)`);
600
+ }
594
601
  const missing = look.missing ?? [];
595
602
  if (look.support === "full" && missing.length > 0) problems.push(`${id}: support full, yet it names missing primitives`);
596
603
  if (look.support !== "full" && !missing.some(text)) problems.push(`${id}: support ${look.support} must name what is missing (the primitive or the authored art)`);
@@ -690,7 +697,7 @@ export const GRAMMARS: readonly Grammar[] = [
690
697
  source: "crates/og-anim/src/graph.rs",
691
698
  region: [/pub fn from_document/, /\/\/\/ The slot of a parameter/],
692
699
  reads: readKeys,
693
- names: { param: 1, clip: 1, blend1d: 2, blend2d: 3, child: 1, state: 2, entry: 1, transition: 2, when: 1, weight: 1 },
700
+ names: { param: 1, clip: 1, blend1d: 2, blend2d: 3, child: 1, state: 2, entry: 1, transition: 2, when: 1, weight: 1, sync: 1 },
694
701
  },
695
702
  { type: "navmesh", source: "crates/og-cook/src/cook/e6.rs", region: [/pub\(super\) fn cook_navmesh/, /og_nav::bake::bake\(/], reads: readKeys, home: "og-ai" },
696
703
  {
@@ -842,14 +849,70 @@ export function grammarDrift(skills: ReadonlyMap<string, string>, read: (path: s
842
849
  return problems;
843
850
  }
844
851
 
845
- function grammar(roots: Roots, files: readonly PayloadFile[], problems: string[]): void {
846
- const repo = join(roots.pkg, "..", "..");
847
- if (!existsSync(join(repo, "crates"))) return; // the published package: the parsers are not beside it
852
+ /** Each skill's SKILL.md text, by skill name. */
853
+ function skillTexts(files: readonly PayloadFile[]): Map<string, string> {
848
854
  const skills = new Map<string, string>();
849
855
  for (const f of files) {
850
856
  const m = /^game\/skills\/([^/]+)\/SKILL\.md$/.exec(f.path);
851
857
  if (m && isText(f.bytes)) skills.set(m[1]!, decoder.decode(f.bytes));
852
858
  }
859
+ return skills;
860
+ }
861
+
862
+ /**
863
+ * Every `.ogt` example a skill writes in a code block (```text or a bare
864
+ * fence, as a whole file or a fragment) holds to og-core's parser
865
+ * (crates/og-core/src/ogt.rs `parse`) line by line, so an author who copies
866
+ * it cooks: a `{` ends its key's line, a `}` stands alone on its own, the
867
+ * blocks close, and a file that opens with `type` has `version` and `---`
868
+ * next. A block with no brace and no `type` line is not checked (shell,
869
+ * prose). Strings and `#` comments are read as the parser reads them.
870
+ */
871
+ export function ogtShape(skills: ReadonlyMap<string, string>): string[] {
872
+ const problems: string[] = [];
873
+ for (const [skill, md] of skills) {
874
+ for (const m of md.matchAll(/^( *)```([\w-]*)\n([\s\S]*?)^ *```/gm)) {
875
+ if (m[2] !== "text" && m[2] !== "") continue;
876
+ const indent = m[1]!.length;
877
+ const lines = m[3]!.split("\n").map((l) => l.slice(Math.min(indent, l.length - l.trimStart().length)));
878
+ const words = lines.map((l) => {
879
+ const out: string[] = [];
880
+ for (const t of l.matchAll(/"(?:[^"\\]|\\.)*"|\S+/g)) {
881
+ if (t[0].startsWith("#")) break;
882
+ out.push(t[0]);
883
+ }
884
+ return out;
885
+ });
886
+ const typed = words.find((w) => w.length > 0)?.[0] === "type";
887
+ if (!typed && !words.some((w) => w.includes("{") || w.includes("}"))) continue;
888
+ const first = md.slice(0, m.index).split("\n").length + 1;
889
+ const at = (i: number) => `skills/${skill}/SKILL.md:${first + i}`;
890
+ if (typed) {
891
+ const head = words.map((w, i) => [w, i] as const).filter(([w]) => w.length > 0).slice(0, 3);
892
+ if (head[1]?.[0][0] !== "version" || head[2]?.[0].join(" ") !== "---") problems.push(`${at(head[0]![1])}: an .ogt example opens with \`type\` but not \`version <n>\` and \`---\` after it; the parser refuses a file without its header`);
893
+ }
894
+ let depth = 0;
895
+ words.forEach((w, i) => {
896
+ const open = w.indexOf("{");
897
+ if (open >= 0 && open !== w.length - 1) problems.push(`${at(i)}: \`${lines[i]!.trim()}\` opens a block mid-line; the .ogt parser refuses it ('{' must end its line): put each line of the block on its own`);
898
+ if (w.includes("}") && w.length !== 1) problems.push(`${at(i)}: \`${lines[i]!.trim()}\` closes a block beside other words; the .ogt parser refuses it ('}' must be alone on its line)`);
899
+ if (open >= 0) depth++;
900
+ if (w.includes("}")) depth--;
901
+ if (depth < 0) {
902
+ problems.push(`${at(i)}: an unmatched '}'`);
903
+ depth = 0;
904
+ }
905
+ });
906
+ if (depth > 0) problems.push(`${at(lines.length - 1)}: an .ogt example leaves ${depth} block(s) open`);
907
+ }
908
+ }
909
+ return problems;
910
+ }
911
+
912
+ function grammar(roots: Roots, files: readonly PayloadFile[], problems: string[]): void {
913
+ const repo = join(roots.pkg, "..", "..");
914
+ if (!existsSync(join(repo, "crates"))) return; // the published package: the parsers are not beside it
915
+ const skills = skillTexts(files);
853
916
  problems.push(...grammarDrift(skills, (p) => (existsSync(join(repo, p)) ? readFileSync(join(repo, p), "utf8") : undefined)));
854
917
  }
855
918
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: og-audio
3
- description: Sound on Origin Engine — one-shot voices for game events, the procedural sound presets og cook synthesises (coin, jump, hit, click and more), UI sounds, the four buses and their effects (filters, reverb send, compressor or limiter, ducking), music with stems, bar-aligned starts, crossfades and stingers on og.Music's clock, spatial sources with occlusion and distance dulling, voice priority and the cap, and proving it all headless with listen and og run --audio. Use when a game is silent, when adding any sound or music, or when the mix is muddy, clipping or wrong.
3
+ description: Sound on Origin Engine — one-shot voices for game events, the procedural sound presets og cook synthesises (coin, jump, hit, click and more), UI sounds, the four buses and their effects (filters, reverb send, compressor or limiter, ducking), music with stems, bar-aligned starts, crossfades and stingers on og.Music's clock, spatial sources with occlusion, distance dulling, Doppler, cones and HRTF for headphones, voice priority and the cap, and proving it all headless with listen and og run --audio. Use when a game is silent, when adding any sound or music, or when the mix is muddy, clipping or wrong.
4
4
  ---
5
5
 
6
6
  Audio on Origin Engine is world state: an entity with `og.AudioSource`
@@ -34,9 +34,11 @@ given and taken, pickups, a door, a death, a win and a loss, every UI press.
34
34
 
35
35
  - One voice entity per sound, spawned in `start` with `playing: false`, and
36
36
  a `Voices` system that plays each requested one: systems set bits of a
37
- game resource (`Sounds.sfx |= Sound.Jump`), Voices sets `playing` on the
38
- matching voice (a voice already playing is stopped this tick and
39
- restarted the next). Every archetype in `templates/archetypes` does
37
+ game resource (`Sounds.sfx |= Sound.Jump`), Voices sets `retrigger: true`
38
+ on the matching voice (it starts again from the top in that tick,
39
+ playing or not, and the engine clears it; archetypes written before
40
+ `retrigger` set `playing`, stopping a playing voice one tick and
41
+ starting it the next). Every archetype in `templates/archetypes` does
40
42
  this; copy a `Voices`.
41
43
  - Vary repeated sounds: `pitch` 0.92–1.08 from the world's `og.Rng` for
42
44
  footsteps, hits and coins, so a stream of them does not machine-gun.
@@ -173,10 +175,34 @@ at 1.0 is a mix bug, not loudness.
173
175
  the listener is. It follows the listener's world transform, so put the
174
176
  og.AudioListener where the ears are (the camera, or the player); a zone
175
177
  on a child of a moving room moves with it.
178
+ - Motion is heard: a spatial source or listener that moves shifts the
179
+ pitch (Doppler, from tick velocities down `og.Parent`, so a car on a
180
+ rig or a listener on a moving camera just works). `dopplerFactor` 1 is
181
+ physical; 0.3–0.6 on a racing game's engines keeps a fly-by readable;
182
+ 0 on a source that must keep its pitch (a voice line, a musical
183
+ instrument in the world) or on the `og.AudioListener` to turn it off
184
+ everywhere. A warp or respawn (a jump faster than sound in one tick)
185
+ shifts nothing. `og.Audio.speedOfSound` (343.3) is the scale.
186
+ - Directional sources: `coneInnerAngle`, `coneOuterAngle` (full angles,
187
+ degrees, about the entity's +Z, as three.js's `setDirectionalCone`)
188
+ and `coneOuterGain`: a loudspeaker or an NPC talking forward `90`,
189
+ `240`, `0.3`; a siren on a vehicle `120`, `300`, `0.5`. Rotate the
190
+ entity to aim it; the default 360/360 is omnidirectional.
191
+ - Headphones: `og.AudioListener.panningModel: "hrtf"` places spatial
192
+ sources by interaural delay and head shadow (a spherical-head model),
193
+ clearer left and right than the stereo panner, without front/back or
194
+ height cues; offer it as a setting ("Headphones"), keep `equalpower`
195
+ for speakers (the default). Non-spatial sources keep their `pan`.
176
196
  - `og.Audio.maxVoices` (32) caps voices; past it the lowest `priority`,
177
197
  then the quietest, then the oldest is culled (`SOUND_CULLED`). Give the
178
198
  player's own sounds and the music a high priority (200+), ambient props a
179
- low one.
199
+ low one. Ambient loops and music stems that should keep their place
200
+ when culled get `virtualize: true`: past the cap they go silent with
201
+ their playhead running and come back where they would be, not from the
202
+ top (`og.AudioPlayback.virtual` says which).
203
+ - `og.Audio.resampler` is `sinc` (clean highs at any pitch); `linear` is
204
+ the older, cheaper mix, kept for a game that compares its exact output
205
+ (a WAV hash in a test). Unpitched 48 kHz sounds play the same either way.
180
206
 
181
207
  ## 7. Prove it
182
208