tosijs-3d 0.8.3 → 0.8.5

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 (254) hide show
  1. package/CHANGELOG.md +604 -0
  2. package/dist/b3d-aircraft.d.ts +40 -0
  3. package/dist/b3d-aircraft.d.ts.map +1 -1
  4. package/dist/b3d-aircraft.js +138 -1
  5. package/dist/b3d-aircraft.js.map +1 -1
  6. package/dist/b3d-ambient.d.ts +5 -0
  7. package/dist/b3d-ambient.d.ts.map +1 -1
  8. package/dist/b3d-ambient.js +106 -60
  9. package/dist/b3d-ambient.js.map +1 -1
  10. package/dist/b3d-beacon.d.ts.map +1 -1
  11. package/dist/b3d-beacon.js +4 -0
  12. package/dist/b3d-beacon.js.map +1 -1
  13. package/dist/b3d-cloud-deck.d.ts +51 -0
  14. package/dist/b3d-cloud-deck.d.ts.map +1 -1
  15. package/dist/b3d-cloud-deck.js +440 -81
  16. package/dist/b3d-cloud-deck.js.map +1 -1
  17. package/dist/b3d-clouds.d.ts.map +1 -1
  18. package/dist/b3d-clouds.js +7 -9
  19. package/dist/b3d-clouds.js.map +1 -1
  20. package/dist/b3d-controllable.d.ts +3 -0
  21. package/dist/b3d-controllable.d.ts.map +1 -1
  22. package/dist/b3d-controllable.js +16 -0
  23. package/dist/b3d-controllable.js.map +1 -1
  24. package/dist/b3d-controller.d.ts.map +1 -1
  25. package/dist/b3d-controller.js.map +1 -1
  26. package/dist/b3d-crowd.d.ts.map +1 -1
  27. package/dist/b3d-crowd.js +7 -6
  28. package/dist/b3d-crowd.js.map +1 -1
  29. package/dist/b3d-decorator.d.ts +109 -0
  30. package/dist/b3d-decorator.d.ts.map +1 -0
  31. package/dist/b3d-decorator.js +688 -0
  32. package/dist/b3d-decorator.js.map +1 -0
  33. package/dist/b3d-destroyable.d.ts +0 -2
  34. package/dist/b3d-destroyable.d.ts.map +1 -1
  35. package/dist/b3d-destroyable.js +5 -26
  36. package/dist/b3d-destroyable.js.map +1 -1
  37. package/dist/b3d-galaxy.d.ts +16 -1
  38. package/dist/b3d-galaxy.d.ts.map +1 -1
  39. package/dist/b3d-galaxy.js +55 -17
  40. package/dist/b3d-galaxy.js.map +1 -1
  41. package/dist/b3d-input-focus.d.ts.map +1 -1
  42. package/dist/b3d-input-focus.js +4 -0
  43. package/dist/b3d-input-focus.js.map +1 -1
  44. package/dist/b3d-launcher.d.ts +9 -0
  45. package/dist/b3d-launcher.d.ts.map +1 -1
  46. package/dist/b3d-launcher.js +24 -2
  47. package/dist/b3d-launcher.js.map +1 -1
  48. package/dist/b3d-light-shafts.d.ts +74 -0
  49. package/dist/b3d-light-shafts.d.ts.map +1 -0
  50. package/dist/b3d-light-shafts.js +723 -0
  51. package/dist/b3d-light-shafts.js.map +1 -0
  52. package/dist/b3d-lightning.d.ts +82 -0
  53. package/dist/b3d-lightning.d.ts.map +1 -0
  54. package/dist/b3d-lightning.js +726 -0
  55. package/dist/b3d-lightning.js.map +1 -0
  56. package/dist/b3d-manipulator.d.ts +5 -1
  57. package/dist/b3d-manipulator.d.ts.map +1 -1
  58. package/dist/b3d-manipulator.js +13 -6
  59. package/dist/b3d-manipulator.js.map +1 -1
  60. package/dist/b3d-moon.d.ts +32 -0
  61. package/dist/b3d-moon.d.ts.map +1 -0
  62. package/dist/b3d-moon.js +101 -0
  63. package/dist/b3d-moon.js.map +1 -0
  64. package/dist/b3d-panel.d.ts +7 -0
  65. package/dist/b3d-panel.d.ts.map +1 -1
  66. package/dist/b3d-panel.js +25 -6
  67. package/dist/b3d-panel.js.map +1 -1
  68. package/dist/b3d-prop.d.ts +0 -1
  69. package/dist/b3d-prop.d.ts.map +1 -1
  70. package/dist/b3d-prop.js +3 -10
  71. package/dist/b3d-prop.js.map +1 -1
  72. package/dist/b3d-quality.d.ts +11 -0
  73. package/dist/b3d-quality.d.ts.map +1 -1
  74. package/dist/b3d-quality.js +19 -0
  75. package/dist/b3d-quality.js.map +1 -1
  76. package/dist/b3d-skybox.d.ts +48 -0
  77. package/dist/b3d-skybox.d.ts.map +1 -1
  78. package/dist/b3d-skybox.js +587 -98
  79. package/dist/b3d-skybox.js.map +1 -1
  80. package/dist/b3d-spawner.d.ts +1 -0
  81. package/dist/b3d-spawner.d.ts.map +1 -1
  82. package/dist/b3d-spawner.js +10 -2
  83. package/dist/b3d-spawner.js.map +1 -1
  84. package/dist/b3d-star-system.d.ts +14 -1
  85. package/dist/b3d-star-system.d.ts.map +1 -1
  86. package/dist/b3d-star-system.js +33 -16
  87. package/dist/b3d-star-system.js.map +1 -1
  88. package/dist/b3d-terrain.d.ts +9 -0
  89. package/dist/b3d-terrain.d.ts.map +1 -1
  90. package/dist/b3d-terrain.js +20 -9
  91. package/dist/b3d-terrain.js.map +1 -1
  92. package/dist/b3d-trail.d.ts +65 -0
  93. package/dist/b3d-trail.d.ts.map +1 -0
  94. package/dist/b3d-trail.js +228 -0
  95. package/dist/b3d-trail.js.map +1 -0
  96. package/dist/b3d-trigger.d.ts.map +1 -1
  97. package/dist/b3d-trigger.js +5 -4
  98. package/dist/b3d-trigger.js.map +1 -1
  99. package/dist/b3d-utils.d.ts +18 -0
  100. package/dist/b3d-utils.d.ts.map +1 -1
  101. package/dist/b3d-utils.js +44 -0
  102. package/dist/b3d-utils.js.map +1 -1
  103. package/dist/b3d-warhead.d.ts.map +1 -1
  104. package/dist/b3d-warhead.js +5 -2
  105. package/dist/b3d-warhead.js.map +1 -1
  106. package/dist/b3d-water.d.ts +30 -0
  107. package/dist/b3d-water.d.ts.map +1 -1
  108. package/dist/b3d-water.js +341 -8
  109. package/dist/b3d-water.js.map +1 -1
  110. package/dist/b3d-weather-cell.d.ts +51 -0
  111. package/dist/b3d-weather-cell.d.ts.map +1 -0
  112. package/dist/b3d-weather-cell.js +207 -0
  113. package/dist/b3d-weather-cell.js.map +1 -0
  114. package/dist/caustics.d.ts +34 -0
  115. package/dist/caustics.d.ts.map +1 -0
  116. package/dist/caustics.js +197 -0
  117. package/dist/caustics.js.map +1 -0
  118. package/dist/cloud-field.d.ts +33 -0
  119. package/dist/cloud-field.d.ts.map +1 -1
  120. package/dist/cloud-field.js +54 -0
  121. package/dist/cloud-field.js.map +1 -1
  122. package/dist/cloud-shadows.d.ts +9 -0
  123. package/dist/cloud-shadows.d.ts.map +1 -1
  124. package/dist/cloud-shadows.js +14 -0
  125. package/dist/cloud-shadows.js.map +1 -1
  126. package/dist/curve-field.js +1 -1
  127. package/dist/curve-field.js.map +1 -1
  128. package/dist/demo-utils.d.ts.map +1 -1
  129. package/dist/demo-utils.js +2 -0
  130. package/dist/demo-utils.js.map +1 -1
  131. package/dist/fly-by-wire.d.ts +24 -0
  132. package/dist/fly-by-wire.d.ts.map +1 -1
  133. package/dist/fly-by-wire.js +47 -9
  134. package/dist/fly-by-wire.js.map +1 -1
  135. package/dist/footprint-field.js +1 -1
  136. package/dist/footprint-field.js.map +1 -1
  137. package/dist/frame-panel.d.ts +31 -0
  138. package/dist/frame-panel.d.ts.map +1 -1
  139. package/dist/frame-panel.js +94 -2
  140. package/dist/frame-panel.js.map +1 -1
  141. package/dist/galaxy-data.d.ts +79 -0
  142. package/dist/galaxy-data.d.ts.map +1 -1
  143. package/dist/galaxy-data.js +155 -106
  144. package/dist/galaxy-data.js.map +1 -1
  145. package/dist/index.d.ts +21 -5
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +20 -3
  148. package/dist/index.js.map +1 -1
  149. package/dist/keyboard.d.ts +7 -0
  150. package/dist/keyboard.d.ts.map +1 -1
  151. package/dist/keyboard.js +42 -13
  152. package/dist/keyboard.js.map +1 -1
  153. package/dist/land-and-sky.js +156 -10
  154. package/dist/land-and-sky.js.map +1 -1
  155. package/dist/library-mesh.js +1 -1
  156. package/dist/light-rays.d.ts +61 -0
  157. package/dist/light-rays.d.ts.map +1 -0
  158. package/dist/light-rays.js +113 -0
  159. package/dist/light-rays.js.map +1 -0
  160. package/dist/lightning.d.ts +65 -0
  161. package/dist/lightning.d.ts.map +1 -0
  162. package/dist/lightning.js +164 -0
  163. package/dist/lightning.js.map +1 -0
  164. package/dist/mersenne-twister.d.ts +28 -0
  165. package/dist/mersenne-twister.d.ts.map +1 -1
  166. package/dist/mersenne-twister.js +59 -2
  167. package/dist/mersenne-twister.js.map +1 -1
  168. package/dist/perf-probe.d.ts +8 -0
  169. package/dist/perf-probe.d.ts.map +1 -1
  170. package/dist/perf-probe.js +3 -0
  171. package/dist/perf-probe.js.map +1 -1
  172. package/dist/population-table.d.ts +9 -0
  173. package/dist/population-table.d.ts.map +1 -0
  174. package/dist/population-table.js +87 -0
  175. package/dist/population-table.js.map +1 -0
  176. package/dist/scatter.d.ts +139 -0
  177. package/dist/scatter.d.ts.map +1 -0
  178. package/dist/scatter.js +421 -0
  179. package/dist/scatter.js.map +1 -0
  180. package/dist/scene-schemas.d.ts +14 -0
  181. package/dist/scene-schemas.d.ts.map +1 -1
  182. package/dist/scene-schemas.js +143 -13
  183. package/dist/scene-schemas.js.map +1 -1
  184. package/dist/sdf-lattice.d.ts +26 -0
  185. package/dist/sdf-lattice.d.ts.map +1 -1
  186. package/dist/sdf-lattice.js +17 -0
  187. package/dist/sdf-lattice.js.map +1 -1
  188. package/dist/skybox-baker.d.ts +82 -0
  189. package/dist/skybox-baker.d.ts.map +1 -1
  190. package/dist/skybox-baker.js +127 -13
  191. package/dist/skybox-baker.js.map +1 -1
  192. package/dist/spectral-classes.d.ts +9 -0
  193. package/dist/spectral-classes.d.ts.map +1 -0
  194. package/dist/spectral-classes.js +18 -0
  195. package/dist/spectral-classes.js.map +1 -0
  196. package/dist/star-populations.d.ts +29 -0
  197. package/dist/star-populations.d.ts.map +1 -0
  198. package/dist/star-populations.js +126 -0
  199. package/dist/star-populations.js.map +1 -0
  200. package/dist/svg-icons.d.ts +16 -1
  201. package/dist/svg-icons.d.ts.map +1 -1
  202. package/dist/svg-icons.js +31 -0
  203. package/dist/svg-icons.js.map +1 -1
  204. package/dist/table.d.ts.map +1 -1
  205. package/dist/table.js +7 -4
  206. package/dist/table.js.map +1 -1
  207. package/dist/theme-editor.d.ts +14 -1
  208. package/dist/theme-editor.d.ts.map +1 -1
  209. package/dist/theme-editor.js +10 -2
  210. package/dist/theme-editor.js.map +1 -1
  211. package/dist/tosi-b3d.d.ts +60 -2
  212. package/dist/tosi-b3d.d.ts.map +1 -1
  213. package/dist/tosi-b3d.js +291 -14
  214. package/dist/tosi-b3d.js.map +1 -1
  215. package/dist/touch-gesture.d.ts +30 -0
  216. package/dist/touch-gesture.d.ts.map +1 -0
  217. package/dist/touch-gesture.js +74 -0
  218. package/dist/touch-gesture.js.map +1 -0
  219. package/dist/touch-orbit.d.ts +3 -0
  220. package/dist/touch-orbit.d.ts.map +1 -0
  221. package/dist/touch-orbit.js +68 -0
  222. package/dist/touch-orbit.js.map +1 -0
  223. package/dist/vector-field.js +1 -1
  224. package/dist/vector-field.js.map +1 -1
  225. package/dist/voxel-galaxy.d.ts +161 -0
  226. package/dist/voxel-galaxy.d.ts.map +1 -0
  227. package/dist/voxel-galaxy.js +548 -0
  228. package/dist/voxel-galaxy.js.map +1 -0
  229. package/dist/w3d-theme.d.ts.map +1 -1
  230. package/dist/w3d-theme.js +8 -4
  231. package/dist/w3d-theme.js.map +1 -1
  232. package/dist/weapon-fit.js +2 -2
  233. package/dist/weather.d.ts +47 -0
  234. package/dist/weather.d.ts.map +1 -0
  235. package/dist/weather.js +99 -0
  236. package/dist/weather.js.map +1 -0
  237. package/dist/widgets3d-layout.d.ts.map +1 -1
  238. package/dist/widgets3d-layout.js +7 -0
  239. package/dist/widgets3d-layout.js.map +1 -1
  240. package/dist/widgets3d.d.ts +25 -3
  241. package/dist/widgets3d.d.ts.map +1 -1
  242. package/dist/widgets3d.js +38 -8
  243. package/dist/widgets3d.js.map +1 -1
  244. package/dist/wind.d.ts +9 -0
  245. package/dist/wind.d.ts.map +1 -1
  246. package/dist/wind.js +13 -0
  247. package/dist/wind.js.map +1 -1
  248. package/dist/xr-frames.d.ts +32 -0
  249. package/dist/xr-frames.d.ts.map +1 -1
  250. package/dist/xr-frames.js +64 -0
  251. package/dist/xr-frames.js.map +1 -1
  252. package/package.json +5 -3
  253. package/static/attributes.json +630 -13
  254. package/static/attributes.txt +156 -9
