openvibers/openvibe.games Commits
Games: physics loads as a wasm file in the browser (M6: no 4.4 MB base64 chunk) (#61)
Changes
@@@ -14,6 +14,7 @@"@babylonjs/loaders": "^9.20.0","@babylonjs/materials": "^9.20.0","@babylonjs/procedural-textures": "^9.20.0",++ "@dimforge/rapier3d-deterministic": "0.21.0","@openvibe/casual": "workspace:*","@openvibe/content": "workspace:*","@openvibe/gameplay": "workspace:*",@@@ -23,6 +24,7 @@},"devDependencies": {"babylonjs-gltf2interface": "^9.20.0",-- "vite": "^6.2.5"++ "vite": "^6.2.5",++ "vite-plugin-wasm": "3.6.0"}}@@@ -1,7 +1,20 @@import { defineConfig } from 'vite'++import wasm from 'vite-plugin-wasm'++import { fileURLToPath } from 'node:url'export default defineConfig({++ // Physics (plan M6): the deterministic Rapier build whose wasm is a file of its own, compiled by the browser as it++ // streams in, instead of the compat build's 4.4 MB of base64 inside a JavaScript chunk. Same engine, same results.++ // An absolute path: packages/physics imports the compat name and does not depend on this build itself.++ resolve: {++ alias: {++ '@dimforge/rapier3d-deterministic-compat': fileURLToPath(++ new URL('./node_modules/@dimforge/rapier3d-deterministic/rapier.js', import.meta.url),++ ),++ },++ },plugins: [++ wasm(),{// Mirror the production server's pretty-URL aliases so the OAuth// callback's redirect to /play works in dev too.@@@ -140,5 +140,12 @@ The Live legacy import (`platform/liveLegacyImport.ts`, `scripts/importLiveLegacto Play. This local schema is the seed for Play's per-title results and unlocks;the title-scoped principal/grant design in decision 10 remains the target.- **M6: client floor.** Quality tiers, KTX2 and atlases, baked icons, bundle budget, editor E2E in CI.++ *2026-10-14:* quality tiers and hardware scaling are in (`render/quality.ts`, the HUD's Graphics quality); initial++ JS is 2.35 MB of 2.5 (579 KB gzip). The browser build now loads Rapier from the deterministic non-compat package++ (Vite alias + `vite-plugin-wasm`): the same engine and results, its wasm a 3.1 MB file the browser compiles as it++ streams (1.18 MB gzip) instead of a 4.37 MB JavaScript chunk of base64 (1.65 MB gzip). Node keeps the compat build.++ Still over the 400 KB per-chunk line: Babylon's core (one 2.09 MB chunk: engines, materials, meshes, maths). A split++ by Babylon folder was tried and refused: circular chunks, and it pulled the lazily loaded shaders into the initial++ graph (3.97 MB). Left for M6: that chunk, KTX2 and atlases, baked icons, editor E2E in CI.Each milestone is playable end to end and keeps the slice harness green.@@@ -3,14 +3,17 @@ import type { RapierModule } from './rapierWorld.js'/*** Loads and initializes the pinned deterministic Rapier build (ADR-0007* decision 2): `@dimforge/rapier3d-deterministic-compat`, whose wasm is inlined-- * as base64 so a single entry works in Node and the browser with no asset-- * plumbing and no loader.++ * as base64 so a single entry works in Node with no asset plumbing. The++ * browser build aliases it to `@dimforge/rapier3d-deterministic` (plan M6: the++ * wasm becomes its own file, so the 4.4 MB base64 chunk is gone).** The import is dynamic on purpose: physics is loaded while the WebSocket* ticket is fetched, so it is never part of the initial JavaScript.*/export async function loadRapier(): Promise<RapierModule> {const R = (await import('@dimforge/rapier3d-deterministic-compat')) as unknown as RapierModule-- await R.init()++ // The browser build resolves this import to `@dimforge/rapier3d-deterministic` (apps/client/vite.config.ts): the same++ // engine and wasm as a separate file, instantiated as the module loads, so it has no init() to call.++ if (typeof R.init === 'function') await R.init()return R}@@@ -56,6 +56,9 @@ importers:'@babylonjs/procedural-textures':specifier: ^9.20.0version: 9.20.0(@babylonjs/[email protected])++ '@dimforge/rapier3d-deterministic':++ specifier: 0.21.0++ version: 0.21.0'@openvibe/casual':specifier: workspace:*version: link:../../packages/casual@@@ -81,6 +84,9 @@ importers:vite:specifier: ^6.2.5version: 6.4.3(@types/[email protected])([email protected])++ vite-plugin-wasm:++ specifier: 3.6.0++ version: 3.6.0([email protected](@types/[email protected])([email protected]))apps/server:dependencies:@@@ -289,6 +295,9 @@ packages:'@dimforge/[email protected]':resolution: {integrity: sha512-n7JuU62Obljlgq+OeH2DHhTmRSjRzwuJPl54tS9pFIzVQyvXsO5CH+a5KkIRBtnFWDbg1Zicv9BulG0jfBAopA==}++ '@dimforge/[email protected]':++ resolution: {integrity: sha512-ITZuCbM1hg4B1RXisactr9vQ+GU8UeuHQYXiv20itjt8aihV/lXhdkHqFJoloZJFyuY23gurCedICdWOMGguDw==}++'@electric-sql/[email protected]':resolution: {integrity: sha512-n9tsbUOhwx2epK1V0ZG9Ar4SHWUju04dhmzZXiSBXwBoleOvIfals33NAaWgagQVAL4Rbvx/Ptsu3P+pA09f6Q==}@@@ -1619,6 +1628,11 @@ packages:resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}++ resolution: {integrity: sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw==}++ peerDependencies:++ vite: ^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8++resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}@@@ -1829,6 +1843,8 @@ snapshots:'@dimforge/[email protected]': {}++ '@dimforge/[email protected]': {}++'@electric-sql/[email protected]': {}'@esbuild/[email protected]':@@@ -3013,6 +3029,10 @@ snapshots:dependencies:punycode: 2.3.1++ [email protected]([email protected](@types/[email protected])([email protected])):++ dependencies:++ vite: 6.4.3(@types/[email protected])([email protected])++dependencies:esbuild: 0.25.12
@@ -14,6 +14,7 @@
@@ -14,6 +14,7 @@
"@babylonjs/loaders": "^9.20.0",
"@babylonjs/loaders": "^9.20.0",
"@babylonjs/materials": "^9.20.0",
"@babylonjs/materials": "^9.20.0",
"@babylonjs/procedural-textures": "^9.20.0",
"@babylonjs/procedural-textures": "^9.20.0",
+ "@dimforge/rapier3d-deterministic": "0.21.0",
"@openvibe/casual": "workspace:*",
"@openvibe/casual": "workspace:*",
"@openvibe/content": "workspace:*",
"@openvibe/content": "workspace:*",
"@openvibe/gameplay": "workspace:*",
"@openvibe/gameplay": "workspace:*",
@@ -23,6 +24,7 @@
@@ -23,6 +24,7 @@
},
},
"devDependencies": {
"devDependencies": {
"babylonjs-gltf2interface": "^9.20.0",
"babylonjs-gltf2interface": "^9.20.0",
- "vite": "^6.2.5"
+ "vite": "^6.2.5",
+ "vite-plugin-wasm": "3.6.0"
}
}
}
}
@@ -1,7 +1,20 @@
@@ -1,7 +1,20 @@
import { defineConfig } from 'vite'
import { defineConfig } from 'vite'
+import wasm from 'vite-plugin-wasm'
+import { fileURLToPath } from 'node:url'
export default defineConfig({
export default defineConfig({
+ // Physics (plan M6): the deterministic Rapier build whose wasm is a file of its own, compiled by the browser as it
+ // streams in, instead of the compat build's 4.4 MB of base64 inside a JavaScript chunk. Same engine, same results.
+ // An absolute path: packages/physics imports the compat name and does not depend on this build itself.
+ resolve: {
+ alias: {
+ '@dimforge/rapier3d-deterministic-compat': fileURLToPath(
+ new URL('./node_modules/@dimforge/rapier3d-deterministic/rapier.js', import.meta.url),
+ ),
+ },
+ },
plugins: [
plugins: [
+ wasm(),
{
{
// Mirror the production server's pretty-URL aliases so the OAuth
// Mirror the production server's pretty-URL aliases so the OAuth
// callback's redirect to /play works in dev too.
// callback's redirect to /play works in dev too.
@@ -140,5 +140,12 @@ The Live legacy import (`platform/liveLegacyImport.ts`, `scripts/importLiveLegac
@@ -140,5 +140,12 @@ The Live legacy import (`platform/liveLegacyImport.ts`, `scripts/importLiveLegac
to Play. This local schema is the seed for Play's per-title results and unlocks;
to Play. This local schema is the seed for Play's per-title results and unlocks;
the title-scoped principal/grant design in decision 10 remains the target.
the title-scoped principal/grant design in decision 10 remains the target.
- **M6: client floor.** Quality tiers, KTX2 and atlases, baked icons, bundle budget, editor E2E in CI.
- **M6: client floor.** Quality tiers, KTX2 and atlases, baked icons, bundle budget, editor E2E in CI.
+ *2026-10-14:* quality tiers and hardware scaling are in (`render/quality.ts`, the HUD's Graphics quality); initial
+ JS is 2.35 MB of 2.5 (579 KB gzip). The browser build now loads Rapier from the deterministic non-compat package
+ (Vite alias + `vite-plugin-wasm`): the same engine and results, its wasm a 3.1 MB file the browser compiles as it
+ streams (1.18 MB gzip) instead of a 4.37 MB JavaScript chunk of base64 (1.65 MB gzip). Node keeps the compat build.
+ Still over the 400 KB per-chunk line: Babylon's core (one 2.09 MB chunk: engines, materials, meshes, maths). A split
+ by Babylon folder was tried and refused: circular chunks, and it pulled the lazily loaded shaders into the initial
+ graph (3.97 MB). Left for M6: that chunk, KTX2 and atlases, baked icons, editor E2E in CI.
Each milestone is playable end to end and keeps the slice harness green.
Each milestone is playable end to end and keeps the slice harness green.
@@ -3,14 +3,17 @@ import type { RapierModule } from './rapierWorld.js'
@@ -3,14 +3,17 @@ import type { RapierModule } from './rapierWorld.js'
/**
/**
* Loads and initializes the pinned deterministic Rapier build (ADR-0007
* Loads and initializes the pinned deterministic Rapier build (ADR-0007
* decision 2): `@dimforge/rapier3d-deterministic-compat`, whose wasm is inlined
* decision 2): `@dimforge/rapier3d-deterministic-compat`, whose wasm is inlined
- * as base64 so a single entry works in Node and the browser with no asset
- * plumbing and no loader.
+ * as base64 so a single entry works in Node with no asset plumbing. The
+ * browser build aliases it to `@dimforge/rapier3d-deterministic` (plan M6: the
+ * wasm becomes its own file, so the 4.4 MB base64 chunk is gone).
*
*
* The import is dynamic on purpose: physics is loaded while the WebSocket
* The import is dynamic on purpose: physics is loaded while the WebSocket
* ticket is fetched, so it is never part of the initial JavaScript.
* ticket is fetched, so it is never part of the initial JavaScript.
*/
*/
export async function loadRapier(): Promise<RapierModule> {
export async function loadRapier(): Promise<RapierModule> {
const R = (await import('@dimforge/rapier3d-deterministic-compat')) as unknown as RapierModule
const R = (await import('@dimforge/rapier3d-deterministic-compat')) as unknown as RapierModule
- await R.init()
+ // The browser build resolves this import to `@dimforge/rapier3d-deterministic` (apps/client/vite.config.ts): the same
+ // engine and wasm as a separate file, instantiated as the module loads, so it has no init() to call.
+ if (typeof R.init === 'function') await R.init()
return R
return R
}
}
@@ -56,6 +56,9 @@ importers:
@@ -56,6 +56,9 @@ importers:
'@babylonjs/procedural-textures':
'@babylonjs/procedural-textures':
specifier: ^9.20.0
specifier: ^9.20.0
version: 9.20.0(@babylonjs/[email protected])
version: 9.20.0(@babylonjs/[email protected])
+ '@dimforge/rapier3d-deterministic':
+ specifier: 0.21.0
+ version: 0.21.0
'@openvibe/casual':
'@openvibe/casual':
specifier: workspace:*
specifier: workspace:*
version: link:../../packages/casual
version: link:../../packages/casual
@@ -81,6 +84,9 @@ importers:
@@ -81,6 +84,9 @@ importers:
vite:
vite:
specifier: ^6.2.5
specifier: ^6.2.5
version: 6.4.3(@types/[email protected])([email protected])
version: 6.4.3(@types/[email protected])([email protected])
+ vite-plugin-wasm:
+ specifier: 3.6.0
apps/server:
apps/server:
dependencies:
dependencies:
@@ -289,6 +295,9 @@ packages:
@@ -289,6 +295,9 @@ packages:
'@dimforge/[email protected]':
'@dimforge/[email protected]':
resolution: {integrity: sha512-n7JuU62Obljlgq+OeH2DHhTmRSjRzwuJPl54tS9pFIzVQyvXsO5CH+a5KkIRBtnFWDbg1Zicv9BulG0jfBAopA==}
resolution: {integrity: sha512-n7JuU62Obljlgq+OeH2DHhTmRSjRzwuJPl54tS9pFIzVQyvXsO5CH+a5KkIRBtnFWDbg1Zicv9BulG0jfBAopA==}
+ '@dimforge/[email protected]':
+ resolution: {integrity: sha512-ITZuCbM1hg4B1RXisactr9vQ+GU8UeuHQYXiv20itjt8aihV/lXhdkHqFJoloZJFyuY23gurCedICdWOMGguDw==}
+
'@electric-sql/[email protected]':
'@electric-sql/[email protected]':
resolution: {integrity: sha512-n9tsbUOhwx2epK1V0ZG9Ar4SHWUju04dhmzZXiSBXwBoleOvIfals33NAaWgagQVAL4Rbvx/Ptsu3P+pA09f6Q==}
resolution: {integrity: sha512-n9tsbUOhwx2epK1V0ZG9Ar4SHWUju04dhmzZXiSBXwBoleOvIfals33NAaWgagQVAL4Rbvx/Ptsu3P+pA09f6Q==}
@@ -1619,6 +1628,11 @@ packages:
@@ -1619,6 +1628,11 @@ packages:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ resolution: {integrity: sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw==}
+ peerDependencies:
+ vite: ^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+
resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -1829,6 +1843,8 @@ snapshots:
@@ -1829,6 +1843,8 @@ snapshots:
'@dimforge/[email protected]': {}
'@dimforge/[email protected]': {}
+ '@dimforge/[email protected]': {}
+
'@electric-sql/[email protected]': {}
'@electric-sql/[email protected]': {}
'@esbuild/[email protected]':
'@esbuild/[email protected]':
@@ -3013,6 +3029,10 @@ snapshots:
@@ -3013,6 +3029,10 @@ snapshots:
dependencies:
dependencies:
punycode: 2.3.1
punycode: 2.3.1
+ dependencies:
+ vite: 6.4.3(@types/[email protected])([email protected])
+
dependencies:
dependencies:
esbuild: 0.25.12
esbuild: 0.25.12