@jgengine/shell 0.8.0 → 0.10.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 (152) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +11 -0
  3. package/dist/GamePlayerShell.d.ts +10 -22
  4. package/dist/GamePlayerShell.js +709 -264
  5. package/dist/GameUiPreview.js +2 -1
  6. package/dist/audio/AudioComponents.js +25 -8
  7. package/dist/audio/audioEngine.d.ts +9 -0
  8. package/dist/audio/audioEngine.js +37 -0
  9. package/dist/audio/musicDirector.d.ts +31 -0
  10. package/dist/audio/musicDirector.js +125 -0
  11. package/dist/audio/musicVoices.d.ts +10 -0
  12. package/dist/audio/musicVoices.js +338 -0
  13. package/dist/audio/synthEngine.d.ts +9 -0
  14. package/dist/audio/synthEngine.js +57 -0
  15. package/dist/behaviour.d.ts +5 -24
  16. package/dist/behaviour.js +14 -50
  17. package/dist/behaviourAttach.d.ts +9 -0
  18. package/dist/behaviourAttach.js +32 -0
  19. package/dist/behaviourDriver.d.ts +7 -0
  20. package/dist/behaviourDriver.js +21 -0
  21. package/dist/camera/GameCameraRig.js +1 -1
  22. package/dist/camera/GameFirstPersonCamera.d.ts +3 -0
  23. package/dist/camera/GameFirstPersonCamera.js +35 -4
  24. package/dist/camera/GameInspectionCamera.js +12 -1
  25. package/dist/camera/GameOrbitCamera.js +44 -1
  26. package/dist/camera/PlayerFov.d.ts +18 -0
  27. package/dist/camera/PlayerFov.js +48 -0
  28. package/dist/camera/cameraBlendMath.d.ts +13 -0
  29. package/dist/camera/cameraBlendMath.js +36 -0
  30. package/dist/camera/cameraRigs.d.ts +3 -0
  31. package/dist/camera/cameraRigs.js +36 -33
  32. package/dist/camera/fovPreference.d.ts +25 -0
  33. package/dist/camera/fovPreference.js +73 -0
  34. package/dist/camera/index.d.ts +3 -0
  35. package/dist/camera/index.js +3 -0
  36. package/dist/camera/orbitCameraMath.d.ts +18 -0
  37. package/dist/camera/orbitCameraMath.js +6 -1
  38. package/dist/camera/rigMath.d.ts +10 -0
  39. package/dist/camera/rigMath.js +36 -1
  40. package/dist/camera/shakeChannel.d.ts +3 -17
  41. package/dist/camera/shakeChannel.js +2 -21
  42. package/dist/camera/shakeChannelMath.d.ts +8 -0
  43. package/dist/camera/shakeChannelMath.js +21 -0
  44. package/dist/cartridge.d.ts +145 -0
  45. package/dist/cartridge.js +246 -0
  46. package/dist/commandSink.d.ts +20 -0
  47. package/dist/commandSink.js +27 -0
  48. package/dist/defineGame.js +4 -1
  49. package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
  50. package/dist/devtools/CollisionDebugWorld.js +181 -0
  51. package/dist/devtools/DevtoolsOverlay.d.ts +65 -1
  52. package/dist/devtools/DevtoolsOverlay.js +383 -41
  53. package/dist/devtools/collisionDebug.d.ts +57 -0
  54. package/dist/devtools/collisionDebug.js +127 -0
  55. package/dist/devtools/collisionDebugMath.d.ts +103 -0
  56. package/dist/devtools/collisionDebugMath.js +129 -0
  57. package/dist/environment/Daylight.d.ts +20 -8
  58. package/dist/environment/Daylight.js +63 -15
  59. package/dist/environment/EnvironmentScene.js +91 -35
  60. package/dist/environment/RoadRibbons.d.ts +7 -0
  61. package/dist/environment/RoadRibbons.js +57 -0
  62. package/dist/environment/groundPadMath.d.ts +2 -2
  63. package/dist/environment/groundPadMath.js +1 -1
  64. package/dist/environment/index.d.ts +2 -1
  65. package/dist/environment/index.js +1 -0
  66. package/dist/environment/skyLightingPolicy.d.ts +10 -0
  67. package/dist/environment/skyLightingPolicy.js +6 -0
  68. package/dist/input/mouseLook.d.ts +27 -0
  69. package/dist/input/mouseLook.js +35 -0
  70. package/dist/inputSink.d.ts +18 -0
  71. package/dist/inputSink.js +35 -0
  72. package/dist/materialOverride.d.ts +4 -6
  73. package/dist/materialOverride.js +12 -16
  74. package/dist/multiplayer.js +7 -3
  75. package/dist/pointer/PointerProbe.js +6 -2
  76. package/dist/pointer/pointerService.d.ts +3 -0
  77. package/dist/pointer/pointerService.js +6 -0
  78. package/dist/postfx/PostProcessing.d.ts +10 -0
  79. package/dist/postfx/PostProcessing.js +82 -0
  80. package/dist/postfx/gradeShader.d.ts +4 -0
  81. package/dist/postfx/gradeShader.js +64 -0
  82. package/dist/render/modelRender.d.ts +10 -1
  83. package/dist/render/modelRender.js +32 -5
  84. package/dist/render/resolveModel.d.ts +14 -0
  85. package/dist/render/resolveModel.js +24 -0
  86. package/dist/settings/QuickControls.d.ts +4 -0
  87. package/dist/settings/QuickControls.js +42 -0
  88. package/dist/settings/SettingsChrome.d.ts +1 -0
  89. package/dist/settings/SettingsChrome.js +10 -0
  90. package/dist/settings/SettingsMenu.d.ts +6 -0
  91. package/dist/settings/SettingsMenu.js +148 -0
  92. package/dist/settings/SettingsRuntime.d.ts +11 -0
  93. package/dist/settings/SettingsRuntime.js +19 -0
  94. package/dist/settings/appliedSettings.d.ts +14 -0
  95. package/dist/settings/appliedSettings.js +27 -0
  96. package/dist/settings/settingsController.d.ts +20 -0
  97. package/dist/settings/settingsController.js +165 -0
  98. package/dist/structures/GeneratedBuilding.d.ts +10 -0
  99. package/dist/structures/GeneratedBuilding.js +96 -8
  100. package/dist/structures/index.d.ts +1 -1
  101. package/dist/structures/index.js +1 -1
  102. package/dist/terrain/CarvedTerrain.d.ts +5 -1
  103. package/dist/terrain/CarvedTerrain.js +6 -5
  104. package/dist/terrain/GrassField.d.ts +3 -1
  105. package/dist/terrain/GrassField.js +4 -2
  106. package/dist/terrain/grassBudget.d.ts +3 -0
  107. package/dist/terrain/grassBudget.js +6 -0
  108. package/dist/terrain/grassGeometry.js +1 -1
  109. package/dist/terrain/terrainDetailMaterial.d.ts +14 -0
  110. package/dist/terrain/terrainDetailMaterial.js +90 -0
  111. package/dist/terrain/terrainMath.d.ts +8 -0
  112. package/dist/terrain/terrainMath.js +9 -0
  113. package/dist/touch/OrientationHint.d.ts +3 -0
  114. package/dist/touch/OrientationHint.js +13 -0
  115. package/dist/touch/TouchControlsOverlay.d.ts +17 -1
  116. package/dist/touch/TouchControlsOverlay.js +125 -9
  117. package/dist/visibility/CullingProvider.d.ts +21 -0
  118. package/dist/visibility/CullingProvider.js +134 -0
  119. package/dist/vision/FrustumSensorHud.d.ts +1 -6
  120. package/dist/vision/FrustumSensorHud.js +25 -27
  121. package/dist/vision/frustumSampleEqual.d.ts +2 -0
  122. package/dist/vision/frustumSampleEqual.js +10 -0
  123. package/dist/water/Ocean.js +1 -1
  124. package/dist/water/OceanConfig.d.ts +13 -0
  125. package/dist/water/OceanConfig.js +25 -17
  126. package/dist/water/index.d.ts +1 -1
  127. package/dist/water/index.js +1 -1
  128. package/dist/weather/FireSpreadLayer.js +7 -2
  129. package/dist/weather/RainField.d.ts +3 -1
  130. package/dist/weather/RainField.js +4 -4
  131. package/dist/weather/SnowField.d.ts +3 -1
  132. package/dist/weather/SnowField.js +4 -4
  133. package/dist/weather/fireSpreadPose.d.ts +2 -0
  134. package/dist/weather/fireSpreadPose.js +4 -0
  135. package/dist/weather/weatherMath.d.ts +5 -1
  136. package/dist/weather/weatherMath.js +7 -2
  137. package/dist/world/DataObjects.d.ts +1 -1
  138. package/dist/world/DataObjects.js +3 -1
  139. package/dist/world/SpriteBatch.d.ts +44 -0
  140. package/dist/world/SpriteBatch.js +112 -0
  141. package/dist/world/WorldHud.d.ts +6 -1
  142. package/dist/world/WorldHud.js +95 -48
  143. package/dist/world/entityPose.d.ts +14 -0
  144. package/dist/world/entityPose.js +10 -0
  145. package/dist/world/telegraphPulse.d.ts +1 -0
  146. package/dist/world/telegraphPulse.js +4 -0
  147. package/dist/world/worldBarSamples.d.ts +30 -0
  148. package/dist/world/worldBarSamples.js +57 -0
  149. package/dist/worldSync.d.ts +10 -0
  150. package/dist/worldSync.js +19 -0
  151. package/llms.txt +1522 -1143
  152. package/package.json +4 -4