package/CHANGELOG.md CHANGED
@@ -6,6 +6,610 @@ All notable changes to **tosijs-3d**. This project is pre-1.0 (`0.x`), so minor
6
6
  versions may carry breaking peer-dependency changes — each is called out in a
7
7
  **⚠️ Breaking** block in its version section below, with what a consumer must do.
8
8
 
9
+ ## 0.8.5
10
+
11
+ **Weather you can watch.** Storms that darken the day and strike: lightning
12
+ that lights the cloud from below, inside and above, lights the landscape and
13
+ throws shadows, with thunder at the speed of sound. Shafts of sun through a
14
+ broken sky, and the sky and sun seen through the water from below, with rays
15
+ fanning down from them. Rain and snow from the weather, the wind carrying
16
+ aircraft and buffeting them in a storm. And two long-hidden bugs: the cloud
17
+ deck's local weather was mirrored north-south, and on the doc site no shadow
18
+ map had rendered at all.
19
+
20
+ Size: barrel (min, packages external) 883.2 KB raw / 295.6 KB gz (+5.7% gz),
21
+ for lightning, light shafts, weather cells, trails and caustics.
22
+
23
+ ### Fixed
24
+
25
+ - **Lightning lit the land through cloud-shaped shadows** (Tonio: "the
26
+ directional light seems to be coming from above the cloud layer ... the
27
+ shadows are mostly cloud"). The projected cloud shadow multiplied the
28
+ whole lit colour, flash included; a flash starts BELOW the cloud, so the
29
+ cloud shadow now fades while the deck is flashing (`strengthSource`, read
30
+ at draw time).
31
+ - **Moonlight made shafts as bright as noon.** The shafts' colour was
32
+ normalised and never scaled by the light's intensity; the skybox puts the
33
+ moon in the same light at night. They now scale with it (moon shafts are
34
+ real, and faint).
35
+ - **Shafts are SMALL where they leave the deck or the surface** (Tonio: the
36
+ wider they are, the more obvious any mismatch with the gap): sky `width`
37
+ 200 -> 40 m (4 m near closed cover), underwater rays 0.2-0.8 m.
38
+
39
+ - **No shadow map rendered on the doc site, the sun's included.** Babylon's
40
+ shadow generators lazy-load their shaders from code-split chunks, and each
41
+ chunk loaded a second copy of the site bundle (tosijs-ui#191), which threw,
42
+ so the shaders never loaded and every `isReady()` stayed false. The site
43
+ entry now imports the shadow shaders statically and tells the generators
44
+ they are loaded (site-only workaround; the library is unaffected).
45
+
46
+ - **Thunder kept playing after leaving the page** (Tonio). Thunder is
47
+ scheduled ahead on the audio clock (a storm 2 km off rumbles 6 s after its
48
+ flash, for 4 s), and disposing `<tosi-b3d-lightning>` neither stopped it
49
+ nor closed the context. It now closes its AudioContext, silencing
50
+ anything queued at once and no longer leaking a context per visit.
51
+
52
+ - **The cloud deck's local-weather texture was MIRRORED north-south.**
53
+ Babylon's ground rows run from +Z to -Z; the texture's rows the other way.
54
+ Everything reading the texture (local coverage, storm gloom, the cloud
55
+ shadow) put storms and orographic cloud at the mirror image of where they
56
+ were, invisible when a storm sat near the deck's centre. The lightning
57
+ demo's storm, 1.3 km off-centre, was drawn 1.3 km the other way, so every
58
+ strike lit empty sky. Pure `packWeatherTexture` flips the rows, tested.
59
+ - **A storm's sky only closed at its very core**, so from below you looked
60
+ up through gaps at the inside of the tower, which read as the deck's
61
+ BOTTOM bowing upward (Tonio). A storm now counts in full in the coverage
62
+ (its share is taken out of the ramped field and added back whole): a cell
63
+ of 1.8 closes the sky over the inner half of its radius, the base stays
64
+ flat, and only the top skin rises into the tower. From above the ceiling
65
+ bulges up; from below it is a flat, solid underside.
66
+ - **A strike lit the cloud one frame late** (the deck pushed its flash before
67
+ lightning set it); the flash is now written at draw time. It also lights
68
+ the TOP skin, so a tower seen from the side glows, and the ground light
69
+ sits halfway between the cloud base and the terrain under the strike.
70
+
71
+ - **The launcher demo's left stick did nothing** (board #2434). Since the
72
+ GTA-style remap the biped mapping sends A/D and left-stick X to `strafe`
73
+ and only the right stick to `turn`; three doc demos (launcher, warhead,
74
+ the controller's rover) still steered on `turn` alone, so the glass
75
+ pad's stick and A/D were dead. They now steer on either.
76
+
77
+ - **Orographic cloud filled the whole sky over land** (Tonio: "orographic is
78
+ the problem. It fills the transparent areas without changing apparent
79
+ cloud coverage"). The lift was measured from ABSOLUTE height, so with Land
80
+ and Sky's sea at 147 m every bit of land counted as mountain and added
81
+ +0.5 to +0.75 coverage: a 0.3 sky rendered as a solid grey sheet. It is
82
+ now measured from the SEA (the scene's water surface) and weighted to
83
+ RIDGES (ground above its surroundings); a high flat plateau keeps 35% of
84
+ it. Pure: `cloud-field`'s `orographicLift`, tested. `orographicPeak` is now
85
+ the height above the sea.
86
+
87
+ ### Added
88
+
89
+ - **Lightning casts shadows** (Tonio: "spin up a shadow map and retain it
90
+ between strokes and just use it for the brightest strike"). ONE shadow map,
91
+ kept; re-rendered once per new brightest ground strike, never per frame.
92
+ The strike's light is a POINT light by default (`flashLight`), from the
93
+ middle of the channel, so shadows radiate from the bolt (a cube map: six
94
+ renders per strike); `'directional'` is the one-render option for low-end
95
+ devices. Casters within `shadowRange` of you (and of the strike, for a
96
+ point light); `shadows`, `shadowSize`. It replaces the old point light, so
97
+ the scene stays within a material's 4-light budget. The lightning demo now
98
+ has hills, trees and a hamlet under the storm.
99
+ - **A storm darkens the day around it** (Tonio: "cut ambient and even sun
100
+ brightness somewhat when lightning is flashing ... by half"): new
101
+ `darken` on `<tosi-b3d-lightning>` (0.5) takes the sun and the ambient
102
+ fill down by half at full storm, easing with distance and strength,
103
+ through the cloud deck's own light handling (`deck.stormDim`) so the two
104
+ cannot compound. The deck's gloom no longer dims lightning's own lights
105
+ (it was cutting the landscape flash by up to 90% under cover).
106
+ - **Land and Sky: a "lightning storm" toggle** beside the volcano: the storm
107
+ gathers in front of you over 20 s, strikes, rains and throws strike
108
+ shadows, then drifts away east.
109
+ - **Lightning is dramatic** (Tonio: "having the lightning be dramatically
110
+ bright"): each strike lights the WHOLE landscape from above (falling off
111
+ with the storm's distance), the tower from inside, harder, and the bolt
112
+ carries an additive halo. New `brightness` (the drama) and `rate` (how
113
+ often, a multiple of the natural rate; pure `strikesBetween` takes it,
114
+ tested) on `<tosi-b3d-lightning>`; `groundLight` default 2.5 -> 6. Storm
115
+ lightning also brightens the WHITEOUT when you are inside the cloud. The
116
+ landscape flash is strong enough for a dark ground to read as lit (peak
117
+ 3.5, halving at 6 km; the ground light reaches 6 km). The
118
+ demo has a scene panel: storm size,
119
+ how often, cloud cover, brightness, time of day. A storm is no longer a
120
+ TOWER by default (Tonio: "it basically looks terrible"): the deck's
121
+ `stormRise` defaults to 0 (opt-in, domed when on), so a storm is more
122
+ cover and lightning. From below the flash lights the underside and the
123
+ ground, inside it the whiteout, above it the top.
124
+ - **Weather reaches the airframe** (board #1125). `<tosi-b3d-aircraft>` asks
125
+ the scene for the weather where it is: the WIND carries it (it chases an
126
+ air-relative velocity, so a crosswind crabs it downwind), and a STORM
127
+ buffets it — seeded disturbance on pitch, roll and climb that the attitude
128
+ controller fights back toward the stick. Strong wind alone adds a little.
129
+ New attributes **`turbulence`** (`'on'`/`'off'`, default on) and
130
+ **`turbulenceScale`** (default `1`), and a read-only **`turbulenceLevel`**
131
+ (0–1) for a HUD or a debug readout. The disturbance is the pure, seeded
132
+ **`turbulence(t, seed, level)`** in `fly-by-wire`. Nothing happens on the
133
+ ground or in an owner without the weather service.
134
+ - **Light shafts** (WEATHER-DESIGN stage 4, board #1084), to Tonio's model
135
+ (pure half: **`light-rays`**): **`<tosi-b3d-light-shafts>`** hangs shafts
136
+ from the gaps in the cloud deck, BELOW it, running along the sun and
137
+ widening slightly (`spread`), and as broad as the gaps: `width` at the
138
+ 0.5 threshold down to slits as the cover closes toward 1. An additive,
139
+ flat fill of the sun's colour — `strength` 0.35 at the cloud, linear to 0 — most prominent looking toward
140
+ the sun (a forward-scatter phase, per pixel), and only where the local
141
+ cover is broken: 0.5 up to (not including) 1. Placement searches a fine
142
+ grid near you as well as a coarse one far out, keeps one shaft per gap,
143
+ and only refuses a shaft that would pass through you, so a deck just
144
+ above your head (Land and Sky) gets shafts too. The same model runs UNDER
145
+ THE WATER: shafts from the surface, bent by Snell's law, sunlight tinted
146
+ by the water's fog, shimmering as cells come and go (`underwater`,
147
+ `underwaterCount`). SURFACE TURBULENCE drives them (the wind over the
148
+ water, its `waveHeight` and `bumpHeight`): calm water gives a few broad,
149
+ slow rays, choppy water many narrow ones that flicker (`waterRoughness`,
150
+ `waterRayShape`; `waterRoughness` on the element reads it back). The doc
151
+ page has an underwater demo. Shafts are RECTANGULAR (`spread` 0), so
152
+ perspective alone makes them radiate from the sun; sky placement is
153
+ weighted hard toward the sun's position in view, and underwater rays are
154
+ biased toward the viewer so the middle of the view is not empty. Rain adds
155
+ to the strength.
156
+ - **The weather-cell demo has a frame of reference** (Tonio: "it has no
157
+ frame of reference so it just looks like leaves blowing around"): a row of
158
+ flagpoles you fly past, each flag showing the wind WHERE IT STANDS
159
+ (streaming downwind, limp in the lee), and the lee's edge drawn as a ring
160
+ on the water.
161
+ - **The sky through the water, from below** (`b3d-water` `undersideSky`,
162
+ default 1; Tonio: "we want to see the sky and sun through and distorted by
163
+ the water surface"). The window no longer shows a flat colour: a small
164
+ reflection probe photographs the sky with its fog and veil OFF, and the
165
+ underside refracts it through the water's own ripples, so the sky and the
166
+ sun arrive bent and moving. Clearest in the shallows, fading with depth,
167
+ giving way to the dark mirror at grazing angles. The probe is HALF-FLOAT
168
+ and captures the sun as HDR (a new skybox uniform, `b3dSunHdr`, 0 in
169
+ normal rendering), so the sun still punches through the partial
170
+ transmission of the surface (Tonio: "our skybox isn't HDR so its glare
171
+ doesn't punch through"). (It replaces a glare sprite that looked fake.) Underwater rays are fewer (`underwaterCount`
172
+ 12, lower presence).
173
+ - **Rain and snow from the weather** (WEATHER-DESIGN stage 5, board #1124).
174
+ `b3d-ambient` gains `weather: 'rain' | 'snow'`: emission follows the
175
+ PRECIPITATION where the viewer is, so a storm brings its own rain and it
176
+ eases in and out as the storm passes; the local temperature splits rain
177
+ from snow (sleet between). Rain now draws as STREAKS stretched along its
178
+ velocity, so the wind visibly slants it. Land and Sky's storm rains.
179
+ - **Lightning and thunder** (WEATHER-DESIGN stage 3, board #1123):
180
+ **`<tosi-b3d-lightning>`** strikes under every weather cell with
181
+ `storminess`, seeded (same seed, same storm; frame rate cannot change it).
182
+ Cloud-to-ground strikes draw a jagged, branching bolt that flickers through
183
+ its re-strokes and light the ground; every strike lights the cloud deck from
184
+ INSIDE around it (`deck.flash()`, a new hook: why storms want coverage past
185
+ 1); thunder is procedural and arrives at the speed of sound, so you can
186
+ count the distance; and over strong storms, rarely, **sprites**: brief
187
+ red-pink crowns with purple tendrils high above the tower. Each strike
188
+ dispatches `strike` with `{ kind, x, z, t, distance }`. The pure model is
189
+ `lightning.ts` (`strikesBetween`, `boltPath`, `flashAt`, `thunderDelay`).
190
+ Land and Sky's storm now brings lightning.
191
+ - **Weather, stage 2: a storm you can see** (board #1122). A
192
+ `<tosi-b3d-weather-cell>` with `coverage` joins the cloud deck's local
193
+ weather field: denser cloud over it, a greyer, heavier underside (its own
194
+ "gloom" channel, so orographic cloud still never repaints fair weather as
195
+ foul), the storm's shadow on the ground beneath (the shadow reads the same
196
+ field), and full strength even when the global dial says clear. A drifting
197
+ cell re-bakes about once per 10 m of travel.
198
+ Land and Sky has a **storm** toggle in its Weather group: a storm drifting
199
+ with the wind (the scene's wind now matches the deck's) that passes over
200
+ you and recedes into view.
201
+ Cells gain **`grow`** (seconds to gather from nothing to full), so a storm
202
+ builds rather than appearing; the deck re-bakes in 1% strength steps as it
203
+ does. Across SPACE a cell was already gradual: a smoothstep over its whole
204
+ radius, smoothed again by the deck.
205
+ **Storm towers:** cell coverage now runs to 2, and past 1 is THICKNESS,
206
+ as on the deck's own dial: the deck lifts its top skin there by up to
207
+ `stormRise` (1,500 m), whatever the global dial says, so a storm is a dark
208
+ heavy base from below and a bright tower from above, and the thing
209
+ lightning will light from inside. The cell channels get one smoothing pass,
210
+ not the orographic field's four (which flattened a storm's excess from 0.7
211
+ to 0.18). Land and Sky's storm is coverage 1.7.
212
+ - **Weather, stage 1: the weather where you are** (WEATHER-DESIGN.md, board
213
+ #255). `b3d.weatherAt(x, z)` (and `weatherHere()`) answers what every
214
+ weather consumer asks: the scene's base wind plus every
215
+ **`<tosi-b3d-weather-cell>`** that reaches the point, composed per quantity
216
+ (wind and temperature sum, coverage sums clamped, precipitation and
217
+ storminess take the max). Clouds, water and ambient now read the wind AT
218
+ THE VIEWER rather than one scene-wide value, so a lee is calm and a squall
219
+ is local. A cell with `drift="wind"` travels with the wind (a weather
220
+ system), and `lifetime` grows, holds and ends it. The pure model is
221
+ `weather.ts`. Measured on the new page: leaves' wind 12 → 0.96 m/s crossing
222
+ a lee's centre.
223
+ - **Caustics** (board #198, tosijs-3d#16): light through the moving surface,
224
+ dancing on whatever is beneath a `twoSided` `b3d-water` (`caustics`,
225
+ `'auto'` = on whenever `twoSided`; `causticsStrength`, `causticsScale`).
226
+ Projected by world position through a material plugin (the cloud-shadow
227
+ technique), so terrain, hulls and the player all get it with no per-mesh
228
+ setup; slants with the sun, shimmers (two moving layers, combined with
229
+ `min`), fades with depth and fog, and goes with the sun. The pattern is a
230
+ seeded, tiling cellular web (`causticPattern`), with no file to fetch.
231
+ - **One lattice per world, enforced where code can** (board #257,
232
+ tosijs-3d#75): every `extractChunk` result carries its `lattice` identity
233
+ (`latticeIdentity(cfg)`), and `assertLatticesWeld(chunks)` throws on a
234
+ mismatch instead of leaving a crack. TUNNEL-DESIGN.md records the decision
235
+ that carves are world-level objects provinces declare but don't own, so a
236
+ tunnel between provinces is just a carve in the field.
237
+ - **Snell's window: the water's underside** (board #197, tosijs-3d#15). Looking
238
+ up from under a `twoSided` `b3d-water` used to show flat dark blue. Now
239
+ (`underside`, `'auto'` = on whenever `twoSided`) it is a bright window onto
240
+ the sky overhead, darkening to a mirror of the depths toward grazing angles,
241
+ rippling with the same normal map as the surface, and fading in with the
242
+ underwater fog's own band weight. One plane, one draw call, only while
243
+ submerged. `undersideColor` / `undersideDepthColor` tune the two ends. It
244
+ stays OPAQUE and the sky stays fogged, unlike the prototype, so a level look
245
+ under water still sees murk rather than sky.
246
+ - **`b3d.debugFogColor`** (`'#ff00ff'`) paints the composited fog a colour
247
+ nothing else is, to tell fog from surface from sky; also a toggle on the
248
+ Perf Stats panel's new Fog row (with density, end and veil), for a headset.
249
+ - **`<tosi-b3d-trail>`** — a ribbon behind anything that moves (contrails,
250
+ missile smoke, a fast enemy's wake), lifted from manta-recon's engine
251
+ contrails (tosijs-3d#4, board #186). Nest it in the mover, give it a local
252
+ offset; it measures speed at its own emitter (so it works on anything),
253
+ fades below `minSpeed`, takes `underwaterColor` below a `<tosi-b3d-water>`,
254
+ re-attaches when the host's mesh is replaced (respawn, async load), and
255
+ resets on a floating-origin shift. **`attachTrail(node, scene, options)`**
256
+ is the same for a node you hold, such as a spawned missile.
257
+ Missiles get it for free: `<tosi-b3d-launcher missile-trail="on">`, or
258
+ `trail` on `spawnProjectile`/`spawnMissile` for your own rounds.
259
+ - **Frame panels are usable as menus** (board #768, tosijs-3d#92, from
260
+ manta-recon moving its outer game to viewer-relative panels):
261
+ - **They route pointers.** A `panel3d` on a frame panel now takes presses,
262
+ drags and hover from mouse, touch and XR rays (the same routing
263
+ `b3d-svg-plane` has), and repaints immediately on press and release rather
264
+ than on the texture's 400 ms timer. Before, the buttons drew and ignored
265
+ every click, with no error.
266
+ - `attachFramePanel` returns its **`mesh`**.
267
+ - **`resolution`** on `FramePanelSpec` (and `<tosi-b3d-panel resolution>`):
268
+ 384 suits a reticle, not a menu of text.
269
+ - **`flatFrame` applies to any panel**, not only hand panels, so
270
+ `flatFrame: 'face'` measures a flat panel's elevation from the VIEW rather
271
+ than the off-screen horizon of a pitched camera.
272
+ - **Flying under water feels like water** (board #185, manta's #3). A
273
+ `submersible` aircraft now meets a thicker MEDIUM below the surface:
274
+ `waterDrag` (×10 by default, the 2010 Manta's air:water ratio) multiplies
275
+ drag, blended over `waterTransition` metres. The controls are unchanged; full
276
+ throttle settles at ~32% of `maxSpeed`. **`surface-crossed`** fires on each
277
+ crossing with `{ entering, point, speed, aircraft }` for splash, wake and
278
+ audio cues, and **`submerged`** (0–1) is readable. In the pure model:
279
+ `FlyByWireConfig.mediumDrag`, and drag now clamps at rest instead of
280
+ overshooting into reverse on a long frame.
281
+
282
+ ### Fixed
283
+
284
+ - **Replacing the player left input focus on the removed one** (board #2421,
285
+ from manta-recon's respawn): the new craft got no input and its crash was
286
+ ignored by `b3d-death`. A controllable now releases focus when it is
287
+ disposed, and `adoptIfVacant` treats a disconnected focused entity as a
288
+ vacant seat. No more `releaseFocus()` before the swap.
289
+ - **Ambient wind accumulated every frame and flung particles away**: the
290
+ preset's direction vectors were assigned by reference, and the per-frame
291
+ `direction.set(preset + wind)` wrote into the preset, so the wind grew frame
292
+ on frame (drops at kilometres a second, never seen). Cloned now, and the
293
+ particles take the INHERITED wind (scene and weather cells), not only the
294
+ element's own `windX`/`windZ`.
295
+ - **Flat eye-anchored panels trailed a moving camera by one frame** (board
296
+ #767, found by manta-recon at 2.15 m per frame behind b3d-aircraft's follow
297
+ camera). The flat frames are now posed on `onBeforeCameraRenderObservable`,
298
+ after anything that moves the camera during the render. Measured: 0.000 m
299
+ gap over 64 frames of a camera moving 2 m per frame.
300
+ - **Every scene with a glass gamepad logged a type warning** (`gamepad is
301
+ declared boolean … but was written string`). `gamepad` is genuinely
302
+ `boolean | string` (`true` = the default layout, a string = a layout), so
303
+ it is now a plain property read once at setup rather than a typed attribute.
304
+ Markup (`gamepad`, `gamepad="…"`) and `b3d({ gamepad })` are unchanged.
305
+ - **`b3d-terrain.recenter()` threw away an authored `worldU`/`worldV`** (board
306
+ #458), resetting them to the defaults. It now clears the travel (the
307
+ floating-origin offset) and leaves the placement alone.
308
+
309
+ ## 0.8.4
310
+
311
+ **A sky you can actually see the stars in, and a world with things growing on
312
+ it.** The encoded sky is decoded exactly (sharp points, not smeared blobs),
313
+ twinkles, and is rebaked from the new voxel galaxy — one galaxy implementation
314
+ now, with stable star addresses and a biased "interesting stars" population.
315
+ Worlds get their own air and dust (a black noon on the Moon, a butterscotch
316
+ Mars), cosmetic extra moons whose phase follows the sun, and render resolution
317
+ follows the display by device tier. `<tosi-b3d-decorator>` scatters rocks and
318
+ trees by budget and biome, with near-only shadows and colliders. Plus a
319
+ batch of adopter fixes (ensemble's #76–#91, touch-orbit #52) and review
320
+ follow-ups, and the first release through the OIDC + staged publish flow.
321
+
322
+ Size: barrel 837.8 KB min (+5.6%) · **279.6 KB gz (+6.3%)**; dist total 1.30 MB gz (+4.3%).
323
+
324
+ > **Visible, not breaking:** every `PRNG`-seeded output re-rolls (xoshiro128\*\*
325
+ > replaces the Mersenne Twister), so a given seed produces a different galaxy,
326
+ > star system, cloud field, crowd and spawn sequence from 0.8.3 — "same seed,
327
+ > same battles" holds within 0.8.4, not across the upgrade. The shipped sky is
328
+ > rebaked (and now served from a versioned URL). Deprecated names still work
329
+ > and warn once: `generateGalaxy` (use `voxelGalaxy`), `onEnter`/`onExit` on
330
+ > the trigger, `handleChange`/`handleCommit` on the manipulator
331
+ > (`whenChange`/`whenCommit`). `registerIcons` now refuses markup that can run.
332
+
333
+ ### Fixed
334
+
335
+ - **Dropdowns in a panel with a pinned header opened over their own
336
+ control** — body popups were anchored as if there were no header, too high
337
+ by its height. The in-VR scene panel has one (the transport bar), so this was
338
+ every select in the headset. Header widgets also get a host now, so a
339
+ `select3d` pinned in a header can open at all.
340
+ - **`FrameInfo` keeps wall-clock time while paused**, as documented:
341
+ `realDt`, `realElapsed` and `frame` advance on a paused (still rendering)
342
+ frame; `dt` and `elapsed` stay stopped. They used to freeze too, which broke
343
+ the one case they exist for — a spinner on the pause panel.
344
+ - **Floating origin reaches the placeables.** `b3d-prop`, a standalone
345
+ `b3d-beacon` and a place-anchored `b3d-spawner` drifted on a terrain rebase;
346
+ they now shift with it. The shared piece is `AbstractMesh.followOrigin()`,
347
+ which `b3d-destroyable` now uses too.
348
+ - **`b3d-trigger` no longer calls BOTH `whenEnter` and `onEnter`** — the new
349
+ name wins and the old one warns once, like every other deprecated callback.
350
+ `themeEditor` reads `handleChange`/`onChange` the same way (it never warned).
351
+ - **A table's row-button menu opened low** by the COLUMN gap; it is now
352
+ anchored at the row.
353
+ - **A log slider with `zeroStop` ignored `step`**; it quantises in decades
354
+ like one without.
355
+ - **`registerIcons` refuses markup that can run** (`<script>`, `on*=`
356
+ handlers, `<foreignObject>`, `javascript:` URLs) with a warning; the trust
357
+ boundary is now documented.
358
+ - Clouds, ambient and water share one wind-inheritance rule
359
+ (`inheritedWind`); `vector3d`/`footprint3d`/`curve3d` import `handlerOf`
360
+ from its own module rather than dragging in `widgets3d`.
361
+ - **Stars were smeared into soft plus-shaped blobs.** The sky's star decoder
362
+ placed each star relative to whichever tap fetched its texel, so it moved
363
+ as the fragment moved and was drawn across the whole texel (sometimes twice,
364
+ when two rotated taps hit one texel). It now walks the 3×3 neighbourhood in
365
+ texel space and rebuilds every star from its own texel and sub-texel offset,
366
+ through an exact GLSL mirror of the codec's cube convention. Also, **the
367
+ data cube had mipmaps**, despite a comment saying otherwise: a mip of packed
368
+ fields is data nobody encoded. They are off now.
369
+ - **Airless sun and moon vanished** (the moon kept only its glow). The sky
370
+ model draws both through scattering, so without air there was nothing left.
371
+ Real half-degree discs now fade in as the air goes, and the glow fades with
372
+ it: a hard white sun and moon on a black sky. The airless moon is as bright
373
+ as the moon seen through air.
374
+ - **The voxel galaxy's disc was built of slabs** (seen by Tonio as
375
+ "ellipsoid columns" at 1M stars). Voxel layers (0.03 at `nz` 20) were as
376
+ thick as the disc, so stars sat in flat slabs with hard edges. `nz` now
377
+ defaults to 60. Every voxel address changes (0.8.4 already rerolls the
378
+ galaxy), and the shipped sky is rebaked with a slightly crisper band.
379
+ - **Big objects in the star sky were clipped square**, and **packed stars sat
380
+ on a lattice.** Objects wider than the decoder's 3×3 reach (distant galaxies
381
+ run to 3.5 texels) were cut at texel edges. Everything now tapers to nothing
382
+ within a texel. Packed stars in the dense core, known only to a quarter
383
+ texel, were drawn at their cells' centres, which formed a lattice the cube
384
+ projection bends into curves. They now get a deterministic jitter within the
385
+ cell (error diffusion for position).
386
+ - **`atmosphere` just below 1 made the clouds vanish.** 5% vacuum pushed a
387
+ 1–4 km fog out past 50 km, and the haze that hides the cloud deck's rim went
388
+ with it. The world's air is now steep, nearly binary (vacuum is
389
+ `(1 − atmosphere)⁴`): 0.95 is Earth, and only the last stretch thins the sky.
390
+ - **Touch on the orbit camera: two fingers pan, a pinch zooms** (tosijs-3d#52).
391
+ Babylon's default does both on every two-finger move, with zoom from the
392
+ change in SQUARED spacing, so a drag read as "a weird zoom"; its alternative
393
+ mode turns a slow pinch into a pan for good. New **`touchOrbit(camera)`**
394
+ (wired into `<tosi-b3d>`'s default camera and `orbitCam`) decides once per
395
+ gesture from NET spread vs NET centroid travel, then locks. Pan follows the
396
+ fingers (pixels → world units at the target's depth), and zoom is natural.
397
+ Pure decision model in **`touch-gesture`**, unit-tested; measured in
398
+ headless Chrome with emulated touch: a 150 px two-finger drag now moves the
399
+ target 2.35 m with the radius unchanged, where before it moved 0.76 m and
400
+ zoomed 8 → 8.68.
401
+ - **Vacuum hid the stars it was supposed to reveal.** The space band's fog
402
+ layer defaulted its sky veil to its weight, so leaving the atmosphere
403
+ painted the sky black OVER the starfield. Vacuum is the absence of a
404
+ medium; it now thins the haze without veiling the sky. Found building
405
+ `atmosphere: 0`, and it applies to the rocket climb too.
406
+ - **A field outside an attached `fieldGroup` swallowed keystrokes silently**
407
+ (tosijs-3d#82). Attaching any group stood the global key listener down for
408
+ EVERY field, so a tapped field outside it showed a caret while its keys went
409
+ to the group's active field or nowhere. Now the listener stands down only
410
+ for fields an attached group owns, and a field outside every group that
411
+ takes focus makes those groups let go. `fieldGroup` gains **`add(field)`**,
412
+ so a field created after the group can join it rather than depending on
413
+ construction order.
414
+ - **A page that mounted and removed scenes hit Chrome's WebGL context cap**
415
+ (tosijs-3d#79). Babylon's `engine.dispose()` keeps the context alive unless
416
+ `loseContextOnDispose` is set, so every removed scene held one until GC, and
417
+ Chrome force-loses the OLDEST live context past its cap — the scene you are
418
+ looking at goes black. Teardown now releases the context, and an element
419
+ re-added after a removal gets a fresh canvas (a lost context is permanent
420
+ for its canvas). A genuine re-add no longer warns "an engine already
421
+ exists". `bun bin/context-cap.ts` checks it in headless Chrome: 50 trips
422
+ with the removed elements retained; the live scene rendered nothing before
423
+ the fix and renders now.
424
+ - **Starfield attributes were construction-time only** (tosijs-3d#88). Setting
425
+ `starfieldData`, `starfieldCube`, `starfieldTilt`, `nebulae`, … on an
426
+ existing sky did nothing — no request, no stars, no error. They rebuild the
427
+ starfield now; the dome is untouched. A dispose also releases the cube
428
+ textures (a moved sky used to leave them behind).
429
+ - **A sky given `starfieldData` alone drew no stars.** The build returned
430
+ early unless `starfield` or `starfieldCube` was set, although the data cube
431
+ stands on its own.
432
+ - **`slider3d`, `toggle3d` and `select3d` rejected the reactive boxes they
433
+ are designed to bind** (tosijs-3d#76) — 24 false type errors in one
434
+ consumer panel. `value` is `Bindable<T>` now (exported): a `T` or anything
435
+ with `.value` and `.observe`.
436
+ - **`spaceStart`, `spaceFull` and `starDistance` said `unit` where every other
437
+ schema field says `x-unit`** (tosijs-3d#85). A test now rejects a bare `unit`.
438
+ - **`azimuth` is out of `skyboxSchema()`** (tosijs-3d#86). It has been a no-op
439
+ since the sky material fork (the sun is placed by `latitude` and
440
+ `timeOfDay`), and the schema offered it as a 0–360° slider while the docs
441
+ called it a 0–1 fraction. Still accepted in markup; documented as ignored.
442
+ - **A cloud deck forgot its local weather on a re-parent.** Its bake memo
443
+ survived `sceneDispose`, so the fresh mesh stayed unbaked — orographic cloud
444
+ gone — until some input moved. A deck moved into another `<tosi-b3d>` also
445
+ kept sampling the OLD scene's terrain (a narrow regression from 0.8.3's
446
+ cached lookup). Both reset on dispose now.
447
+ - **The deck did per-frame work it did not need.** With no field in force
448
+ (orographic 0, no `weather`) it re-baked zeros on every grid snap and every
449
+ terrain rebuild; and the fly-into-cloud whiteout rebuilt the whole weather
450
+ field — a fresh terrain height sampler — every frame to sample one point.
451
+ The first bakes once; the second samples the field the bake already built.
452
+
453
+ ### Added
454
+
455
+ - **Undo for the two page-global mutations.** `themeEditor()`'s element has
456
+ `restore()` — the palette as it was when the editor was built, then
457
+ `handleChange` so you rebuild. `registerIcons()` now RETURNS an undo that
458
+ removes the names it added and brings back any icon it replaced.
459
+ - **`<tosi-b3d-moon>`** — cosmetic extra moons (tosijs-3d#89). Put up to
460
+ four inside a `<tosi-b3d-skybox>`: `azimuth`/`elevation` on the star
461
+ sphere (they ride with the stars), `size` (angular diameter, degrees),
462
+ `color`, `brightness`. The PHASE is automatic: each disc is shaded as a
463
+ sphere lit by the real sun, so a moon near the sun is a crescent and one
464
+ opposite it is full. A moon hides the stars behind it; it casts no light.
465
+ - **`voxel-galaxy`** (`tosijs-3d/voxel-galaxy`) — the first step of
466
+ `GALAXY-DESIGN.md`: a galaxy as voxels, each with a derived bright and dim
467
+ seed. `brightStars()` is the global pass; `dimStarsNear(point, radius)` is
468
+ the local one the skybox baker will use. Density is sampled from
469
+ `generateGalaxy` and propagated so no voxel is empty; stars are addressed
470
+ `population:voxel:n` (no shared-seed clones). `galaxy-data` exports `starDetailFor`, `SPECTRAL_CLASSES`
471
+ and `SPECTRAL_WEIGHTS`, split out so both generators share one definition.
472
+ - **The baker reads the voxel galaxy** (`GALAXY-DESIGN.md` step 2):
473
+ `starsFromVoxelGalaxy(galaxy, shell, eye, { dimReach, floor })` gives every
474
+ bright star (today's rule — intrinsic brightness), the dim stars near the
475
+ eye falling off with distance (the gather radius computed from the floor,
476
+ not guessed), and the distant shell. `bakeSkyPair` takes precomputed
477
+ `objects`; `bin/bake-stars.ts` rebakes `static/sky/stars_*` without a
478
+ browser in ~1 s for the tuning loop; `SHIPPED_SKY.voxel` holds the dials.
479
+
480
+ ### Changed
481
+
482
+ - **`b3d-manipulator` callbacks are `whenChange` / `whenCommit`** — the
483
+ component convention. `handleChange`/`handleCommit` still work, warn once,
484
+ and are removed in 0.9.
485
+ - `AbstractMesh.applyUniformScale()` replaces the identical private copies in
486
+ `b3d-prop` and `b3d-destroyable`.
487
+ - **ONE galaxy implementation** (GALAXY-DESIGN.md → "Reconciliation").
488
+ The voxel galaxy is the galaxy now. `b3d-galaxy`, `b3d-star-system`, the
489
+ skybox baker and `bin/bake-stars` all read it. Its nebulae and distant shell
490
+ have their own derived seeds, so they no longer depend on the star count.
491
+ - **Visible:** a given seed now produces a **different galaxy** from 0.8.3
492
+ (same shape and distributions, different stars), and `starCount` is the
493
+ galaxy's **bright budget** (±noise), not an exact count.
494
+ - **`generateGalaxy` is deprecated** (removed in 0.9). It is now an adapter
495
+ returning `voxelGalaxy({ seed, brightBudget }).view()`. The spiral model it
496
+ was built on survives as **`sampleSpiral`**, which the density is sampled
497
+ from.
498
+ - **Stars have an address:** `id` is `seed:population:voxel:n`, with a
499
+ sequence number rather than the derived seed. `galaxy.star(id)`,
500
+ `b3d-galaxy.getStar(id)` and `b3d-star-system`'s new `star` attribute
501
+ resolve it. Star _n_ never depends on the budget, so an address resolves
502
+ even in a galaxy computed with a smaller one.
503
+ - **New:** `voxelGalaxy().view()`, `.star(id)`, `.nebulae()`, `.shell()`;
504
+ `b3d-galaxy`'s `dimBudget`; `starAddress` / `parseStarAddress`,
505
+ `starNameFor`, `sampleSpiral` and `starsFromVoxelGalaxy` in the barrel.
506
+ - **Interesting stars.** The dim population splits in two. **Interesting**
507
+ stars have a planet with HI ≤ 2 (breathable or filterable air, under 2 g,
508
+ not extreme); they are generated globally, so the galaxy demo's HI filter
509
+ sees every habitable system with its HI already known. **Boring** stars
510
+ are generated only locally, as sky texture. Both are exact: candidates are
511
+ drawn from a spectral mix biased by measured pass rates, then verified with
512
+ the real HI rules. The rates come from a harness, **`bin/tune-populations.ts`**,
513
+ which writes `population-table.ts`. A fingerprint test fails when HI
514
+ changes, and the fix is to rerun it. HI 3 is deliberately not
515
+ "interesting": it has no air requirement, so the Moon and Mars are HI 3.
516
+ `generateGalaxy` (deprecated) keeps meaning bright stars only.
517
+ - **`PRNG` runs on xoshiro128\*\*** (new **`Xoshiro128`**, exported). MT
518
+ carried 624 words of state and regenerated them before its first draw,
519
+ so just constructing one cost ~13 µs, and a star system builds one per
520
+ planet. xoshiro has 4 words: 30–75× faster to construct and draw, and it
521
+ passes BigCrush and PractRand, which MT fails. A star system now costs
522
+ 6.5 µs, down from 70. It is checked against an independent BigInt
523
+ transcription of the reference C. **Every `PRNG`-seeded output
524
+ re-rolls:** planets, the spiral samples, nebulae, the shell and the
525
+ skybox's point starfield. That is folded into this release, which already
526
+ changes every galaxy. `b3d-clouds`, `b3d-crowd` and `b3d-spawner` moved
527
+ too, so their seeded layouts (cloud blobs, crowd placement, spawn rings)
528
+ reroll once as well. `MersenneTwister` stays exported.
529
+ - **The shipped sky's nebula half is rebaked** with the new nebulae.
530
+ **`bin/bake-nebula.ts`** bakes it in headless Chrome and checks that the
531
+ point half on disk came from the same galaxy.
532
+ - **Noted late, from 0.8.3: `slider3d`'s `peek` readout appears BESIDE the
533
+ caption rather than replacing it** (tosijs-3d#84). Deliberate — a label that
534
+ vanishes the moment you touch the control loses the name of what you are
535
+ adjusting — but it went unannounced, and a consumer test pinned the old
536
+ behaviour. Press and drag over the caption also work (the stacked layout made
537
+ the whole width the track); now pinned by a test.
538
+ - **The shipped sky's stars are rebaked from the voxel galaxy** — 100k bright
539
+ stars plus the dim stars around the bake point (~119k objects, against 100k
540
+ before). Far more stars at night, and a dense local sprinkle across the
541
+ whole sky rather than only along the band. The band itself is a little
542
+ broader. `bun bin/bake-stars.ts` with no flags reproduces it byte-for-byte;
543
+ the smooth `nebula_*` half is unchanged.
544
+ - **The shipped sky has pinned, versioned URLs** (tosijs-3d#90):
545
+ `/sky/0.8.3/…` (the previous sky) and `/sky/0.8.4/…` (this one), each with a
546
+ `manifest.json` holding the decode parameters and the recipe.
547
+ `/sky/stars` stays "latest" for the demos; a document should pin a version.
548
+ `shipped-sky.test.ts` fails if latest drifts from the newest version.
549
+ - **A world's own air, and its sky colour** (tosijs-3d#89). `b3d-skybox`
550
+ gains **`atmosphere`** (1 Earth … 0 the Moon: black noon, stars out, no
551
+ haze, a hard sun), which multiplies with the space band, and
552
+ **`zenithTint` / `horizonTint` / `tintStrength`**, which colourize the
553
+ scattered light only, keeping its brightness (a butterscotch Mars, a green
554
+ alien sky). Stars and moon are behind the air and stay untinted. Land and
555
+ Sky gains an **Atmosphere** group: a world preset (Earth / Mars / Alien /
556
+ Airless) plus live air, tint, turbidity, rayleigh, mie and luminance.
557
+ - **Viewer-relative panels in the flat view** (tosijs-3d#81).
558
+ `<tosi-b3d-panel presence="both">` appears on a monitor as well as in a
559
+ headset, placed with the same frame vocabulary (eye / body / neck / face,
560
+ azimuth-elevation-distance, presets, gaze reveal). **`XrFrames.flat(scene,
561
+ camera)`** derives the frames from any flat camera's world pose (the eye
562
+ frame takes the view's yaw, not its pitch). Hands have no flat analogue, so
563
+ a hand panel declares **`flatFrame`**; without one it stays VR-only and
564
+ warns once. `presence` defaults to `'xr'`, so existing panels are unchanged.
565
+ - **`<tosi-b3d-decorator>` — rocks and trees on the terrain, by budget and
566
+ by climate.** The pure **`scatterPlacements`** model places a fixed NUMBER of things.
567
+ Its rules use the biome shader's own axes (temperature, moisture, altitude
568
+ above sea, slope), so pines grow in the cold, palms on warm shores, cacti in
569
+ hot dry country, and boulders on the steep. Candidates are world-anchored
570
+ and deterministic, and an acceptance threshold is solved to hit the budget.
571
+ Provinces suppress by kind (no plants on a lava field; rocks still). Models
572
+ come from Kenney's Nature Kit, drawn as thin instances: one draw call per
573
+ model part, whatever the budget. A small collider pool follows the camera:
574
+ trunks as posts, boulders as boxes. `measureCost()` steps through budgets
575
+ and reports frame time, GPU time (where exposed), draw calls and indices.
576
+ Land and Sky has a Vegetation group (budget 0–20k, reach).
577
+ - **Render resolution follows the display, by device tier.** Scenes rendered
578
+ at CSS resolution, so a 1.25× or 2× display upscaled everything (soft edges,
579
+ soft stars). The render now uses the display's pixel ratio capped by tier:
580
+ high up to 2×, medium up to 1.5×, low at CSS resolution. The new
581
+ `PerfBudgets.pixelRatioCap` sets the cap, and **`<tosi-b3d pixel-ratio>`**
582
+ overrides it with an explicit value (`1` = CSS resolution, `2` = full
583
+ Retina, `0.75` = below CSS for speed). It follows a window moved to another
584
+ monitor, and XR is unaffected. The rule is `renderScalingLevel` in
585
+ `b3d-quality`, tested.
586
+ - **Twinkle** (`starfieldTwinkle`, default 0.35): scintillation, which is
587
+ the air, so it scales with the gas (none on an airless world), is strongest
588
+ at the horizon, and each star flickers on its own pattern. Land and Sky has
589
+ a twinkle slider.
590
+ - **`dust` on `b3d-skybox`** separates dust from gas. Gas (`atmosphere`)
591
+ scatters blue; dust scatters a bright grey haze that the tint colours. So
592
+ Mars is almost no air, lots of dust and a butterscotch sky (Land and Sky's
593
+ preset: air 0.03, dust 0.85), and the Moon is neither. The previous Mars
594
+ needed full air to be bright at all.
595
+ - **Star look dials on `b3d-skybox`:** **`starfieldGain`** (brightness) and
596
+ **`starfieldFloor`** (the faint mass) join `starfieldSharpness` (size) and
597
+ `starfieldSizeScale`. All four are live uniforms, so a slider does not reload
598
+ the cube. With exact points the defaults moved to gain 0.9 and floor 0.4.
599
+ Land and Sky has a Stars group (size, brightness, faint stars).
600
+ - **`slider3d({ useful: [lo, hi] })`** (tosijs-3d#83) — a soft band on the
601
+ track where the values anybody wants live; the handle still reaches `min`
602
+ and `max`. It is where a schema's `x-useful` lands.
603
+ - **`cloudDeckSchema()`** in `sceneSchemas` (tosijs-3d#87) — the deck was the
604
+ one scene primitive a schema-driven consumer had to describe by hand.
605
+ - **Fetched strings are marked in the scene schemas** (tosijs-3d#91):
606
+ `format: 'uri-reference'` on `starfieldData`, `starfieldCube`,
607
+ `nebulaTexture`, `water.normalMap`, `clouds.model` and `ground.texture`
608
+ (which also lists its keywords in `x-keywords`). A test fails on any new
609
+ plain string that is not a colour, enum, URL or deliberately listed.
610
+ - **`bun run sizes`** — the barrel as a consumer bundles it, and the shipped
611
+ `dist/*.js`, against a committed `dist-sizes.json` (0.8.3: 263.1 KB gz).
612
+
9
613
  ## 0.8.3
10
614
 
11
615
  **A galaxy that costs nothing per frame, a sky that keeps the right time, and