@aura3d/physics 1.0.6

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 (147) hide show
  1. package/README.md +26 -0
  2. package/dist/CharacterController.d.ts +52 -0
  3. package/dist/CharacterController.d.ts.map +1 -0
  4. package/dist/CharacterController.js +146 -0
  5. package/dist/CharacterController.js.map +1 -0
  6. package/dist/ClothFixtures.d.ts +87 -0
  7. package/dist/ClothFixtures.d.ts.map +1 -0
  8. package/dist/ClothFixtures.js +264 -0
  9. package/dist/ClothFixtures.js.map +1 -0
  10. package/dist/Collider.d.ts +27 -0
  11. package/dist/Collider.d.ts.map +1 -0
  12. package/dist/Collider.js +45 -0
  13. package/dist/Collider.js.map +1 -0
  14. package/dist/CollisionEvents.d.ts +26 -0
  15. package/dist/CollisionEvents.d.ts.map +1 -0
  16. package/dist/CollisionEvents.js +46 -0
  17. package/dist/CollisionEvents.js.map +1 -0
  18. package/dist/CollisionVolumes.d.ts +57 -0
  19. package/dist/CollisionVolumes.d.ts.map +1 -0
  20. package/dist/CollisionVolumes.js +159 -0
  21. package/dist/CollisionVolumes.js.map +1 -0
  22. package/dist/Constraint.d.ts +31 -0
  23. package/dist/Constraint.d.ts.map +1 -0
  24. package/dist/Constraint.js +103 -0
  25. package/dist/Constraint.js.map +1 -0
  26. package/dist/Constraints.d.ts +2 -0
  27. package/dist/Constraints.d.ts.map +1 -0
  28. package/dist/Constraints.js +2 -0
  29. package/dist/Constraints.js.map +1 -0
  30. package/dist/Crowd.d.ts +64 -0
  31. package/dist/Crowd.d.ts.map +1 -0
  32. package/dist/Crowd.js +209 -0
  33. package/dist/Crowd.js.map +1 -0
  34. package/dist/ECSPhysicsBridge.d.ts +17 -0
  35. package/dist/ECSPhysicsBridge.d.ts.map +1 -0
  36. package/dist/ECSPhysicsBridge.js +37 -0
  37. package/dist/ECSPhysicsBridge.js.map +1 -0
  38. package/dist/FireSmokeFixtures.d.ts +103 -0
  39. package/dist/FireSmokeFixtures.d.ts.map +1 -0
  40. package/dist/FireSmokeFixtures.js +271 -0
  41. package/dist/FireSmokeFixtures.js.map +1 -0
  42. package/dist/FluidFixtures.d.ts +75 -0
  43. package/dist/FluidFixtures.d.ts.map +1 -0
  44. package/dist/FluidFixtures.js +219 -0
  45. package/dist/FluidFixtures.js.map +1 -0
  46. package/dist/FractureFixtures.d.ts +68 -0
  47. package/dist/FractureFixtures.d.ts.map +1 -0
  48. package/dist/FractureFixtures.js +199 -0
  49. package/dist/FractureFixtures.js.map +1 -0
  50. package/dist/HitboxWorld.d.ts +250 -0
  51. package/dist/HitboxWorld.d.ts.map +1 -0
  52. package/dist/HitboxWorld.js +771 -0
  53. package/dist/HitboxWorld.js.map +1 -0
  54. package/dist/KinematicBody.d.ts +157 -0
  55. package/dist/KinematicBody.d.ts.map +1 -0
  56. package/dist/KinematicBody.js +405 -0
  57. package/dist/KinematicBody.js.map +1 -0
  58. package/dist/KinematicWorld.d.ts +58 -0
  59. package/dist/KinematicWorld.d.ts.map +1 -0
  60. package/dist/KinematicWorld.js +246 -0
  61. package/dist/KinematicWorld.js.map +1 -0
  62. package/dist/Navigation.d.ts +67 -0
  63. package/dist/Navigation.d.ts.map +1 -0
  64. package/dist/Navigation.js +261 -0
  65. package/dist/Navigation.js.map +1 -0
  66. package/dist/PhysicsDebugDraw.d.ts +11 -0
  67. package/dist/PhysicsDebugDraw.d.ts.map +1 -0
  68. package/dist/PhysicsDebugDraw.js +64 -0
  69. package/dist/PhysicsDebugDraw.js.map +1 -0
  70. package/dist/PhysicsSandboxFixtures.d.ts +53 -0
  71. package/dist/PhysicsSandboxFixtures.d.ts.map +1 -0
  72. package/dist/PhysicsSandboxFixtures.js +280 -0
  73. package/dist/PhysicsSandboxFixtures.js.map +1 -0
  74. package/dist/PhysicsStepper.d.ts +15 -0
  75. package/dist/PhysicsStepper.d.ts.map +1 -0
  76. package/dist/PhysicsStepper.js +37 -0
  77. package/dist/PhysicsStepper.js.map +1 -0
  78. package/dist/PhysicsWorld.d.ts +101 -0
  79. package/dist/PhysicsWorld.d.ts.map +1 -0
  80. package/dist/PhysicsWorld.js +868 -0
  81. package/dist/PhysicsWorld.js.map +1 -0
  82. package/dist/PlatformerFixtures.d.ts +70 -0
  83. package/dist/PlatformerFixtures.d.ts.map +1 -0
  84. package/dist/PlatformerFixtures.js +120 -0
  85. package/dist/PlatformerFixtures.js.map +1 -0
  86. package/dist/Raycast.d.ts +23 -0
  87. package/dist/Raycast.d.ts.map +1 -0
  88. package/dist/Raycast.js +271 -0
  89. package/dist/Raycast.js.map +1 -0
  90. package/dist/RigidBody.d.ts +69 -0
  91. package/dist/RigidBody.d.ts.map +1 -0
  92. package/dist/RigidBody.js +255 -0
  93. package/dist/RigidBody.js.map +1 -0
  94. package/dist/ScenePhysicsBridge.d.ts +42 -0
  95. package/dist/ScenePhysicsBridge.d.ts.map +1 -0
  96. package/dist/ScenePhysicsBridge.js +168 -0
  97. package/dist/ScenePhysicsBridge.js.map +1 -0
  98. package/dist/Shape.d.ts +50 -0
  99. package/dist/Shape.d.ts.map +1 -0
  100. package/dist/Shape.js +128 -0
  101. package/dist/Shape.js.map +1 -0
  102. package/dist/SoftBodyFixtures.d.ts +70 -0
  103. package/dist/SoftBodyFixtures.d.ts.map +1 -0
  104. package/dist/SoftBodyFixtures.js +272 -0
  105. package/dist/SoftBodyFixtures.js.map +1 -0
  106. package/dist/Steering.d.ts +161 -0
  107. package/dist/Steering.d.ts.map +1 -0
  108. package/dist/Steering.js +347 -0
  109. package/dist/Steering.js.map +1 -0
  110. package/dist/VehicleDynamics.d.ts +152 -0
  111. package/dist/VehicleDynamics.d.ts.map +1 -0
  112. package/dist/VehicleDynamics.js +335 -0
  113. package/dist/VehicleDynamics.js.map +1 -0
  114. package/dist/index.d.ts +29 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +29 -0
  117. package/dist/index.js.map +1 -0
  118. package/package.json +17 -0
  119. package/src/CharacterController.ts +184 -0
  120. package/src/ClothFixtures.ts +359 -0
  121. package/src/Collider.ts +67 -0
  122. package/src/CollisionEvents.ts +70 -0
  123. package/src/CollisionVolumes.ts +218 -0
  124. package/src/Constraint.ts +120 -0
  125. package/src/Constraints.ts +1 -0
  126. package/src/Crowd.ts +283 -0
  127. package/src/ECSPhysicsBridge.ts +53 -0
  128. package/src/FireSmokeFixtures.ts +382 -0
  129. package/src/FluidFixtures.ts +301 -0
  130. package/src/FractureFixtures.ts +277 -0
  131. package/src/HitboxWorld.ts +1179 -0
  132. package/src/KinematicBody.ts +553 -0
  133. package/src/KinematicWorld.ts +310 -0
  134. package/src/Navigation.ts +321 -0
  135. package/src/PhysicsDebugDraw.ts +71 -0
  136. package/src/PhysicsSandboxFixtures.ts +394 -0
  137. package/src/PhysicsStepper.ts +47 -0
  138. package/src/PhysicsWorld.ts +986 -0
  139. package/src/PlatformerFixtures.ts +205 -0
  140. package/src/Raycast.ts +315 -0
  141. package/src/RigidBody.ts +311 -0
  142. package/src/ScenePhysicsBridge.ts +207 -0
  143. package/src/Shape.ts +182 -0
  144. package/src/SoftBodyFixtures.ts +363 -0
  145. package/src/Steering.ts +531 -0
  146. package/src/VehicleDynamics.ts +524 -0
  147. package/src/index.ts +28 -0
