@origingame/engine 0.3.0 → 0.4.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 (84) hide show
  1. package/dist/web/origin-engine.d.ts +38 -2
  2. package/dist/web/origin-engine.js +250 -10
  3. package/dist/web/origin-engine_bg.wasm +0 -0
  4. package/dist/web/origin-engine_bg.wasm.d.ts +14 -2
  5. package/dist/web-publish/origin-engine-lanes.js +255 -34
  6. package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
  7. package/dist/web-publish/origin-engine.d.ts +39 -3
  8. package/dist/web-publish/origin-engine.js +253 -32
  9. package/dist/web-publish/origin-engine_bg.wasm +0 -0
  10. package/dist/web-publish/origin-engine_bg.wasm.d.ts +15 -3
  11. package/dist/web-publish-2d/origin-engine-lanes.js +253 -32
  12. package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
  13. package/dist/web-publish-2d/origin-engine.d.ts +38 -2
  14. package/dist/web-publish-2d/origin-engine.js +250 -29
  15. package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
  16. package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +14 -2
  17. package/knowledge/README.md +2 -1
  18. package/knowledge/guides/three-to-og.md +393 -0
  19. package/knowledge/knowledge.json +2 -1
  20. package/knowledge/package.json +3 -2
  21. package/knowledge/scaffold/origin-engine.json +2 -2
  22. package/knowledge/scaffold/template/package.json +1 -1
  23. package/knowledge/scaffold/template/project.ogt +1 -1
  24. package/knowledge/scripts/validate.ts +140 -21
  25. package/knowledge/skills/og-audio/SKILL.md +2 -2
  26. package/knowledge/skills/og-build/SKILL.md +22 -3
  27. package/knowledge/skills/og-core/SKILL.md +20 -5
  28. package/knowledge/skills/og-feel/SKILL.md +2 -2
  29. package/knowledge/skills/og-game/SKILL.md +481 -35
  30. package/knowledge/skills/og-i18n/SKILL.md +7 -7
  31. package/knowledge/skills/og-model/SKILL.md +374 -0
  32. package/knowledge/skills/og-platform-sdk/SKILL.md +1 -1
  33. package/knowledge/skills/og-ui/SKILL.md +15 -8
  34. package/knowledge/wiki/ai.md +20 -20
  35. package/knowledge/wiki/anim.md +63 -55
  36. package/knowledge/wiki/atmosphere.md +13 -13
  37. package/knowledge/wiki/audio.md +18 -18
  38. package/knowledge/wiki/camera.md +13 -13
  39. package/knowledge/wiki/commands.md +11 -11
  40. package/knowledge/wiki/debug.md +13 -13
  41. package/knowledge/wiki/game.md +8 -8
  42. package/knowledge/wiki/i18n.md +7 -7
  43. package/knowledge/wiki/index.md +57 -3
  44. package/knowledge/wiki/input.md +47 -47
  45. package/knowledge/wiki/load.md +57 -57
  46. package/knowledge/wiki/nav.md +34 -34
  47. package/knowledge/wiki/net.md +10 -10
  48. package/knowledge/wiki/path.md +14 -14
  49. package/knowledge/wiki/physics.md +95 -95
  50. package/knowledge/wiki/platform.md +44 -44
  51. package/knowledge/wiki/probe.md +86 -86
  52. package/knowledge/wiki/reflect.md +16 -4
  53. package/knowledge/wiki/render.md +1168 -170
  54. package/knowledge/wiki/rng.md +14 -14
  55. package/knowledge/wiki/shapes.md +727 -0
  56. package/knowledge/wiki/stream.md +15 -15
  57. package/knowledge/wiki/system.md +8 -8
  58. package/knowledge/wiki/systems.md +2 -2
  59. package/knowledge/wiki/terrain.md +24 -20
  60. package/knowledge/wiki/tilemap.md +13 -13
  61. package/knowledge/wiki/time.md +4 -4
  62. package/knowledge/wiki/timeline.md +14 -14
  63. package/knowledge/wiki/transform.md +8 -8
  64. package/knowledge/wiki/tween.md +17 -17
  65. package/knowledge/wiki/ui.md +245 -269
  66. package/knowledge/wiki/video.md +5 -5
  67. package/knowledge/wiki/world.md +36 -36
  68. package/package.json +4 -4
  69. package/schema/api.schema.json +1834 -305
  70. package/shell/platform.ts +5 -0
  71. package/shell/player.ts +8 -2
  72. package/shell/transport.ts +6 -0
  73. package/src/generated/abi.ts +37 -1
  74. package/src/geom.ts +260 -0
  75. package/src/index.ts +8 -1
  76. package/src/node/playtest-run.ts +1 -0
  77. package/src/platform-local.ts +2 -0
  78. package/src/playtest.ts +8 -0
  79. package/src/runtime.ts +87 -6
  80. package/src/ui-kit/settings.ts +9 -0
  81. package/src/web/shell.ts +1 -1
  82. package/src/web/worker.ts +2 -2
  83. package/types/bindings.json +697 -402
  84. package/types/index.d.ts +1045 -127
@@ -116,6 +116,7 @@ Mistakes the games made, or would have, before reading the right page:
116
116
  | **og-feel** | how much: hit stop, shake, squash, easing, floating text, the camera, frame rates, sorting, parallax, controller tunables | time, tween, camera, systems |
117
117
  | **og-ui** | screens and HUDs: templates, widgets, focus, phones, UI styles and motion | ui |
118
118
  | **og-taste** | the look: a style from the premise, composition, colour, type, motion, feedback, atmosphere by intent, the done checklist | — (it uses ui and atmosphere) |