@@ -0,0 +1,338 @@
1
+ import { mtof } from "@jgengine/core/audio/music";
2
+ function adsr(ctx, when, dur, peak, attack, release) {
3
+ const g = ctx.createGain();
4
+ g.gain.setValueAtTime(0.0001, when);
5
+ g.gain.linearRampToValueAtTime(peak, when + attack);
6
+ g.gain.setValueAtTime(peak, Math.max(when + attack, when + dur - release));
7
+ g.gain.exponentialRampToValueAtTime(0.0001, when + dur + release);
8
+ return g;
9
+ }
10
+ function stack(ctx, when, freq, dur, out, type, dets, tail) {
11
+ for (const det of dets) {
12
+ const o = ctx.createOscillator();
13
+ o.type = type;
14
+ o.frequency.value = freq;
15
+ o.detune.value = det;
16
+ o.connect(out);
17
+ o.start(when);
18
+ o.stop(when + dur + tail);
19
+ }
20
+ }
21
+ function strings(ctx, when, freq, dur, vel, out, attack = 0.3) {
22
+ const g = adsr(ctx, when, dur, vel * 0.16, attack, 0.7);
23
+ const lp = ctx.createBiquadFilter();
24
+ lp.type = "lowpass";
25
+ lp.frequency.value = 750 + freq * 2;
26
+ lp.connect(g).connect(out);
27
+ stack(ctx, when, freq, dur, lp, "sawtooth", [-6, 5], 0.9);
28
+ }
29
+ function pad(ctx, when, freq, dur, vel, out) {
30
+ const g = adsr(ctx, when, dur, vel * 0.11, 0.6, 1.1);
31
+ const lp = ctx.createBiquadFilter();
32
+ lp.type = "lowpass";
33
+ lp.frequency.value = 520 + freq * 1.4;
34
+ lp.connect(g).connect(out);
35
+ stack(ctx, when, freq, dur, lp, "sawtooth", [-9, -3, 4, 10], 1.2);
36
+ }
37
+ function reed(ctx, when, freq, dur, vel, out) {
38
+ const g = adsr(ctx, when, dur, vel * 0.14, 0.08, 0.25);
39
+ const lp = ctx.createBiquadFilter();
40
+ lp.type = "lowpass";
41
+ lp.frequency.value = 1600 + freq;
42
+ lp.connect(g).connect(out);
43
+ stack(ctx, when, freq, dur, lp, "sawtooth", [-3, 4], 0.35);
44
+ }
45
+ function horn(ctx, when, freq, dur, vel, out) {
46
+ const g = adsr(ctx, when, dur, vel * 0.15, 0.09, 0.3);
47
+ const lp = ctx.createBiquadFilter();
48
+ lp.type = "lowpass";
49
+ lp.frequency.value = 900 + freq * 1.5;
50
+ lp.Q.value = 0.6;
51
+ lp.connect(g).connect(out);
52
+ stack(ctx, when, freq, dur, lp, "sawtooth", [-4, 3], 0.4);
53
+ const sub = ctx.createOscillator();
54
+ sub.type = "sine";
55
+ sub.frequency.value = freq;
56
+ const sg = ctx.createGain();
57
+ sg.gain.value = 0.4;
58
+ sub.connect(sg).connect(lp);
59
+ sub.start(when);
60
+ sub.stop(when + dur + 0.4);
61
+ }
62
+ function brassStab(ctx, when, freq, dur, vel, out) {
63
+ const g = adsr(ctx, when, Math.min(dur, 0.4), vel * 0.17, 0.02, 0.14);
64
+ const lp = ctx.createBiquadFilter();
65
+ lp.type = "lowpass";
66
+ lp.frequency.value = 1400 + freq * 2;
67
+ lp.connect(g).connect(out);
68
+ stack(ctx, when, freq, Math.min(dur, 0.4), lp, "sawtooth", [-5, 6], 0.2);
69
+ }
70
+ function flute(ctx, when, freq, dur, vel, out) {
71
+ const g = adsr(ctx, when, dur, vel * 0.15, 0.06, 0.2);
72
+ const lp = ctx.createBiquadFilter();
73
+ lp.type = "lowpass";
74
+ lp.frequency.value = 2600;
75
+ lp.connect(g).connect(out);
76
+ const o = ctx.createOscillator();
77
+ o.type = "sine";
78
+ o.frequency.value = freq;
79
+ o.connect(lp);
80
+ o.start(when);
81
+ o.stop(when + dur + 0.25);
82
+ const breath = ctx.createBufferSource();
83
+ const len = Math.floor(ctx.sampleRate * Math.min(dur, 0.4));
84
+ const buf = ctx.createBuffer(1, len, ctx.sampleRate);
85
+ const data = buf.getChannelData(0);
86
+ for (let i = 0; i < len; i += 1)
87
+ data[i] = (Math.random() * 2 - 1) * 0.5;
88
+ breath.buffer = buf;
89
+ const bp = ctx.createBiquadFilter();
90
+ bp.type = "bandpass";
91
+ bp.frequency.value = freq * 2;
92
+ const bg = ctx.createGain();
93
+ bg.gain.value = vel * 0.02;
94
+ breath.connect(bp).connect(bg).connect(out);
95
+ breath.start(when);
96
+ }
97
+ function pipe(ctx, when, freq, dur, vel, out) {
98
+ const g = adsr(ctx, when, dur, vel * 0.13, 0.03, 0.15);
99
+ const lp = ctx.createBiquadFilter();
100
+ lp.type = "lowpass";
101
+ lp.frequency.value = 3000;
102
+ lp.connect(g).connect(out);
103
+ stack(ctx, when, freq, dur, lp, "triangle", [0, 7], 0.2);
104
+ }
105
+ function squareLead(ctx, when, freq, dur, vel, out) {
106
+ const g = adsr(ctx, when, dur, vel * 0.1, 0.02, 0.12);
107
+ const lp = ctx.createBiquadFilter();
108
+ lp.type = "lowpass";
109
+ lp.frequency.value = 2200 + freq;
110
+ lp.connect(g).connect(out);
111
+ const o = ctx.createOscillator();
112
+ o.type = "square";
113
+ o.frequency.value = freq;
114
+ o.connect(lp);
115
+ o.start(when);
116
+ o.stop(when + dur + 0.2);
117
+ }
118
+ function choir(ctx, when, freq, dur, vel, out) {
119
+ const g = adsr(ctx, when, dur, vel * 0.12, 0.25, 0.6);
120
+ const formant = ctx.createBiquadFilter();
121
+ formant.type = "bandpass";
122
+ formant.frequency.value = Math.min(1800, 500 + freq * 1.6);
123
+ formant.Q.value = 0.7;
124
+ formant.connect(g).connect(out);
125
+ stack(ctx, when, freq, dur, formant, "sawtooth", [-8, -2, 6], 0.8);
126
+ stack(ctx, when, freq * 2, dur, formant, "triangle", [3], 0.8);
127
+ }
128
+ function oboe(ctx, when, freq, dur, vel, out) {
129
+ const g = adsr(ctx, when, dur, vel * 0.17, 0.055, 0.22);
130
+ const formant = ctx.createBiquadFilter();
131
+ formant.type = "bandpass";
132
+ formant.frequency.value = Math.min(2400, 600 + freq * 2.2);
133
+ formant.Q.value = 0.9;
134
+ const lp = ctx.createBiquadFilter();
135
+ lp.type = "lowpass";
136
+ lp.frequency.value = 2800;
137
+ formant.connect(lp).connect(g).connect(out);
138
+ const vib = ctx.createOscillator();
139
+ vib.frequency.value = 5.2;
140
+ const vibGain = ctx.createGain();
141
+ vibGain.gain.setValueAtTime(0, when);
142
+ vibGain.gain.linearRampToValueAtTime(freq * 0.004, when + 0.3);
143
+ vib.connect(vibGain);
144
+ for (const det of [-5, 4]) {
145
+ const o = ctx.createOscillator();
146
+ o.type = "sawtooth";
147
+ o.frequency.value = freq;
148
+ o.detune.value = det;
149
+ vibGain.connect(o.frequency);
150
+ o.connect(formant);
151
+ o.start(when);
152
+ o.stop(when + dur + 0.4);
153
+ }
154
+ vib.start(when);
155
+ vib.stop(when + dur + 0.4);
156
+ }
157
+ function pluck(ctx, when, freq, vel, out, decay, bassy = false) {
158
+ const g = ctx.createGain();
159
+ const peak = vel * (bassy ? 0.26 : 0.2);
160
+ g.gain.setValueAtTime(0.0001, when);
161
+ g.gain.linearRampToValueAtTime(peak, when + 0.004);
162
+ g.gain.exponentialRampToValueAtTime(0.0001, when + decay);
163
+ const lp = ctx.createBiquadFilter();
164
+ lp.type = "lowpass";
165
+ lp.frequency.value = bassy ? 900 : 2600 + freq;
166
+ lp.connect(g).connect(out);
167
+ const o = ctx.createOscillator();
168
+ o.type = bassy ? "triangle" : "sawtooth";
169
+ o.frequency.value = freq;
170
+ o.connect(lp);
171
+ o.start(when);
172
+ o.stop(when + decay + 0.1);
173
+ if (!bassy) {
174
+ const o2 = ctx.createOscillator();
175
+ o2.type = "triangle";
176
+ o2.frequency.value = freq * 2.001;
177
+ const g2 = ctx.createGain();
178
+ g2.gain.setValueAtTime(peak * 0.4, when);
179
+ g2.gain.exponentialRampToValueAtTime(0.0001, when + decay * 0.6);
180
+ o2.connect(g2).connect(out);
181
+ o2.start(when);
182
+ o2.stop(when + decay);
183
+ }
184
+ }
185
+ function bell(ctx, when, freq, vel, out, decay = 1.8) {
186
+ for (const [ratio, amp] of [[1, 1], [2.76, 0.5], [5.4, 0.25], [8.9, 0.12]]) {
187
+ const g = ctx.createGain();
188
+ g.gain.setValueAtTime(0.0001, when);
189
+ g.gain.linearRampToValueAtTime(vel * 0.14 * amp, when + 0.005);
190
+ g.gain.exponentialRampToValueAtTime(0.0001, when + decay * (1 - (ratio - 1) * 0.06));
191
+ const o = ctx.createOscillator();
192
+ o.type = "sine";
193
+ o.frequency.value = freq * ratio;
194
+ o.connect(g).connect(out);
195
+ o.start(when);
196
+ o.stop(when + decay + 0.2);
197
+ }
198
+ }
199
+ function piano(ctx, when, freq, dur, vel, out) {
200
+ const naturalDecay = Math.min(5.2, Math.max(1.2, 380 / freq));
201
+ const body = ctx.createBiquadFilter();
202
+ body.type = "lowpass";
203
+ body.frequency.value = Math.min(5600, 1400 + freq * 4);
204
+ body.Q.value = 0.35;
205
+ body.connect(out);
206
+ const partials = [
207
+ [1, 0.62, 1, -3],
208
+ [1.0005, 0.62, 1, 3],
209
+ [2.003, 0.5, 0.58, 2],
210
+ [3.006, 0.2, 0.36, -4],
211
+ [4.012, 0.09, 0.24, 5],
212
+ [5.02, 0.05, 0.17, -6],
213
+ [7.03, 0.025, 0.12, 4],
214
+ ];
215
+ for (const [ratio, amp, decayMul, cents] of partials) {
216
+ const decay = Math.min(naturalDecay * decayMul, dur + 0.35);
217
+ const g = ctx.createGain();
218
+ const peak = vel * 0.24 * amp;
219
+ g.gain.setValueAtTime(0.0001, when);
220
+ g.gain.linearRampToValueAtTime(peak, when + 0.004);
221
+ g.gain.exponentialRampToValueAtTime(0.0001, when + Math.max(0.14, decay));
222
+ const o = ctx.createOscillator();
223
+ o.type = ratio < 1.01 ? "triangle" : "sine";
224
+ o.frequency.value = freq * ratio;
225
+ o.detune.value = cents;
226
+ o.connect(g).connect(body);
227
+ o.start(when);
228
+ o.stop(when + Math.max(0.14, decay) + 0.1);
229
+ }
230
+ }
231
+ function noiseHit(ctx, when, vel, out, hp, len, curve) {
232
+ const n = Math.floor(ctx.sampleRate * len);
233
+ const buf = ctx.createBuffer(1, n, ctx.sampleRate);
234
+ const data = buf.getChannelData(0);
235
+ for (let i = 0; i < n; i += 1)
236
+ data[i] = (Math.random() * 2 - 1) * (1 - i / n) ** curve;
237
+ const src = ctx.createBufferSource();
238
+ src.buffer = buf;
239
+ const f = ctx.createBiquadFilter();
240
+ f.type = "highpass";
241
+ f.frequency.value = hp;
242
+ const g = ctx.createGain();
243
+ g.gain.setValueAtTime(vel * 0.22, when);
244
+ g.gain.exponentialRampToValueAtTime(0.0001, when + len);
245
+ src.connect(f).connect(g).connect(out);
246
+ src.start(when);
247
+ }
248
+ function drum(ctx, when, freq, vel, out, decay, punch) {
249
+ const o = ctx.createOscillator();
250
+ o.type = "sine";
251
+ o.frequency.setValueAtTime(freq * punch, when);
252
+ o.frequency.exponentialRampToValueAtTime(freq, when + 0.06);
253
+ const g = ctx.createGain();
254
+ g.gain.setValueAtTime(vel * 0.5, when);
255
+ g.gain.exponentialRampToValueAtTime(0.0001, when + decay);
256
+ o.connect(g).connect(out);
257
+ o.start(when);
258
+ o.stop(when + decay + 0.05);
259
+ noiseHit(ctx, when, vel * 0.4, out, 200, Math.min(decay, 0.12), 2);
260
+ }
261
+ function woodBlock(ctx, when, vel, out) {
262
+ const o = ctx.createOscillator();
263
+ o.type = "square";
264
+ o.frequency.value = 1100;
265
+ const g = ctx.createGain();
266
+ g.gain.setValueAtTime(vel * 0.2, when);
267
+ g.gain.exponentialRampToValueAtTime(0.0001, when + 0.05);
268
+ const bp = ctx.createBiquadFilter();
269
+ bp.type = "bandpass";
270
+ bp.frequency.value = 1100;
271
+ o.connect(bp).connect(g).connect(out);
272
+ o.start(when);
273
+ o.stop(when + 0.07);
274
+ }
275
+ function cymSwell(ctx, when, dur, vel, out) {
276
+ const n = Math.floor(ctx.sampleRate * (dur + 0.3));
277
+ const buf = ctx.createBuffer(1, n, ctx.sampleRate);
278
+ const data = buf.getChannelData(0);
279
+ for (let i = 0; i < n; i += 1)
280
+ data[i] = Math.random() * 2 - 1;
281
+ const src = ctx.createBufferSource();
282
+ src.buffer = buf;
283
+ const hp = ctx.createBiquadFilter();
284
+ hp.type = "highpass";
285
+ hp.frequency.value = 6000;
286
+ const g = ctx.createGain();
287
+ g.gain.setValueAtTime(0.0001, when);
288
+ g.gain.linearRampToValueAtTime(vel * 0.1, when + dur);
289
+ g.gain.exponentialRampToValueAtTime(0.0001, when + dur + 0.3);
290
+ src.connect(hp).connect(g).connect(out);
291
+ src.start(when);
292
+ }
293
+ /**
294
+ * Play one theme note through its instrument voice into `out` (a per-layer gain
295
+ * node the director wires to master + reverb). `spb` is seconds-per-beat and
296
+ * `transpose` shifts the note in semitones for per-zone key changes. This is the
297
+ * engine's reusable instrument library; unknown instruments fall back to a sine.
298
+ */
299
+ export function playMusicNote(ctx, event, when, spb, out, transpose = 0) {
300
+ const freq = mtof(event.midi + transpose);
301
+ const dur = Math.max(0.1, event.dur * spb);
302
+ const vel = event.vel;
303
+ const inst = event.inst;
304
+ switch (inst) {
305
+ case "strings": return strings(ctx, when, freq, dur, vel, out);
306
+ case "stacc": return strings(ctx, when, freq, Math.min(dur, 0.22), vel, out, 0.02);
307
+ case "pad": return pad(ctx, when, freq, dur, vel, out);
308
+ case "reed": return reed(ctx, when, freq, dur, vel, out);
309
+ case "horn": return horn(ctx, when, freq, dur, vel, out);
310
+ case "brassStab": return brassStab(ctx, when, freq, dur, vel, out);
311
+ case "flute": return flute(ctx, when, freq, dur, vel, out);
312
+ case "pipe": return pipe(ctx, when, freq, dur, vel, out);
313
+ case "squareLead": return squareLead(ctx, when, freq, dur, vel, out);
314
+ case "choir": return choir(ctx, when, freq, dur, vel, out);
315
+ case "oboe": return oboe(ctx, when, freq, dur, vel, out);
316
+ case "harp": return pluck(ctx, when, freq, vel, out, 1.4);
317
+ case "lute": return pluck(ctx, when, freq, vel, out, 0.7);
318
+ case "dulcimer": return pluck(ctx, when, freq, vel, out, 1.0);
319
+ case "bass": return pluck(ctx, when, freq, vel, out, 0.9, true);
320
+ case "bell": return bell(ctx, when, freq, vel, out);
321
+ case "tinyBell": return bell(ctx, when, freq, vel, out, 0.7);
322
+ case "piano": return piano(ctx, when, freq, dur, vel, out);
323
+ case "timpani": return drum(ctx, when, freq, vel, out, 0.6, 1.5);
324
+ case "warDrum": return drum(ctx, when, Math.max(50, freq * 0.5), vel, out, 0.35, 1.8);
325
+ case "frameDrum": return drum(ctx, when, Math.max(70, freq * 0.6), vel, out, 0.25, 1.6);
326
+ case "shaker": return noiseHit(ctx, when, vel, out, 6800, 0.055, 1.8);
327
+ case "woodBlock": return woodBlock(ctx, when, vel, out);
328
+ case "cymSwell": return cymSwell(ctx, when, dur, vel, out);
329
+ default: {
330
+ const o = ctx.createOscillator();
331
+ o.frequency.value = freq;
332
+ const g = adsr(ctx, when, dur, vel * 0.12, 0.02, 0.2);
333
+ o.connect(g).connect(out);
334
+ o.start(when);
335
+ o.stop(when + dur + 0.3);
336
+ }
337
+ }
338
+ }
@@ -0,0 +1,9 @@
1
+ import type { SynthPatch } from "@jgengine/core/audio/synth";
2
+ /** Build the shared 1-second mono white-noise buffer every noise voice samples from. */
3
+ export declare function createNoiseBuffer(ctx: BaseAudioContext): AudioBuffer;
4
+ /**
5
+ * Realise a procedural cue on Web Audio: every voice is scheduled at
6
+ * `ctx.currentTime + delay` into `out`, summed into one one-shot. `noiseBuf` is
7
+ * the shared buffer from {@link createNoiseBuffer}.
8
+ */
9
+ export declare function realizeSynthPatch(ctx: BaseAudioContext, out: AudioNode, noiseBuf: AudioBuffer, patch: SynthPatch): void;
@@ -0,0 +1,57 @@
1
+ /** Build the shared 1-second mono white-noise buffer every noise voice samples from. */
2
+ export function createNoiseBuffer(ctx) {
3
+ const len = Math.floor(ctx.sampleRate);
4
+ const buf = ctx.createBuffer(1, len, ctx.sampleRate);
5
+ const data = buf.getChannelData(0);
6
+ for (let i = 0; i < len; i += 1)
7
+ data[i] = Math.random() * 2 - 1;
8
+ return buf;
9
+ }
10
+ function realizeTone(ctx, out, voice, cueGain, at) {
11
+ const t = at + (voice.delay ?? 0);
12
+ const peak = (voice.gain ?? 1) * cueGain;
13
+ const attack = voice.attack ?? 0.012;
14
+ const osc = ctx.createOscillator();
15
+ osc.type = voice.wave ?? "sine";
16
+ osc.frequency.setValueAtTime(voice.freq, t);
17
+ if (voice.slideTo !== undefined)
18
+ osc.frequency.exponentialRampToValueAtTime(Math.max(1, voice.slideTo), t + voice.duration);
19
+ const g = ctx.createGain();
20
+ g.gain.setValueAtTime(0, t);
21
+ g.gain.linearRampToValueAtTime(peak, t + attack);
22
+ g.gain.exponentialRampToValueAtTime(0.001, t + voice.duration);
23
+ osc.connect(g).connect(out);
24
+ osc.start(t);
25
+ osc.stop(t + voice.duration + 0.05);
26
+ }
27
+ function realizeNoise(ctx, out, noiseBuf, voice, cueGain, at) {
28
+ const t = at + (voice.delay ?? 0);
29
+ const peak = (voice.gain ?? 1) * cueGain;
30
+ const decay = voice.decay ?? 0.9;
31
+ const src = ctx.createBufferSource();
32
+ src.buffer = noiseBuf;
33
+ src.playbackRate.value = 0.8 + Math.random() * 0.4;
34
+ const filter = ctx.createBiquadFilter();
35
+ filter.type = voice.filterType ?? "lowpass";
36
+ filter.frequency.value = voice.filterFreq;
37
+ const g = ctx.createGain();
38
+ g.gain.setValueAtTime(peak, t);
39
+ g.gain.exponentialRampToValueAtTime(0.001, t + voice.duration * decay);
40
+ src.connect(filter).connect(g).connect(out);
41
+ src.start(t, Math.random() * 0.5, voice.duration);
42
+ }
43
+ /**
44
+ * Realise a procedural cue on Web Audio: every voice is scheduled at
45
+ * `ctx.currentTime + delay` into `out`, summed into one one-shot. `noiseBuf` is
46
+ * the shared buffer from {@link createNoiseBuffer}.
47
+ */
48
+ export function realizeSynthPatch(ctx, out, noiseBuf, patch) {
49
+ const cueGain = patch.gain ?? 1;
50
+ const at = ctx.currentTime;
51
+ for (const voice of patch.voices) {
52
+ if (voice.kind === "tone")
53
+ realizeTone(ctx, out, voice, cueGain, at);
54
+ else
55
+ realizeNoise(ctx, out, noiseBuf, voice, cueGain, at);
56
+ }
57
+ }
@@ -1,29 +1,10 @@
1
- import type { Object3D } from "three";
2
- import { Behaviour, type BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
3
- /**
4
- * A core `Behaviour` bound to a three.js object. `onBeforeRender`/`onAfterRender`
5
- * ride the object's own three.js render callbacks, so they only fire for renderable
6
- * objects (Mesh, Line, Points, Sprite) that are visible and in frustum — attach to
7
- * the mesh itself, not a parent Group, when you need them.
8
- */
9
- export declare class Object3DBehaviour extends Behaviour {
10
- /** @internal */ _object: Object3D | null;
11
- /** The bound three.js object; available from `onAwake` onward. */
12
- get object(): Object3D;
13
- onBeforeRender(): void;
14
- onAfterRender(): void;
15
- }
16
- /**
17
- * Binds `behaviour` to `object` and attaches it to `world` under `nodeId`
18
- * (default: the object's uuid). Render hooks are chained onto the object's
19
- * existing `onBeforeRender`/`onAfterRender` only when actually overridden, and
20
- * are gated on the behaviour being active; the update lifecycle still flows
21
- * through `world.update`.
22
- */
23
- export declare function attachObject3D<T extends Object3DBehaviour>(world: BehaviourWorld, object: Object3D, behaviour: T, nodeId?: string): T;
1
+ import type { BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
2
+ export { createBehaviourWorldDriver } from "./behaviourDriver.js";
3
+ export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
24
4
  /**
25
5
  * Bootstraps `world` on mount and dispatches `world.update` every frame at
26
- * simulation priority. `scaleDt` maps the raw frame delta to the dt behaviours
6
+ * simulation priority. Stops driving updates on unmount so remount does not
7
+ * double-step. `scaleDt` maps the raw frame delta to the dt behaviours
27
8
  * receive — pass the game clock's scaling to keep behaviours on game time, or
28
9
  * omit for real-time seconds. Games driving `world.update` themselves from
29
10
  * `loop.onTick` should not also render this.
package/dist/behaviour.js CHANGED
@@ -1,63 +1,27 @@
1
1
  import { useFrame } from "@react-three/fiber";
2
- import { useEffect } from "react";
3
- import { Behaviour } from "@jgengine/core/behaviour/behaviour";
4
- /**
5
- * A core `Behaviour` bound to a three.js object. `onBeforeRender`/`onAfterRender`
6
- * ride the object's own three.js render callbacks, so they only fire for renderable
7
- * objects (Mesh, Line, Points, Sprite) that are visible and in frustum — attach to
8
- * the mesh itself, not a parent Group, when you need them.
9
- */
10
- export class Object3DBehaviour extends Behaviour {
11
- /** @internal */ _object = null;
12
- /** The bound three.js object; available from `onAwake` onward. */
13
- get object() {
14
- if (this._object === null)
15
- throw new Error("behaviour is not bound to an Object3D");
16
- return this._object;
17
- }
18
- onBeforeRender() { }
19
- onAfterRender() { }
20
- }
21
- /**
22
- * Binds `behaviour` to `object` and attaches it to `world` under `nodeId`
23
- * (default: the object's uuid). Render hooks are chained onto the object's
24
- * existing `onBeforeRender`/`onAfterRender` only when actually overridden, and
25
- * are gated on the behaviour being active; the update lifecycle still flows
26
- * through `world.update`.
27
- */
28
- export function attachObject3D(world, object, behaviour, nodeId = object.uuid) {
29
- behaviour._object = object;
30
- world.attach(nodeId, behaviour);
31
- if (behaviour.onBeforeRender !== Object3DBehaviour.prototype.onBeforeRender) {
32
- const previous = object.onBeforeRender;
33
- object.onBeforeRender = function chainedBeforeRender(...args) {
34
- previous.apply(this, args);
35
- if (behaviour.isActive)
36
- behaviour.onBeforeRender();
37
- };
38
- }
39
- if (behaviour.onAfterRender !== Object3DBehaviour.prototype.onAfterRender) {
40
- const previous = object.onAfterRender;
41
- object.onAfterRender = function chainedAfterRender(...args) {
42
- previous.apply(this, args);
43
- if (behaviour.isActive)
44
- behaviour.onAfterRender();
45
- };
46
- }
47
- return behaviour;
48
- }
2
+ import { useEffect, useRef } from "react";
3
+ import { createBehaviourWorldDriver } from "./behaviourDriver.js";
4
+ export { createBehaviourWorldDriver } from "./behaviourDriver.js";
5
+ export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
49
6
  /**
50
7
  * Bootstraps `world` on mount and dispatches `world.update` every frame at
51
- * simulation priority. `scaleDt` maps the raw frame delta to the dt behaviours
8
+ * simulation priority. Stops driving updates on unmount so remount does not
9
+ * double-step. `scaleDt` maps the raw frame delta to the dt behaviours
52
10
  * receive — pass the game clock's scaling to keep behaviours on game time, or
53
11
  * omit for real-time seconds. Games driving `world.update` themselves from
54
12
  * `loop.onTick` should not also render this.
55
13
  */
56
14
  export function useBehaviourWorld(world, scaleDt) {
15
+ const driverRef = useRef(createBehaviourWorldDriver(world));
57
16
  useEffect(() => {
58
- world.start();
17
+ const driver = createBehaviourWorldDriver(world);
18
+ driverRef.current = driver;
19
+ driver.start();
20
+ return () => {
21
+ driver.stop();
22
+ };
59
23
  }, [world]);
60
24
  useFrame((_state, rawDt) => {
61
- world.update(scaleDt === undefined ? rawDt : scaleDt(rawDt));
25
+ driverRef.current.step(scaleDt === undefined ? rawDt : scaleDt(rawDt));
62
26
  });
63
27
  }
@@ -0,0 +1,9 @@
1
+ import type { Object3D } from "three";
2
+ import { Behaviour, type BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
3
+ export declare class Object3DBehaviour extends Behaviour {
4
+ /** @internal */ _object: Object3D | null;
5
+ get object(): Object3D;
6
+ onBeforeRender(): void;
7
+ onAfterRender(): void;
8
+ }
9
+ export declare function attachObject3D<T extends Object3DBehaviour>(world: BehaviourWorld, object: Object3D, behaviour: T, nodeId?: string): T;
@@ -0,0 +1,32 @@
1
+ import { Behaviour } from "@jgengine/core/behaviour/behaviour";
2
+ export class Object3DBehaviour extends Behaviour {
3
+ /** @internal */ _object = null;
4
+ get object() {
5
+ if (this._object === null)
6
+ throw new Error("behaviour is not bound to an Object3D");
7
+ return this._object;
8
+ }
9
+ onBeforeRender() { }
10
+ onAfterRender() { }
11
+ }
12
+ export function attachObject3D(world, object, behaviour, nodeId = object.uuid) {
13
+ behaviour._object = object;
14
+ world.attach(nodeId, behaviour);
15
+ if (behaviour.onBeforeRender !== Object3DBehaviour.prototype.onBeforeRender) {
16
+ const previous = object.onBeforeRender;
17
+ object.onBeforeRender = function chainedBeforeRender(...args) {
18
+ previous.apply(this, args);
19
+ if (behaviour.isActive)
20
+ behaviour.onBeforeRender();
21
+ };
22
+ }
23
+ if (behaviour.onAfterRender !== Object3DBehaviour.prototype.onAfterRender) {
24
+ const previous = object.onAfterRender;
25
+ object.onAfterRender = function chainedAfterRender(...args) {
26
+ previous.apply(this, args);
27
+ if (behaviour.isActive)
28
+ behaviour.onAfterRender();
29
+ };
30
+ }
31
+ return behaviour;
32
+ }
@@ -0,0 +1,7 @@
1
+ import type { BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
2
+ export declare function createBehaviourWorldDriver(world: BehaviourWorld): {
3
+ start(): void;
4
+ stop(): void;
5
+ isRunning(): boolean;
6
+ step(dt: number): void;
7
+ };
@@ -0,0 +1,21 @@
1
+ export function createBehaviourWorldDriver(world) {
2
+ let running = false;
3
+ return {
4
+ start() {
5
+ if (!world.started())
6
+ world.start();
7
+ running = true;
8
+ },
9
+ stop() {
10
+ running = false;
11
+ },
12
+ isRunning() {
13
+ return running;
14
+ },
15
+ step(dt) {
16
+ if (!running)
17
+ return;
18
+ world.update(dt);
19
+ },
20
+ };
21
+ }
@@ -22,7 +22,7 @@ export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerC
22
22
  const followEntityId = directed.followEntityId;
23
23
  const rig = (() => {
24
24
  if (directed.cinematic !== undefined) {
25
- return (_jsx(CinematicRig, { yawRef: yawRef, pitchRef: pitchRef, config: { ...config, cinematic: directed.cinematic }, followEntityId: followEntityId }));
25
+ return (_jsx(CinematicRig, { yawRef: yawRef, pitchRef: pitchRef, config: { ...config, cinematic: directed.cinematic }, followEntityId: followEntityId, absoluteFov: true }));
26
26
  }
27
27
  const kind = resolveRigKind(config);
28
28
  switch (kind) {
@@ -1,5 +1,8 @@
1
1
  import { type MutableRefObject } from "react";
2
+ import * as THREE from "three";
2
3
  import type { FirstPersonCameraConfig } from "@jgengine/core/game/playableGame";
4
+ /** World position of the first-person weapon muzzle, or false when no viewmodel is mounted. */
5
+ export declare function readFirstPersonMuzzle(target: THREE.Vector3): boolean;
3
6
  export interface GameFirstPersonCameraProps {
4
7
  yawRef: MutableRefObject<number>;
5
8
  pitchRef: MutableRefObject<number>;