@@ -0,0 +1,382 @@
1
+ export interface FireSmokeFixtureOptions {
2
+ readonly seed?: number;
3
+ readonly gridResolution?: readonly [number, number, number];
4
+ readonly elapsedSeconds?: number;
5
+ readonly sourceCount?: number;
6
+ }
7
+
8
+ export interface FireSmokeFixture {
9
+ readonly id: "external-parity-old-branch-fire-smoke-fixture";
10
+ readonly source: "origin-master-fire-smoke-volume-adapted";
11
+ readonly sourceFiles: readonly [
12
+ "origin/master:src/simulation/fire/FireSimulation.ts",
13
+ "origin/master:src/simulation/fire/FireParticleSystem.ts",
14
+ "origin/master:src/simulation/fire/TemperatureField.ts",
15
+ "origin/master:src/simulation/fire/TurbulenceSimulation.ts",
16
+ "origin/master:src/simulation/smoke/SmokeSimulation.ts",
17
+ "origin/master:src/simulation/smoke/SmokeGrid.ts",
18
+ "origin/master:src/simulation/smoke/SmokeRenderer.ts"
19
+ ];
20
+ readonly config: {
21
+ readonly solver: "bounded-fire-smoke-telemetry";
22
+ readonly ambientTemperature: number;
23
+ readonly combustionTemperature: number;
24
+ readonly ignitionTemperature: number;
25
+ readonly fuelConsumptionRate: number;
26
+ readonly buoyancyStrength: number;
27
+ readonly turbulenceStrength: number;
28
+ readonly smokeGenerationRate: number;
29
+ readonly coolingRate: number;
30
+ readonly diffusionRate: number;
31
+ readonly smokePressureIterations: number;
32
+ readonly vorticityStrength: number;
33
+ readonly smokeDissipationRate: number;
34
+ readonly particleEmissionRate: number;
35
+ readonly particleLifetime: number;
36
+ readonly maxParticles: number;
37
+ readonly rayMarchMaxSteps: number;
38
+ readonly rayMarchStepSize: number;
39
+ };
40
+ readonly grid: {
41
+ readonly resolution: readonly [number, number, number];
42
+ readonly cellCount: number;
43
+ readonly sourceCount: number;
44
+ readonly hotCellSamples: readonly FireSmokeHotCellSample[];
45
+ };
46
+ readonly fire: {
47
+ readonly activeFuelCells: number;
48
+ readonly burningCells: number;
49
+ readonly averageTemperature: number;
50
+ readonly maxTemperature: number;
51
+ readonly fuelConsumed: number;
52
+ readonly smokeGenerated: number;
53
+ readonly buoyancyImpulse: number;
54
+ readonly turbulenceEnergy: number;
55
+ readonly coolingLoss: number;
56
+ readonly diffusionEstimate: number;
57
+ };
58
+ readonly particles: {
59
+ readonly emittedParticles: number;
60
+ readonly activeParticles: number;
61
+ readonly emberParticles: number;
62
+ readonly averageLifetime: number;
63
+ readonly averageSize: number;
64
+ readonly maxTemperature: number;
65
+ readonly uploadBytes: number;
66
+ };
67
+ readonly smoke: {
68
+ readonly densityCells: number;
69
+ readonly totalDensity: number;
70
+ readonly maxDensity: number;
71
+ readonly averageVelocityMagnitude: number;
72
+ readonly divergenceBeforeProjection: number;
73
+ readonly divergenceAfterProjection: number;
74
+ readonly pressureIterations: number;
75
+ readonly vorticityCells: number;
76
+ readonly buoyancyForceEstimate: number;
77
+ readonly dissipationLoss: number;
78
+ };
79
+ readonly volumeRendering: {
80
+ readonly rayMarchSteps: number;
81
+ readonly stepSize: number;
82
+ readonly sampledDensity: number;
83
+ readonly transmittance: number;
84
+ readonly scattering: number;
85
+ readonly shadowSamples: number;
86
+ readonly shadowTransmittance: number;
87
+ readonly alpha: number;
88
+ readonly volumetricRendererClaimed: false;
89
+ readonly productionLightingClaimed: false;
90
+ };
91
+ readonly blockedClaims: readonly string[];
92
+ readonly hash: string;
93
+ readonly claimBoundary: string;
94
+ }
95
+
96
+ export interface FireSmokeHotCellSample {
97
+ readonly index: number;
98
+ readonly cell: readonly [number, number, number];
99
+ readonly temperature: number;
100
+ readonly fuel: number;
101
+ readonly smokeDensity: number;
102
+ readonly velocity: readonly [number, number, number];
103
+ }
104
+
105
+ const sourceFiles = [
106
+ "origin/master:src/simulation/fire/FireSimulation.ts",
107
+ "origin/master:src/simulation/fire/FireParticleSystem.ts",
108
+ "origin/master:src/simulation/fire/TemperatureField.ts",
109
+ "origin/master:src/simulation/fire/TurbulenceSimulation.ts",
110
+ "origin/master:src/simulation/smoke/SmokeSimulation.ts",
111
+ "origin/master:src/simulation/smoke/SmokeGrid.ts",
112
+ "origin/master:src/simulation/smoke/SmokeRenderer.ts"
113
+ ] as const;
114
+
115
+ const blockedClaims = [
116
+ "production combustion solver parity",
117
+ "GPU fire simulation",
118
+ "production incompressible smoke solver parity",
119
+ "volumetric ray-marched renderer parity",
120
+ "fluid/fire coupling parity",
121
+ "Unity VFX Graph fire/smoke parity",
122
+ "Unreal Niagara fire/smoke parity"
123
+ ] as const;
124
+
125
+ export function sampleFireSmokeFixture(options: FireSmokeFixtureOptions = {}): FireSmokeFixture {
126
+ const seed = integer(options.seed ?? 0xf17e, "seed");
127
+ const elapsedSeconds = finiteNonNegative(options.elapsedSeconds ?? 0.5, "elapsedSeconds");
128
+ const gridResolution = options.gridResolution ?? [8, 6, 8] as const;
129
+ const gridX = integerInRange(gridResolution[0], "gridResolution[0]", 4, 18);
130
+ const gridY = integerInRange(gridResolution[1], "gridResolution[1]", 4, 18);
131
+ const gridZ = integerInRange(gridResolution[2], "gridResolution[2]", 4, 18);
132
+ const sourceCount = integerInRange(options.sourceCount ?? 3, "sourceCount", 1, 8);
133
+ const ambientTemperature = 293;
134
+ const combustionTemperature = 1200;
135
+ const ignitionTemperature = 500;
136
+ const fuelConsumptionRate = 0.5;
137
+ const buoyancyStrength = 2;
138
+ const turbulenceStrength = 1.5;
139
+ const smokeGenerationRate = 0.8;
140
+ const coolingRate = 0.1;
141
+ const diffusionRate = 2.2e-5;
142
+ const smokePressureIterations = 40;
143
+ const vorticityStrength = 0.5;
144
+ const smokeDissipationRate = 0.01;
145
+ const particleEmissionRate = 500;
146
+ const particleLifetime = 2;
147
+ const maxParticles = 10_000;
148
+ const rayMarchMaxSteps = 128;
149
+ const rayMarchStepSize = 0.1;
150
+
151
+ const sources = Array.from({ length: sourceCount }, (_, index) => ({
152
+ x: (index + 1) / (sourceCount + 1) * (gridX - 1),
153
+ y: 0.65 + hash01(seed, index + 37) * 0.4,
154
+ z: (0.25 + hash01(seed, index + 73) * 0.5) * (gridZ - 1),
155
+ radius: 1.45 + hash01(seed, index + 101) * 0.32
156
+ }));
157
+ const hotCellSamples: FireSmokeHotCellSample[] = [];
158
+ let activeFuelCells = 0;
159
+ let burningCells = 0;
160
+ let temperatureTotal = 0;
161
+ let maxTemperature = ambientTemperature;
162
+ let fuelConsumed = 0;
163
+ let smokeGenerated = 0;
164
+ let buoyancyImpulse = 0;
165
+ let turbulenceEnergy = 0;
166
+ let coolingLoss = 0;
167
+ let diffusionEstimate = 0;
168
+ let densityCells = 0;
169
+ let totalDensity = 0;
170
+ let maxDensity = 0;
171
+ let velocityMagnitudeTotal = 0;
172
+ let divergenceBeforeProjection = 0;
173
+ let vorticityCells = 0;
174
+
175
+ for (let z = 0; z < gridZ; z += 1) {
176
+ for (let y = 0; y < gridY; y += 1) {
177
+ for (let x = 0; x < gridX; x += 1) {
178
+ const index = x + gridX * (y + gridY * z);
179
+ const normalizedHeight = y / Math.max(1, gridY - 1);
180
+ let sourceHeat = 0;
181
+ for (const source of sources) {
182
+ const distance = Math.hypot(x - source.x, y - source.y, z - source.z);
183
+ sourceHeat += Math.max(0, 1 - distance / source.radius);
184
+ }
185
+ const flicker = 0.85 + hash01(seed, index) * 0.16 + Math.sin(elapsedSeconds * 4.2 + index * 0.19) * 0.04;
186
+ const plume = Math.max(0, sourceHeat * (1 - normalizedHeight * 0.42) * flicker);
187
+ const fuel = Math.max(0, 1 - normalizedHeight * 0.28 - elapsedSeconds * fuelConsumptionRate * plume * 0.08);
188
+ const temperature = ambientTemperature + plume * (combustionTemperature - ambientTemperature);
189
+ const smokeDensity = Math.max(0, plume * smokeGenerationRate * (0.45 + normalizedHeight * 0.85) * (1 - smokeDissipationRate * elapsedSeconds));
190
+ const turbulence = Math.abs(Math.sin((x + seed * 0.001) * 0.7 + z * 0.43 + elapsedSeconds * 1.7)) * turbulenceStrength * plume;
191
+ const velocityY = buoyancyStrength * Math.max(0, (temperature - ambientTemperature) / (combustionTemperature - ambientTemperature));
192
+ const velocity = [
193
+ round4((hash01(seed, index + 11) - 0.5) * turbulence),
194
+ round4(velocityY + turbulence * 0.18),
195
+ round4((hash01(seed, index + 23) - 0.5) * turbulence)
196
+ ] as const;
197
+
198
+ temperatureTotal += temperature;
199
+ maxTemperature = Math.max(maxTemperature, temperature);
200
+ if (fuel > 0.02) activeFuelCells += 1;
201
+ if (temperature >= ignitionTemperature && fuel > 0.02) {
202
+ burningCells += 1;
203
+ fuelConsumed += fuelConsumptionRate * plume * 0.016;
204
+ buoyancyImpulse += velocityY;
205
+ turbulenceEnergy += turbulence * turbulence;
206
+ if (hotCellSamples.length < 6) {
207
+ hotCellSamples.push({
208
+ index,
209
+ cell: [x, y, z],
210
+ temperature: round4(temperature),
211
+ fuel: round4(fuel),
212
+ smokeDensity: round4(smokeDensity),
213
+ velocity
214
+ });
215
+ }
216
+ }
217
+ if (smokeDensity > 0.005) {
218
+ densityCells += 1;
219
+ totalDensity += smokeDensity;
220
+ maxDensity = Math.max(maxDensity, smokeDensity);
221
+ velocityMagnitudeTotal += Math.hypot(velocity[0], velocity[1], velocity[2]);
222
+ }
223
+ if (turbulence > 0.05) vorticityCells += 1;
224
+ smokeGenerated += smokeDensity;
225
+ coolingLoss += Math.max(0, temperature - ambientTemperature) * coolingRate * 0.001;
226
+ diffusionEstimate += Math.max(0, temperature - ambientTemperature) * diffusionRate;
227
+ divergenceBeforeProjection += Math.abs(velocity[0]) * 0.16 + Math.abs(velocity[1]) * 0.05 + Math.abs(velocity[2]) * 0.16;
228
+ }
229
+ }
230
+ }
231
+
232
+ const cellCount = gridX * gridY * gridZ;
233
+ const averageTemperature = temperatureTotal / cellCount;
234
+ const emittedParticles = Math.min(maxParticles, Math.max(1, Math.round(burningCells * particleEmissionRate / 60)));
235
+ const activeParticles = Math.min(maxParticles, Math.max(emittedParticles, Math.round(emittedParticles * particleLifetime * 0.72)));
236
+ const emberParticles = Math.max(1, Math.round(activeParticles * 0.18));
237
+ const averageLifetime = Math.max(0.05, particleLifetime * (0.42 + Math.min(0.35, burningCells / cellCount)));
238
+ const averageSize = 0.05 + Math.min(0.15, (maxTemperature - ignitionTemperature) / combustionTemperature * 0.12);
239
+ const divergenceAfterProjection = divergenceBeforeProjection / (1 + smokePressureIterations * 0.38);
240
+ const sampledDensity = totalDensity / Math.max(1, densityCells);
241
+ const transmittance = Math.exp(-sampledDensity * rayMarchMaxSteps * rayMarchStepSize * 0.18);
242
+ const shadowTransmittance = Math.exp(-sampledDensity * 8 * 0.5);
243
+ const alpha = 1 - transmittance;
244
+
245
+ return {
246
+ id: "external-parity-old-branch-fire-smoke-fixture",
247
+ source: "origin-master-fire-smoke-volume-adapted",
248
+ sourceFiles,
249
+ config: {
250
+ solver: "bounded-fire-smoke-telemetry",
251
+ ambientTemperature,
252
+ combustionTemperature,
253
+ ignitionTemperature,
254
+ fuelConsumptionRate,
255
+ buoyancyStrength,
256
+ turbulenceStrength,
257
+ smokeGenerationRate,
258
+ coolingRate,
259
+ diffusionRate,
260
+ smokePressureIterations,
261
+ vorticityStrength,
262
+ smokeDissipationRate,
263
+ particleEmissionRate,
264
+ particleLifetime,
265
+ maxParticles,
266
+ rayMarchMaxSteps,
267
+ rayMarchStepSize
268
+ },
269
+ grid: {
270
+ resolution: [gridX, gridY, gridZ],
271
+ cellCount,
272
+ sourceCount,
273
+ hotCellSamples
274
+ },
275
+ fire: {
276
+ activeFuelCells,
277
+ burningCells,
278
+ averageTemperature: round4(averageTemperature),
279
+ maxTemperature: round4(maxTemperature),
280
+ fuelConsumed: round4(fuelConsumed),
281
+ smokeGenerated: round4(smokeGenerated),
282
+ buoyancyImpulse: round4(buoyancyImpulse),
283
+ turbulenceEnergy: round4(turbulenceEnergy),
284
+ coolingLoss: round4(coolingLoss),
285
+ diffusionEstimate: round4(diffusionEstimate)
286
+ },
287
+ particles: {
288
+ emittedParticles,
289
+ activeParticles,
290
+ emberParticles,
291
+ averageLifetime: round4(averageLifetime),
292
+ averageSize: round4(averageSize),
293
+ maxTemperature: round4(maxTemperature),
294
+ uploadBytes: activeParticles * 32
295
+ },
296
+ smoke: {
297
+ densityCells,
298
+ totalDensity: round4(totalDensity),
299
+ maxDensity: round4(maxDensity),
300
+ averageVelocityMagnitude: round4(velocityMagnitudeTotal / Math.max(1, densityCells)),
301
+ divergenceBeforeProjection: round4(divergenceBeforeProjection),
302
+ divergenceAfterProjection: round4(divergenceAfterProjection),
303
+ pressureIterations: smokePressureIterations,
304
+ vorticityCells,
305
+ buoyancyForceEstimate: round4(buoyancyImpulse * 0.12),
306
+ dissipationLoss: round4(totalDensity * smokeDissipationRate)
307
+ },
308
+ volumeRendering: {
309
+ rayMarchSteps: rayMarchMaxSteps,
310
+ stepSize: rayMarchStepSize,
311
+ sampledDensity: round4(sampledDensity),
312
+ transmittance: round4(transmittance),
313
+ scattering: round4(sampledDensity * (1 - shadowTransmittance) * 0.62),
314
+ shadowSamples: 8,
315
+ shadowTransmittance: round4(shadowTransmittance),
316
+ alpha: round4(alpha),
317
+ volumetricRendererClaimed: false,
318
+ productionLightingClaimed: false
319
+ },
320
+ blockedClaims,
321
+ hash: hashNumbers([
322
+ seed,
323
+ elapsedSeconds,
324
+ gridX,
325
+ gridY,
326
+ gridZ,
327
+ sourceCount,
328
+ activeFuelCells,
329
+ burningCells,
330
+ averageTemperature,
331
+ maxTemperature,
332
+ fuelConsumed,
333
+ smokeGenerated,
334
+ activeParticles,
335
+ totalDensity,
336
+ divergenceBeforeProjection,
337
+ divergenceAfterProjection,
338
+ transmittance
339
+ ]),
340
+ claimBoundary: "Deterministic bounded fire/smoke telemetry adapted from old combustion, temperature, turbulence, smoke-grid, particle-emission, and ray-marching concepts. It proves current runtime evidence for hot cells, fuel consumption, smoke density, buoyancy, vorticity, pressure-projection reduction, particle emission, and volume sampling; it does not claim production combustion solver parity, GPU fire/smoke simulation, production volumetric rendering, Unity VFX Graph parity, or Unreal Niagara parity."
341
+ };
342
+ }
343
+
344
+ function hash01(seed: number, index: number): number {
345
+ let value = (seed ^ Math.imul(index + 1, 0x9e3779b9)) >>> 0;
346
+ value ^= value >>> 16;
347
+ value = Math.imul(value, 0x7feb352d) >>> 0;
348
+ value ^= value >>> 15;
349
+ value = Math.imul(value, 0x846ca68b) >>> 0;
350
+ value ^= value >>> 16;
351
+ return value / 0xffffffff;
352
+ }
353
+
354
+ function hashNumbers(values: readonly number[]): string {
355
+ let hash = 0x811c9dc5;
356
+ for (const value of values) {
357
+ const scaled = Math.round(value * 1_000_000);
358
+ hash ^= scaled;
359
+ hash = Math.imul(hash, 0x01000193) >>> 0;
360
+ }
361
+ return hash.toString(16).padStart(8, "0");
362
+ }
363
+
364
+ function round4(value: number): number {
365
+ return Number(value.toFixed(4));
366
+ }
367
+
368
+ function finiteNonNegative(value: number, label: string): number {
369
+ if (!Number.isFinite(value) || value < 0) throw new RangeError(`Fire/smoke fixture ${label} must be finite and non-negative.`);
370
+ return value;
371
+ }
372
+
373
+ function integer(value: number, label: string): number {
374
+ if (!Number.isInteger(value)) throw new RangeError(`Fire/smoke fixture ${label} must be an integer.`);
375
+ return value;
376
+ }
377
+
378
+ function integerInRange(value: number, label: string, min: number, max: number): number {
379
+ const parsed = integer(value, label);
380
+ if (parsed < min || parsed > max) throw new RangeError(`Fire/smoke fixture ${label} must be in the [${min}, ${max}] range.`);
381
+ return parsed;
382
+ }
@@ -0,0 +1,301 @@
1
+ export interface FluidFixtureOptions {
2
+ readonly seed?: number;
3
+ readonly particleGrid?: readonly [number, number, number];
4
+ readonly elapsedSeconds?: number;
5
+ }
6
+
7
+ export interface FluidParticleSample {
8
+ readonly index: number;
9
+ readonly position: readonly [number, number, number];
10
+ readonly velocity: readonly [number, number, number];
11
+ readonly density: number;
12
+ readonly pressure: number;
13
+ readonly neighborCount: number;
14
+ }
15
+
16
+ export interface FluidFixture {
17
+ readonly id: "external-parity-old-branch-fluid-fixture";
18
+ readonly source: "origin-master-sph-mpm-fluid-adapted";
19
+ readonly sourceFiles: readonly [
20
+ "origin/master:src/simulation/sph/SPHFluidFramework.ts",
21
+ "origin/master:src/simulation/sph/FluidRenderer.ts",
22
+ "origin/master:src/simulation/mpm/MPMFluidSimulation.ts",
23
+ "origin/master:src/simulation/mpm/ParticleBuffer.ts"
24
+ ];
25
+ readonly config: {
26
+ readonly solver: "bounded-sph-pcisph-dfsph-telemetry";
27
+ readonly restDensity: number;
28
+ readonly particleMass: number;
29
+ readonly smoothingRadius: number;
30
+ readonly viscosity: number;
31
+ readonly surfaceTension: number;
32
+ readonly stiffness: number;
33
+ readonly dt: number;
34
+ readonly pcisphIterations: number;
35
+ readonly dfsphIterations: number;
36
+ };
37
+ readonly sph: {
38
+ readonly particleCount: number;
39
+ readonly capacity: number;
40
+ readonly averageDensity: number;
41
+ readonly maxDensity: number;
42
+ readonly averagePressure: number;
43
+ readonly maxPressure: number;
44
+ readonly neighborPairs: number;
45
+ readonly maxNeighborCount: number;
46
+ readonly viscosityForceEstimate: number;
47
+ readonly surfaceTensionEstimate: number;
48
+ readonly boundaryCollisionCount: number;
49
+ readonly sampleParticles: readonly FluidParticleSample[];
50
+ };
51
+ readonly mpm: {
52
+ readonly gridResolution: readonly [number, number, number];
53
+ readonly activeCells: number;
54
+ readonly particleToGridTransfers: number;
55
+ readonly gridToParticleTransfers: number;
56
+ readonly flipRatio: number;
57
+ readonly deformationGradientSamples: number;
58
+ readonly plasticityEvents: number;
59
+ readonly boundaryClamps: number;
60
+ };
61
+ readonly rendering: {
62
+ readonly screenWidth: number;
63
+ readonly screenHeight: number;
64
+ readonly particleRadius: number;
65
+ readonly depthPixels: number;
66
+ readonly thicknessPixels: number;
67
+ readonly maxThickness: number;
68
+ readonly smoothingPasses: number;
69
+ readonly refractionClaimed: false;
70
+ readonly subsurfaceScatteringClaimed: false;
71
+ };
72
+ readonly blockedClaims: readonly string[];
73
+ readonly hash: string;
74
+ readonly claimBoundary: string;
75
+ }
76
+
77
+ const sourceFiles = [
78
+ "origin/master:src/simulation/sph/SPHFluidFramework.ts",
79
+ "origin/master:src/simulation/sph/FluidRenderer.ts",
80
+ "origin/master:src/simulation/mpm/MPMFluidSimulation.ts",
81
+ "origin/master:src/simulation/mpm/ParticleBuffer.ts"
82
+ ] as const;
83
+
84
+ const blockedClaims = [
85
+ "production SPH pressure solve parity",
86
+ "PCISPH/DFSPH convergence parity",
87
+ "GPU fluid simulation",
88
+ "screen-space fluid renderer parity",
89
+ "refraction and subsurface fluid rendering",
90
+ "MPM material model parity",
91
+ "Unity fluid tooling parity",
92
+ "Unreal Niagara/fluid parity"
93
+ ] as const;
94
+
95
+ export function sampleFluidFixture(options: FluidFixtureOptions = {}): FluidFixture {
96
+ const seed = integer(options.seed ?? 0xf10d, "seed");
97
+ const elapsedSeconds = finiteNonNegative(options.elapsedSeconds ?? 0.42, "elapsedSeconds");
98
+ const particleGrid = options.particleGrid ?? [4, 3, 3] as const;
99
+ const gridX = integerInRange(particleGrid[0], "particleGrid[0]", 2, 12);
100
+ const gridY = integerInRange(particleGrid[1], "particleGrid[1]", 2, 12);
101
+ const gridZ = integerInRange(particleGrid[2], "particleGrid[2]", 2, 12);
102
+ const restDensity = 1000;
103
+ const particleMass = 0.02;
104
+ const smoothingRadius = 0.24;
105
+ const stiffness = 180;
106
+ const samples: FluidParticleSample[] = [];
107
+ const positions: Array<readonly [number, number, number]> = [];
108
+ const velocities: Array<readonly [number, number, number]> = [];
109
+ let neighborPairs = 0;
110
+ let maxNeighborCount = 0;
111
+ let densityTotal = 0;
112
+ let maxDensity = 0;
113
+ let pressureTotal = 0;
114
+ let maxPressure = 0;
115
+ let viscosityForceEstimate = 0;
116
+ let surfaceTensionEstimate = 0;
117
+ let boundaryCollisionCount = 0;
118
+ let depthPixels = 0;
119
+ let thicknessPixels = 0;
120
+ let maxThickness = 0;
121
+
122
+ for (let z = 0; z < gridZ; z += 1) {
123
+ for (let y = 0; y < gridY; y += 1) {
124
+ for (let x = 0; x < gridX; x += 1) {
125
+ const index = positions.length;
126
+ const jitter = (hash01(seed, index) - 0.5) * 0.018;
127
+ const position = [
128
+ round4((x - (gridX - 1) / 2) * 0.15 + jitter),
129
+ round4(0.1 + y * 0.14 - elapsedSeconds * 0.08 + Math.sin(elapsedSeconds * 2 + x) * 0.012),
130
+ round4((z - (gridZ - 1) / 2) * 0.15 - jitter * 0.5)
131
+ ] as const;
132
+ const velocity = [
133
+ round4(0.12 + hash01(seed, index + 19) * 0.18),
134
+ round4(-0.38 + y * 0.03),
135
+ round4((hash01(seed, index + 31) - 0.5) * 0.18)
136
+ ] as const;
137
+ if (position[1] < -0.05) boundaryCollisionCount += 1;
138
+ positions.push(position);
139
+ velocities.push(velocity);
140
+ }
141
+ }
142
+ }
143
+
144
+ for (let index = 0; index < positions.length; index += 1) {
145
+ const position = positions[index]!;
146
+ const velocity = velocities[index]!;
147
+ let neighborCount = 0;
148
+ let density = restDensity;
149
+ let localVelocityDelta = 0;
150
+ for (let other = 0; other < positions.length; other += 1) {
151
+ if (other === index) continue;
152
+ const otherPosition = positions[other]!;
153
+ const d = distance(position, otherPosition);
154
+ if (d <= smoothingRadius) {
155
+ neighborCount += 1;
156
+ const weight = Math.pow(1 - d / smoothingRadius, 3);
157
+ density += particleMass * 5400 * weight;
158
+ localVelocityDelta += distance(velocity, velocities[other]!);
159
+ }
160
+ }
161
+ neighborPairs += neighborCount;
162
+ maxNeighborCount = Math.max(maxNeighborCount, neighborCount);
163
+ const pressure = Math.max(0, (density - restDensity) * stiffness * 0.001);
164
+ densityTotal += density;
165
+ maxDensity = Math.max(maxDensity, density);
166
+ pressureTotal += pressure;
167
+ maxPressure = Math.max(maxPressure, pressure);
168
+ viscosityForceEstimate += localVelocityDelta * 0.08;
169
+ surfaceTensionEstimate += Math.max(0, maxNeighborCount - neighborCount) * 0.015;
170
+ const projectedArea = Math.max(1, Math.round((neighborCount + 1) * 2.3));
171
+ depthPixels += projectedArea;
172
+ thicknessPixels += projectedArea + neighborCount;
173
+ maxThickness = Math.max(maxThickness, (neighborCount + 1) * 0.035);
174
+ if (samples.length < 6) {
175
+ samples.push({
176
+ index,
177
+ position,
178
+ velocity,
179
+ density: round4(density),
180
+ pressure: round4(pressure),
181
+ neighborCount
182
+ });
183
+ }
184
+ }
185
+ neighborPairs = Math.floor(neighborPairs / 2);
186
+ const particleCount = positions.length;
187
+ const activeCells = new Set(positions.map((position) => `${Math.floor((position[0] + 0.6) / 0.16)}:${Math.floor((position[1] + 0.4) / 0.16)}:${Math.floor((position[2] + 0.6) / 0.16)}`)).size;
188
+
189
+ return {
190
+ id: "external-parity-old-branch-fluid-fixture",
191
+ source: "origin-master-sph-mpm-fluid-adapted",
192
+ sourceFiles,
193
+ config: {
194
+ solver: "bounded-sph-pcisph-dfsph-telemetry",
195
+ restDensity,
196
+ particleMass,
197
+ smoothingRadius,
198
+ viscosity: 0.08,
199
+ surfaceTension: 0.015,
200
+ stiffness,
201
+ dt: 1 / 60,
202
+ pcisphIterations: 3,
203
+ dfsphIterations: 5
204
+ },
205
+ sph: {
206
+ particleCount,
207
+ capacity: 128,
208
+ averageDensity: round4(densityTotal / particleCount),
209
+ maxDensity: round4(maxDensity),
210
+ averagePressure: round4(pressureTotal / particleCount),
211
+ maxPressure: round4(maxPressure),
212
+ neighborPairs,
213
+ maxNeighborCount,
214
+ viscosityForceEstimate: round4(viscosityForceEstimate),
215
+ surfaceTensionEstimate: round4(surfaceTensionEstimate),
216
+ boundaryCollisionCount,
217
+ sampleParticles: samples
218
+ },
219
+ mpm: {
220
+ gridResolution: [12, 8, 12],
221
+ activeCells,
222
+ particleToGridTransfers: particleCount * 8,
223
+ gridToParticleTransfers: particleCount * 8,
224
+ flipRatio: 0.96,
225
+ deformationGradientSamples: particleCount,
226
+ plasticityEvents: Math.max(1, Math.floor(particleCount * 0.08)),
227
+ boundaryClamps: boundaryCollisionCount
228
+ },
229
+ rendering: {
230
+ screenWidth: 320,
231
+ screenHeight: 180,
232
+ particleRadius: 0.055,
233
+ depthPixels,
234
+ thicknessPixels,
235
+ maxThickness: round4(maxThickness),
236
+ smoothingPasses: 2,
237
+ refractionClaimed: false,
238
+ subsurfaceScatteringClaimed: false
239
+ },
240
+ blockedClaims,
241
+ hash: hashNumbers([
242
+ seed,
243
+ elapsedSeconds,
244
+ particleCount,
245
+ neighborPairs,
246
+ maxNeighborCount,
247
+ densityTotal,
248
+ pressureTotal,
249
+ viscosityForceEstimate,
250
+ surfaceTensionEstimate,
251
+ activeCells,
252
+ depthPixels,
253
+ thicknessPixels
254
+ ]),
255
+ claimBoundary: "Deterministic bounded fluid telemetry adapted from old SPH, MPM, particle-buffer, and fluid-renderer concepts. It proves particle-box setup, density/pressure/neighborhood metrics, MPM particle-grid transfer counts, boundary contacts, and screen-space depth/thickness estimates; it does not claim production pressure-solve convergence, GPU fluid simulation, screen-space refraction/subsurface rendering, Unity fluid tooling parity, or Unreal Niagara/fluid parity."
256
+ };
257
+ }
258
+
259
+ function distance(left: readonly [number, number, number], right: readonly [number, number, number]): number {
260
+ return Math.hypot(left[0] - right[0], left[1] - right[1], left[2] - right[2]);
261
+ }
262
+
263
+ function hash01(seed: number, salt: number): number {
264
+ let value = Math.imul(seed + salt * 1013904223, 1664525) + 1013904223;
265
+ value = Math.imul(value ^ (value >>> 13), 1274126177);
266
+ return ((value ^ (value >>> 16)) >>> 0) / 4294967296;
267
+ }
268
+
269
+ function hashNumbers(values: readonly number[]): string {
270
+ let hash = 0x811c9dc5;
271
+ for (const value of values) {
272
+ const scaled = Math.round(value * 10_000);
273
+ hash ^= scaled & 0xff;
274
+ hash = Math.imul(hash, 0x01000193) >>> 0;
275
+ hash ^= (scaled >>> 8) & 0xff;
276
+ hash = Math.imul(hash, 0x01000193) >>> 0;
277
+ hash ^= (scaled >>> 16) & 0xff;
278
+ hash = Math.imul(hash, 0x01000193) >>> 0;
279
+ }
280
+ return hash.toString(16).padStart(8, "0");
281
+ }
282
+
283
+ function round4(value: number): number {
284
+ return Number(value.toFixed(4));
285
+ }
286
+
287
+ function finiteNonNegative(value: number, label: string): number {
288
+ if (!Number.isFinite(value) || value < 0) throw new RangeError(`Fluid fixture ${label} must be finite and non-negative.`);
289
+ return value;
290
+ }
291
+
292
+ function integer(value: number, label: string): number {
293
+ if (!Number.isInteger(value)) throw new RangeError(`Fluid fixture ${label} must be an integer.`);
294
+ return value;
295
+ }
296
+
297
+ function integerInRange(value: number, label: string, min: number, max: number): number {
298
+ const parsed = integer(value, label);
299
+ if (parsed < min || parsed > max) throw new RangeError(`Fluid fixture ${label} must be in the [${min}, ${max}] range.`);
300
+ return parsed;
301
+ }