119
+ | **og-model** | a 3D model built as code from a reference image: the detail inventory, eight gated passes, the generators' recipes (§18 has their fields), budgets, the review frames and `og compare` | — (it uses render and shapes) |
119
120
  | **og-ai** | enemies and NPCs: navigation and minds as data | nav, ai |
120
121
  | **og-audio** | sound: events, presets, UI sounds, the mix, music, space | audio |
121
122
  | **og-i18n** | languages: string tables, messages, fonts, line breaking, right to left | i18n |
@@ -155,7 +156,14 @@ beside the rebuild: the reload still happens, the `reload` event carries a
155
156
  and a `check` event with the diagnostics follows; `og test --json` ends
156
157
  with a `test` event whose `check` object has the same three. Read the
157
158
  diagnostics and fix them before anything else; `--no-check` skips the
158
- check (never for `og build --publish`). A created project's
159
+ check (never for `og build --publish`). A `2d` project's publish build
160
+ leaves the 3D world out (terrain, planar water, scattered copies, the 3D
161
+ fluids, volumetric fog: `og.render.capabilities(world).world3d` is false
162
+ there), so `og build --publish` refuses one whose scenes hold `Terrain`,
163
+ `TerrainScatter`, `Water`, `WaterSurface` or `Fluid3D`, naming each
164
+ `scene.ogt:line`, and there `og.terrain.*` and a `SHAPE_HEIGHTFIELD`
165
+ collider answer a refusal naming `world3d`: remove them, or make the
166
+ project `profile "3d"`. A created project's
159
167
  `tsconfig.json` resolves `@origingame/engine` (the generated
160
168
  `types/index.d.ts`) and `@origingame/engine/playtest` from `node_modules`.
161
169
  An example inside the engine repository has no `node_modules`: its
@@ -280,9 +288,13 @@ registered schema and refuses a model path that does not exist.
280
288
  A `.vox` with frames (MagicaVoxel's animation) cooks to clips that swap
281
289
  the frames' models: play them as skinned clips (§9). `voxFps 8` sets the
282
290
  rate (default 10), `voxClip walk 0 3` names frames 0 to 3 (repeatable;
283
- without it one clip `frames` holds every frame). Each frame is centred on
291
+ without it one clip `frames` holds the playback range set in the editor,
292
+ else every frame; a node marked to loop repeats its keys over the file's
293
+ frames). Each frame is centred on
284
294
  its own pivot, as in the editor: give the frames one height (or pad them)
285
295
  for a character whose feet stay on the ground.
296
+ - An `.ogt` of `type model` is a model written as text: og-geom parts by
297
+ their `og.render` names, placed, with materials and pivots (§18).
286
298
  - Reusable production assets come through the platform's asset catalog into
287
299
  `assets/`; record each file's licence next to it.
288
300
 
@@ -380,7 +392,13 @@ else ignores it. The stamp is world state, so a replay reproduces it.
380
392
  `flipX`, `flash`, `shadow`, nine-slice `sliceX`/`sliceY`) is drawn every
381
393
  frame at its transform, its scale multiplying the size (so squash and
382
394
  stretch is a tween of `og.Transform.scale`). `og.render.sprite` stays for
383
- one-tick, immediate drawing (HUDs, debug).
395
+ one-tick, immediate drawing (HUDs, debug). Sprites and og.ui's frames
396
+ are filtered anisotropically where the device can
397
+ (`og.render.capabilities(world).spriteAnisotropy`: 16, or 1 on a WebGL2
398
+ without `EXT_texture_filter_anisotropic` and on a software rasterizer), so a sprite or a nine-slice
399
+ squeezed along one axis keeps the other axis's detail (a button's
400
+ outline stays sharp); at 1 it blurs, and og.ui bounds a frame's middle
401
+ at twice its art for that reason.
384
402
  - **Pixel sprites in a 3D set** stand on the ground with `og.Sprite.dropShadow`
385
403
  (or `SpriteDesc.dropShadow`): 0.5 casts the sprite's shape, darkened and
386
404
  flattened onto the ground plane (y 0) along the sun, the first
@@ -980,7 +998,9 @@ pixels then, per bone, `bone x y weight` in that bone's space (weights
980
998
  summing to 1), or plain `x y` in the slot's bone. A bone's or region's
981
999
  `skew` turns its y axis that many degrees past its x axis. A slot draws
982
1000
  `texel × color + offset`. Bone keys add to the setup pose (`rotate` and
983
- `skew` degrees, `translate` pixels, `scale` multiplies); every key eases
1001
+ `skew` degrees, `translate` pixels, `scale` multiplies); a `rotate` key
1002
+ turns the shorter way to the next, or `rotate 0 0 clockwise 1` (or
1003
+ `counterclockwise n`) that way through n more whole turns; every key eases
984
1004
  linearly to the next, holds with `step`, or follows `curve x1 y1 x2 y2`
985
1005
  (a cubic Bézier from 0,0 to 1,1, x time and y value; more segments as
986
1006
  `x y x1 y1 x2 y2`). A `deform` key offsets every influence in order, one
@@ -988,15 +1008,26 @@ linearly to the next, holds with `step`, or follows `curve x1 y1 x2 y2`
988
1008
  then (alone, the file's order); an `ik` key sets the constraint's weight
989
1009
  and bend. An `ik`'s `bones` are a chain of any length, root first: one
990
1010
  bone points, two bend at the joint, three or more (a tail, a tentacle, a
991
- three-joint leg) are solved by FABRIK and curl to the `bend` side. A `box` draws nothing: `og.anim.skeleton2dBoxes(world, hero)`
1011
+ three-joint leg) are solved by FABRIK and curl to the `bend` side. After the IK, a
1012
+ `transform t { bones a; target b; mix 1 1 1 1 }` pulls bones towards
1013
+ another bone's world rotation, origin, scale and shear by its mix, and an
1014
+ `along s { path rail; bones s1 s2; position 0 percent; spacing 0 length;
1015
+ rotate tangent 0; mix 1 1 }` lays bones along a `path` attachment (a
1016
+ cubic spline, `vertex x y` three per knot, drawn as nothing) while its
1017
+ slot shows it. A `skin armor { swap torso torso-armor; swap hat none }`
1018
+ block shows other attachments of the same slots wherever the default ones
1019
+ would show; `og.Skeleton2D.skin` picks it (`rig.skins.indexOf("armor")`,
1020
+ `default` is 0). A `box` draws nothing: `og.anim.skeleton2dBoxes(world, hero)`
992
1021
  answers its polygon (in the Transform's frame) while its slot shows it,
993
1022
  for hit and hurt boxes a clip switches. A `child` shows an `armature`
994
1023
  block (a rig of its own on the same texture) in its slot; it plays the
995
1024
  parent's clip by name when it has one, else its `clip`, at the parent's
996
- time. A DragonBones 5.5 export is imported instead of written: a rig2d
1025
+ time; its events arrive as og.Skeleton2DEvent with `armature` its name
1026
+ (empty for the skeleton's own clip). A DragonBones 5.5 export is imported instead of written: a rig2d
997
1027
  `.ogt` with `dragonbones "hero_ske.json"` and `atlas "hero_tex.json"`
998
1028
  (skew, colour offsets, zOrder and IK timelines, armature displays,
999
- bounding boxes and curves included; Spine's files are not read, convert
1029
+ bounding boxes, path displays and constraints, every skin, curves and `clockwise`
1030
+ turns included; Spine's files are not read, convert
1000
1031
  them to this text).
1001
1032
 
1002
1033
  ```ts
@@ -1369,8 +1400,10 @@ render sprite 12
1369
1400
  `underwaterDensity` and the surface from below shows the sky only
1370
1401
  straight up (on by default; set the density to 0 to see through).
1371
1402
 
1372
- **Terrain** (`reference/terrain.md`) is a height field cooked from a
1373
- `type terrain` .ogt beside a 16-bit grey PNG (or an EXR in metres):
1403
+ **Terrain** (`reference/terrain.md`, 3D projects only: a `2d` project's
1404
+ publish build has none, §2) is a height field cooked from a
1405
+ `type terrain` .ogt beside a 16-bit grey PNG (or an EXR in metres), or
1406
+ generated by the cook (below):
1374
1407
 
1375
1408
  ```text
1376
1409
  type terrain
@@ -1381,9 +1414,23 @@ height -20 120 # metres
1381
1414
  size 2048 2048 # metres along x and z, centred on the entity
1382
1415
  control "island-control.png" # optional RGBA: layer 0 in R, 1 in G, 2 in B, 3 in A
1383
1416
  heightBlend 0.15 # sharpen the blends by each albedo's alpha (its height)
1384
- layer { albedo "textures/grass.png" normal "textures/grass-n.png" tile 6 slope 0 30 }
1385
- layer { albedo "textures/rock.png" tile 12 triplanar true slope 28 90 }
1386
- layer { albedo "textures/snow.png" altitude 90 1000 altitudeFade 10 }
1417
+ layer { # a block's '}' stands alone on its line
1418
+ albedo "textures/grass.png"
1419
+ normal "textures/grass-n.png"
1420
+ tile 6
1421
+ slope 0 30
1422
+ }
1423
+ layer {
1424
+ albedo "textures/rock.png"
1425
+ tile 12
1426
+ triplanar true
1427
+ slope 28 90
1428
+ }
1429
+ layer {
1430
+ albedo "textures/snow.png"
1431
+ altitude 90 1000
1432
+ altitudeFade 10
1433
+ }
1387
1434
  ```
1388
1435
 
1389
1436
  A layer shows where its control channel (1 without a map) times its slope
@@ -1398,6 +1445,151 @@ world.insert(ground, og.Terrain, { data: info.data });
1398
1445
  world.insert(ground, og.Collider, { shape: SHAPE_HEIGHTFIELD, mesh: info.geometry });
1399
1446
  ```
1400
1447
 
1448
+ **A generated terrain.** Instead of `heightmap`, a `generate` block makes
1449
+ the field in the cook (never both: the cook refuses the pair and names
1450
+ the lines). `height` and `size` mean what they mean for a heightmap: the
1451
+ field is stretched so its lowest sample is `height`'s low and its highest
1452
+ its high, and no sample leaves that range. Every line, with its default
1453
+ (leave out what you do not need; erosion, thermal, terraces, rivers and
1454
+ the sea are off until given):
1455
+
1456
+ ```text
1457
+ type terrain
1458
+ version 1
1459
+ ---
1460
+ height -40 160 # metres
1461
+ size 1024 1024 # metres along x and z
1462
+ generate {
1463
+ samples 257 257 # columns and rows (2 to 4097)
1464
+ seed 1 # another seed, another terrain of the same kind
1465
+ noise {
1466
+ octaves 6 # 1 to 12: more is finer detail
1467
+ frequency 3 # features across the longer side
1468
+ lacunarity 2 # each octave's frequency over the last's (1 to 8)
1469
+ gain 0.5 # each octave's amplitude over the last's (0 to 1): higher is rougher
1470
+ warp 0 # domain warp in features: 0.3 to 1 bends ridges and coasts
1471
+ }
1472
+ mask island 0.4 # land fades to `height`'s low over this share of the half size
1473
+ # or `mask ridge 1`: sharp ridged mountains (0 to 1); default no mask
1474
+ erosion { # hydraulic erosion: seeded drops carve gullies and fill valleys
1475
+ drops 40000 # ~0.5 to 1 per sample; more is deeper erosion and a slower cook
1476
+ lifetime 30
1477
+ radius 3 # samples each drop erodes around it
1478
+ inertia 0.05
1479
+ capacity 4
1480
+ deposition 0.3
1481
+ erosion 0.3
1482
+ evaporation 0.01
1483
+ gravity 4
1484
+ }
1485
+ thermal { # slopes steeper than talus slump into scree
1486
+ talus 35 # degrees
1487
+ iterations 20
1488
+ rate 0.5
1489
+ }
1490
+ terraces {
1491
+ step 10 # metres between steps
1492
+ blend 0.4 # 0 hard steps, 1 smooth ramps
1493
+ }
1494
+ sea 0 # metres above the entity: og.terrain.load's seaLevel
1495
+ shore 2 # metres either side of the sea flattened into beaches (needs sea)
1496
+ rivers { # carved where the flow gathers, down to the sea or the edge
1497
+ threshold 400 # samples draining through before it is a river: lower, more rivers
1498
+ depth 2 # metres
1499
+ width 6 # metres
1500
+ }
1501
+ moisture { # what biomes read, 0 to 1
1502
+ flow 1 # weight of the gathered flow (wet in valleys and by rivers)
1503
+ scale 64 # samples draining through for half of it
1504
+ noise 0.5 # weight of a noise
1505
+ frequency 6
1506
+ }
1507
+ biome { # any number; each writes its layer's control channel
1508
+ layer 0 # required: the layer (0 to 3, one the terrain has)
1509
+ weight 1
1510
+ altitude -1000000 1000000 # metres above the entity
1511
+ altitudeFade 2
1512
+ slope 0 90 # degrees
1513
+ slopeFade 4
1514
+ moisture 0 1
1515
+ moistureFade 0.1
1516
+ }
1517
+ }
1518
+ layer {
1519
+ albedo "textures/sand.png"
1520
+ tile 4
1521
+ }
1522
+ layer {
1523
+ albedo "textures/grass.png"
1524
+ tile 6
1525
+ }
1526
+ layer {
1527
+ albedo "textures/rock.png"
1528
+ tile 12
1529
+ triplanar true
1530
+ }
1531
+ ```
1532
+
1533
+ An island with beaches, rivers and rock on its cliffs, for that file's
1534
+ three layers (a block's `{` ends its line):
1535
+
1536
+ ```text
1537
+ generate {
1538
+ seed 7
1539
+ noise {
1540
+ warp 0.5
1541
+ }
1542
+ mask island 0.45
1543
+ erosion {
1544
+ drops 40000
1545
+ }
1546
+ sea 6
1547
+ shore 3
1548
+ rivers {
1549
+ threshold 300
1550
+ depth 2.5
1551
+ width 8
1552
+ }
1553
+ biome {
1554
+ layer 0
1555
+ altitude -1000 9
1556
+ }
1557
+ biome {
1558
+ layer 1
1559
+ altitude 9 1000
1560
+ slope 0 32
1561
+ }
1562
+ biome {
1563
+ layer 2
1564
+ slope 32 90
1565
+ }
1566
+ }
1567
+ ```
1568
+
1569
+ With `biome` rules the generator writes the control map (a terrain with
1570
+ biomes names no `control`); a channel is the most any of its layer's
1571
+ biomes gives a sample, its `weight` times its three bands. Without biomes
1572
+ there is no control map and the layers' own `slope` and `altitude`
1573
+ place them, as for a heightmap. The same file makes the same terrain on
1574
+ every machine (the generator uses only exact arithmetic and its own
1575
+ random numbers). It is a credible terrain of a kind, never a terrain from
1576
+ a picture: move the seed, the mask and the erosion until it reads right.
1577
+ `og cook --report` gives its height histogram, the share of land above
1578
+ the sea and the rivers' length, and writes its height and control maps
1579
+ as PNGs to `og-out/terrain-maps/` to look at.
1580
+
1581
+ Its sea is the water's level; the water is the terrain's size:
1582
+
1583
+ ```ts
1584
+ if (info.seaLevel !== null) {
1585
+ const sea = must(world.spawn({ name: "sea", position: { x: 0, y: info.seaLevel, z: 0 } }));
1586
+ world.insert(sea, og.Water, { size: info.size, segments: 128 });
1587
+ }
1588
+ ```
1589
+
1590
+ (on the terrain's entity's x and z, under its parent if it has one; an
1591
+ `og.WaterSurface` on the same entity makes the waves move and bodies float).
1592
+
1401
1593
  It is drawn around the camera at finer detail nearby (casting and
1402
1594
  receiving shadows, under the fog), placed by its entity's world transform
1403
1595
  (turn or scale the entity and the terrain turns and scales with it), and
@@ -1747,6 +1939,35 @@ og.render.look(world)?.name; // the
1747
1939
  stays sharper under camera motion and `taaSharpen` (0–1) restores
1748
1940
  detail TAA softens. `RenderCapabilities.antialias` says what the device
1749
1941
  resolved it to.
1942
+ - **Graphics quality** is RenderSettings' quality fields, read every
1943
+ frame: `rectShadows`, `shadowedLights`, `shadowMapScale`,
1944
+ `volumetricFog`, `softParticles`, `screenSpaceReflections`,
1945
+ `ambientOcclusion`, `msaaSamples` (4, or 1 for FXAA where the
1946
+ antialiasing multisamples). `og.render.applyQuality(world, preset)`
1947
+ writes a preset's row of them and records it in
1948
+ `RenderSettings.quality` (`none` before any):
1949
+
1950
+ | Field | low | medium | high |
1951
+ |---|---|---|---|
1952
+ | rectShadows | false | true | true |
1953
+ | shadowedLights | 2 | 8 | 8 |
1954
+ | shadowMapScale | 0.5 | 1 | 2 |
1955
+ | volumetricFog, softParticles, screenSpaceReflections, ambientOcclusion | false | true | true |
1956
+ | msaaSamples | 1 | 4 | 4 |
1957
+
1958
+ Medium is the defaults, the frame drawn before any preset. The renderer
1959
+ reads the fields and nothing else, so `og run --commands
1960
+ "0.5:get:og.RenderSettings"` shows what is drawn, and a field set after
1961
+ the preset wins. The switches only take away: the scene's PostEffects
1962
+ and lights keep their settings. The settings screen applies the
1963
+ player's pick when given the world (`og.ui.settingsScreen(id, { world,
1964
+ settings, ... })`); apply a saved preset yourself at start:
1965
+
1966
+ ```ts
1967
+ start(world) { og.render.applyQuality(world, save.settings.quality ?? "medium"); }
1968
+ ```
1969
+
1970
+ Inside a system, declare `og.RenderSettings` among its writes.
1750
1971
 
1751
1972
  ## 17. Saving progress
1752
1973
 
@@ -1891,6 +2112,201 @@ spaced along the length, as three.js spaces them. Rings and circles are one-side
1891
2112
  standard material draws the triangle edges instead of the faces, one pixel
1892
2113
  wide on every tier.
1893
2114
 
2115
+ **Shapes, extrusions, lathes and solids.** Three.js's modelling classes
2116
+ keep their names, parameters and defaults, so a three.js extrusion is a
2117
+ paste. `og.Shape` and `og.Path2` are three.js's Shape and Path:
2118
+ `moveTo`, `lineTo`, `quadraticCurveTo`, `bezierCurveTo`, `splineThru`,
2119
+ `arc`, `absarc`, `ellipse`, `absellipse`, `closePath`, `holes` and
2120
+ `getPoints(divisions)`. They flatten to points before they cross to the
2121
+ engine, so a points array works wherever a shape does
2122
+ (`reference/shapes.md`).
2123
+ - `shapeGeometry` is a flat shape with holes, one-sided like a circle.
2124
+ - `extrudeGeometry` pushes shapes along +Z (`depth`, `steps`) or along an
2125
+ og.path (`extrudePath`). `bevelEnabled`, `bevelThickness`, `bevelSize`,
2126
+ `bevelOffset` and `bevelSegments` round its edges, as in three.js.
2127
+ - `latheGeometry` turns a profile of `points` round +Y (`segments`,
2128
+ `phiStart`, `phiLength`), for vases, bottles and columns.
2129
+ - `polyhedronGeometry` takes `vertices`, `indices`, `radius` and `detail`.
2130
+ `icosahedronGeometry`, `dodecahedronGeometry`, `octahedronGeometry` and
2131
+ `tetrahedronGeometry` take `radius` and `detail`.
2132
+ - `sweepGeometry` sweeps a 2D `section` along an og.path. Its `scale` and
2133
+ `twist` are keyed over the path's length from 0 to 1, for horns, tusks,
2134
+ handles and twisted columns.
2135
+ - `mergeGeometries` places `parts` (each a geometry with `position`,
2136
+ `quaternion`, `scale` and a `mirror` axis) into one draw. Write an
2137
+ array as the same part at several placements.
2138
+ - `csgGeometry` is `union`, `subtract` or `intersect` of two placed
2139
+ closed geometries. Chain calls for more operands.
2140
+
2141
+ Every result is a geometry like `bufferGeometry`'s, with a BVH, `physics`
2142
+ and `dynamic`, and answered by `geometryInfo`:
2143
+
2144
+ ```ts
2145
+ const gear = new og.Shape().absarc(0, 0, 1, 0, Math.PI * 2, false);
2146
+ gear.holes.push(new og.Path2().absarc(0, 0, 0.3, 0, Math.PI * 2, true));
2147
+ const disc = must(og.render.extrudeGeometry(world, {
2148
+ shapes: gear, depth: 0.2, bevelEnabled: true, bevelThickness: 0.05, bevelSize: 0.05,
2149
+ }));
2150
+ const vase = must(og.render.latheGeometry(world, {
2151
+ points: [{ x: 0, y: 0 }, { x: 0.5, y: 0.1 }, { x: 0.3, y: 1 }, { x: 0.4, y: 1.4 }],
2152
+ segments: 32,
2153
+ }));
2154
+ const cube = must(og.render.boxGeometry(world, { width: 1, height: 1, depth: 1 }));
2155
+ const ball = must(og.render.sphereGeometry(world, { radius: 0.15 }));
2156
+ const dice = must(og.render.csgGeometry(world, {
2157
+ op: "subtract", a: { geometry: cube }, b: { geometry: ball, position: { x: 0, y: 0, z: 0.5 } },
2158
+ }));
2159
+ ```
2160
+
2161
+ The generators and the CSG decide every cut with exact predicates and use
2162
+ no platform trigonometry, so every host makes the same triangles. A CSG
2163
+ operand must be closed (every edge shared by two triangles). Its cost
2164
+ grows with the product of the two triangle counts where their boxes
2165
+ overlap, so cut low-poly operands at load, not every tick.
2166
+
2167
+ Every generator above takes `uvRepeat` as `boxGeometry` does (its uvs
2168
+ scaled, so a texture tiles). `bevelSize` left out (or null) is
2169
+ `bevelThickness - 0.1`, as in three.js: 0.1 at the default thickness.
2170
+
2171
+ **Models written as text: `type model`.** A prop made of these parts
2172
+ belongs in an asset, not in `start`: an `.ogt` of `type model` under
2173
+ `assets/` names each part's geometry by its `og.render` call, with the
2174
+ desc's field names and defaults, places it as `mergeGeometries` places a
2175
+ part, gives it a material and hangs it from a named pivot. `og cook`
2176
+ builds it into the same model blob a glTF cooks to: meshopt, levels of
2177
+ detail with their measured errors, the raycast BVH, the nodes. Load it
2178
+ with `og.load.gltf(world, "models/clockwork.ogt")` and `world.instantiate`,
2179
+ or name it in a scene (`model "models/clockwork.ogt"`), as any model.
2180
+
2181
+ ```
2182
+ type model
2183
+ version 1
2184
+ ---
2185
+ material "brass" {
2186
+ color 0.78 0.52 0.16 # linear, as glTF's baseColorFactor
2187
+ metalness 1
2188
+ roughness 0.32
2189
+ }
2190
+ material "glaze" {
2191
+ color 0.12 0.34 0.72
2192
+ roughness 0.3
2193
+ }
2194
+ pivot "axle" { # a node: code and animation find it by name
2195
+ position 0 1.25 0
2196
+ }
2197
+ part "gear" {
2198
+ pivot "axle" # hangs from the pivot (default: the root)
2199
+ material "brass"
2200
+ position 0 0 -0.1
2201
+ extrudeGeometry {
2202
+ shape { # og.Shape's calls, one a line
2203
+ moveTo 1 0
2204
+ absarc 0 0 1 0 6.283185307179586 false
2205
+ hole {
2206
+ absarc 0 0 0.28 0 6.283185307179586 true
2207
+ }
2208
+ }
2209
+ depth 0.16
2210
+ bevelThickness 0.04
2211
+ bevelSize 0.03
2212
+ }
2213
+ }
2214
+ part "bolt" { # one mesh under three nodes: drawn instanced
2215
+ pivot "axle"
2216
+ material "brass"
2217
+ cylinderGeometry {
2218
+ radiusTop 0.06
2219
+ radiusBottom 0.06
2220
+ height 0.36
2221
+ }
2222
+ at {
2223
+ position 0.55 0 0
2224
+ quaternion 0.7071068 0 0 0.7071068
2225
+ }
2226
+ at {
2227
+ position -0.275 0.476 0
2228
+ quaternion 0.7071068 0 0 0.7071068
2229
+ }
2230
+ at {
2231
+ position -0.275 -0.476 0
2232
+ quaternion 0.7071068 0 0 0.7071068
2233
+ }
2234
+ }
2235
+ part "vase" {
2236
+ material "glaze"
2237
+ position -2.2 0 0
2238
+ latheGeometry {
2239
+ points 0 0 0.32 0.04 0.42 0.4 0.24 0.95 0.3 1.25
2240
+ segments 40
2241
+ }
2242
+ }
2243
+ part "horn" {
2244
+ position 2.2 0 0
2245
+ sweepGeometry {
2246
+ section {
2247
+ absarc 0 0 0.22 0 6.283185307179586 false
2248
+ }
2249
+ path { # og.path's lines: kind, closed, tension, point
2250
+ kind catmullRom
2251
+ point 0 0 0
2252
+ point 0.15 0.6 0
2253
+ point -0.45 1.35 0.15
2254
+ }
2255
+ scale 0 1 # t value, repeatable (twist the same)
2256
+ scale 1 0.05
2257
+ }
2258
+ }
2259
+ part "dice" {
2260
+ position 1.1 0.3 0.55
2261
+ csgGeometry {
2262
+ op subtract
2263
+ a { # an operand: a geometry block, placed
2264
+ boxGeometry {
2265
+ width 0.6
2266
+ height 0.6
2267
+ depth 0.6
2268
+ }
2269
+ }
2270
+ b {
2271
+ position 0 0 0.3
2272
+ icosahedronGeometry {
2273
+ radius 0.08
2274
+ detail 1
2275
+ }
2276
+ }
2277
+ }
2278
+ }
2279
+ ```
2280
+
2281
+ - Geometries: `boxGeometry`, `planeGeometry`, `sphereGeometry`,
2282
+ `cylinderGeometry`, `coneGeometry`, `capsuleGeometry`, `torusGeometry`,
2283
+ `shapeGeometry`, `extrudeGeometry` (`extrudePath { }` a path block),
2284
+ `latheGeometry` (`points x y x y ...`), `polyhedronGeometry`, the four
2285
+ solids, `sweepGeometry`, `csgGeometry` (`a { }`, `b { }`) and
2286
+ `mergeGeometries` (`part { }` blocks), each with `uvRepeat u v`. A
2287
+ part holds exactly one; combine more with `csgGeometry` or
2288
+ `mergeGeometries`.
2289
+ - Shapes: `moveTo`, `lineTo`, `quadraticCurveTo`, `bezierCurveTo`,
2290
+ `splineThru x y ...`, `arc`, `absarc`, `ellipse`, `absellipse` (angles in
2291
+ radians, then `true` for clockwise), `closePath`, `points x y ...`
2292
+ (setFromPoints) and `hole { }`. They flatten to the points og.Shape
2293
+ gives in TypeScript, bit for bit.
2294
+ - Placement: `position`, `quaternion x y z w`, `scale`, `mirror x|y|z`,
2295
+ on the part or in `at { }` blocks, one node each. The same part at
2296
+ several placements is one mesh drawn instanced; a mirrored placement is
2297
+ baked into a mesh of its own.
2298
+ - Materials: `color`, `opacity`, `metalness` (default 0), `roughness`
2299
+ (default 1), `emissive`, `emissiveIntensity`, `doubleSided`,
2300
+ `alphaTest`. A part without one draws glTF's grey default.
2301
+ - `og cook --report` lists the model's `triangles`, `parts`, `draws` (the
2302
+ distinct meshes; instanced placements are one) and `bvhBytes`/`bvhShare`.
2303
+ Past `budgets.triangles` or `budgets.vertices` in project.ogt the cook
2304
+ refuses it, so lower `segments`, `curveSegments`, `steps` or `detail`.
2305
+ Import settings (`models/clockwork.ogt.ogt`, `model { lods ... raycast
2306
+ false }`) apply as to a glTF.
2307
+ - `og dev` re-cooks it on an edit and the running game draws the edit
2308
+ under the same asset handle.
2309
+
1894
2310
  **Lines and points.** `og.render.lines(world, desc)` and
1895
2311
  `og.render.points(world, desc)` are immediate mode, like
1896
2312
  `og.render.sprite`: submit them every tick they should show.
@@ -2154,6 +2570,10 @@ These are the checks to use before opening frames. All of them run from
2154
2570
  `totalBytes` 64 MiB, `soundSeconds` 600, `videoSeconds` 300,
2155
2571
  `fontGlyphs` 8192, `navPolygons` 65536, `sceneEntities` 10000 and
2156
2572
  `emitterParticles` 16384, each a default a project may move.
2573
+ `bvhShare` 30 is a warning, not a limit: a model whose cooked raycast
2574
+ BVH is over 30 % of its blob (and at least 16 KiB) is named by `og cook
2575
+ --report` and `og doctor`; write `model { raycast false }` in its .ogt
2576
+ when nothing raycasts against it.
2157
2577
 
2158
2578
  ## 22. Levels of detail, reflection probes and screen-space reflections
2159
2579
 
@@ -2172,15 +2592,21 @@ model {
2172
2592
  }
2173
2593
  ```
2174
2594
 
2175
- `og cook --report` lists each model's triangles per level. The renderer
2176
- draws each `og.Model` entity at the level its size on screen calls for,
2177
- with hysteresis, so nothing flickers at a boundary. `og.Model.lodBias`
2178
- (per entity) and `RenderSettings.lodBias` (the frame) scale that choice:
2179
- 2 keeps the full mesh twice as far, and 0 always draws it. Check the
2180
- choice with `debug:lod`, which tints models green, yellow, orange and red
2181
- by level, with `dump_entity:<name>` (`render.lod`), and with the
2182
- triangles in `ProbeTimings.triangles` or `dump_render`. A field of 2,000
2183
- trees draws about 4× fewer triangles with LOD on.
2595
+ The cook measures each level's error, the largest distance between the
2596
+ level and the full mesh in the model's units, and `og cook --report`
2597
+ lists each model's triangles (`lods`) and error (`lodErrors`) per level.
2598
+ The renderer draws each `og.Model` entity at the coarsest level whose
2599
+ error covers less than a pixel at the entity's distance, with
2600
+ hysteresis, so nothing flickers at a boundary: a small prop keeps its
2601
+ full mesh until it is far away, a large tree drops levels sooner. A
2602
+ level whose error is too large for its purpose (a hard-surface model
2603
+ melting at a distance) is cooked tighter with `lodError`, or the model
2604
+ keeps more detail with `lodBias`. `og.Model.lodBias` (per entity) and
2605
+ `RenderSettings.lodBias` (the frame) scale that choice: 2 keeps the full
2606
+ mesh twice as far, and 0 always draws it. Check the choice with
2607
+ `debug:lod`, which tints models green, yellow, orange and red by level,
2608
+ with `dump_entity:<name>` (`render.lod`), and with the triangles in
2609
+ `ProbeTimings.triangles` or `dump_render`.
2184
2610
 
2185
2611
  **Reflection probes.** Smooth surfaces reflect the environment map, which
2186
2612
  is the sky. Indoors that is wrong: a chrome sphere in a room would show
@@ -2212,8 +2638,11 @@ world.insert(probe, og.ReflectionProbe, {
2212
2638
  the interval high.
2213
2639
  - Size the box to the room. Each object takes the smallest box holding
2214
2640
  its centre, so two rooms that share a model each reflect their own.
2215
- The capture shows everything in the room: characters in their pose,
2216
- terrain, grass, glass. Terrain, grass and water inside the box are lit
2641
+ The capture shows everything in the room: characters in their pose
2642
+ (and each one takes the probe of the room its pose stands in), terrain,
2643
+ grass, water (refracting what lies below it), 3D fluids, glass,
2644
+ particles (soft ones fading as in the frame), lines and world sprites
2645
+ and text. Terrain, grass and water inside the box are lit
2217
2646
  by it too. At most 8 probes (`MAX_REFLECTION_PROBES`) are used,
2218
2647
  nearest the camera first.
2219
2648
  - `dump_render` lists the frame's probes and whether each was loaded from
@@ -2261,7 +2690,8 @@ default) or `hdr`:
2261
2690
  `depth: true` also keeps the view's linear depth, from near (0) to far
2262
2691
  (1), of its opaque meshes, on every tier, WebGL2 included. It costs a
2263
2692
  second draw of the view's opaque meshes, so ask for it only when a
2264
- material reads it.
2693
+ material reads it. (A scene with soft particles, `softDistance` above 0,
2694
+ pays that draw in every view anyway: its particles fade against it.)
2265
2695
  `RenderCapabilities.maxRenderTargetSize` is the largest side.
2266
2696
 
2267
2697
  **A second camera.** `og.CameraView` on an entity is a camera with
@@ -2285,16 +2715,18 @@ world.insert(p2, og.CameraView, { viewportX: 0.5, viewportWidth: 0.5 });
2285
2715
  A view draws what the main camera draws, from its own eye and on its own
2286
2716
  `layers`: meshes and models (skinned meshes and custom shaders included),
2287
2717
  opaque then transparent back to front, the terrain and the sky, particle
2288
- systems, og.Lines and og.Points, and world sprites and text, lit and
2718
+ systems, trails, og.Lines and og.Points, and world sprites and text, lit and
2289
2719
  shadowed by the frame's lights. Each view is a second draw of everything
2290
2720
  its layers admit, so keep it cheap with `layers` and `every`.
2291
2721
  - An `ldr` target and a viewport are exposed with the view's `exposure`
2292
2722
  and tone mapped with the frame's curve. An `hdr` target holds linear
2293
2723
  radiance and is exposed by the frame that shows it.
2294
2724
  - Bloom, AO, SSR, depth of field, colour grading and custom post passes
2295
- run on the main frame only.
2296
- - In a view, sprites are not 2D-lit and do not glow. Trails, 2D fluids,
2297
- screen sprites, the UI and debug draw stay on the main frame.
2725
+ run on the main frame only (the 2D glow, `PostEffects.glow`, is the
2726
+ exception: a view adds its own).
2727
+ - In a view, sprites are 2D-lit and glow and 2D fluids and soft
2728
+ particles draw as in the main frame. Screen sprites, the UI and debug
2729
+ draw stay on the main frame.
2298
2730
 
2299
2731
  A viewport view draws over the finished image and under the UI.
2300
2732
 
@@ -2340,8 +2772,9 @@ fn fragment(f: FragmentInput, p: Params) -> SurfaceOutput // f:
2340
2772
  // standard_surface(f), noise3(p).
2341
2773
  ```
2342
2774
  An entry you leave out is the standard one.
2343
- - `lit` surfaces are lit by the sun's cascades, the point and spot lights
2344
- with their shadows, the environment and the fog. `unlit` surfaces draw
2775
+ - `lit` surfaces are lit by the sun's cascades, the point, spot and rect
2776
+ lights with their shadows and cookies (a rect light as an area, as on a
2777
+ standard material), the environment and the fog. `unlit` surfaces draw
2345
2778
  as returned.
2346
2779
  - The depth prepass, every shadow pass and every view run the same
2347
2780
  entries, and alpha under the material's `alphaTest` is cut in each of
@@ -2361,11 +2794,15 @@ with up to eight `param` lines. The function reads:
2361
2794
 
2362
2795
  It returns linear HDR. To use one:
2363
2796
  1. Load it with `og.load.post(world, "post/crt.ogt")`.
2364
- 2. Put the handle in `PostEffects.custom1` to `custom8`
2365
- (`RenderCapabilities.maxCustomPosts` is 8). They run in that order, a
2366
- slot left 0 skipped, each over the one before it, after depth of field
2367
- and motion blur and before exposure, bloom and tone mapping. There is
2368
- no `custom` list: a resource's fields hold one value each.
2797
+ 2. Put the handles in the list `PostEffects.custom`, at most
2798
+ `RenderCapabilities.maxCustomPosts` (8) of them. They run in its order,
2799
+ each over the one before it, after depth of field and motion blur and
2800
+ before exposure, bloom and tone mapping. Write the list whole:
2801
+ `post.custom = [grade, crt]`, `post.custom = [...post.custom, h]`,
2802
+ `post.custom = []`. Reading it answers a frozen copy, so
2803
+ `post.custom.push(h)` throws instead of changing nothing; nine passes
2804
+ are refused, never cut to eight. A look gives it on one line:
2805
+ `custom "post/tint.ogt" "post/crt.ogt"`.
2369
2806
  3. Set its parameters with `og.render.setPostParam(world, post, "curve",
2370
2807
  [0.1])`.
2371
2808
 
@@ -2450,3 +2887,12 @@ textures between lanterns to stay under it.
2450
2887
  `reference/index.md` (the generated wiki shipped with this skill) maps every
2451
2888
  module (grouped by skill, as in §1), type and function to its page, with the IDL text, defaults, ranges
2452
2889
  and units of every field.
2890
+
2891
+ `guides/three-to-og.md` (beside this skill) maps three.js's classes and
2892
+ idioms to the engine's: the scene graph to entities and og.Parent,
2893
+ Object3D to og.Transform, the geometry classes to §18's generators, the
2894
+ materials to StandardMaterialDesc, the loaders to og.load, the render
2895
+ loop to systems on the tick, Raycaster to `og.render.raycast`, and the
2896
+ gaps by name. `og migrate three <file.ts>` rewrites a three.js file's
2897
+ mechanical part with those mappings and marks every line it cannot,
2898
+ citing the guide's section.