DEV Community

Daniel Ioni
Daniel Ioni

Posted on

Building the MyZubster Metaverse: Browser-First WebXR, Server-Authoritative Rooms, Versioned Scenes, and the Road Ahead

Building the MyZubster Metaverse: Browser-First WebXR, Server-Authoritative Rooms, Versioned Scenes, and the Road Ahead
The MyZubster Metaverse is evolving from a narrative prototype into a browser-first, testable platform with explicit technical contracts.
The objective is not to build a VR-only experience. The objective is to create a shared digital environment that works on ordinary browsers first, can progressively enable WebXR when available, and can connect virtual experiences to real MyZubster projects—including LIFE-oriented environmental pilots.
This technical update covers:

  • What currently works
  • What we verified in production
  • The browser and WebXR capability layer
  • The server-authoritative room lifecycle
  • The versioned scene-manifest contract
  • Security and validation boundaries
  • How this connects to LIFE and evidence-based pilots
  • What is still missing
  • The next implementation milestones Repository: MyZubster-Ecosystem/myzubster Live entry point: MyZubster Metaverse
  • The Architecture We Are Building The current direction separates the metaverse into several technical layers: ┌───────────────────────────────────────────────────────────┐ │ Browser Experience │ │ │ │ React UI → capability checks → browser/WebXR fallback │ └───────────────────────────────┬───────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────┐ │ Metaverse Client Runtime │ │ │ │ Scene loading → controls → chat → emotes → interactions │ └───────────────────────────────┬───────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────┐ │ Server-Authoritative API │ │ │ │ Rooms → sessions → access → capacity → stage → tokens │ └───────────────────────────────┬───────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────┐ │ Versioned Scene Contract │ │ │ │ Assets → spawn points → zones → portals → capabilities │ └───────────────────────────────┬───────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────┐ │ Evidence / LIFE Layer │ │ │ │ Pilot → provenance → evidence → KPI/MRV → visualization │ └───────────────────────────────────────────────────────────┘ Each layer has a different responsibility. The browser detects what the device can do. The server controls room and session authority. The scene manifest defines what should be loaded. The evidence layer determines what claims can legitimately be represented. This separation matters because a 3D scene must not become the source of truth for authentication, permissions, environmental claims, or evidence.
  • What Works Today Neon Plaza is available directly from a browser. A VR headset, crypto wallet, native application, or dedicated GPU is not required for the standard experience. During our production desktop smoke test, we verified:
  • Authenticated access
  • Character-associated entry
  • Neon Plaza loading
  • An active online session
  • Movement controls
  • Emotes
  • Room chat
  • Browser capability detection
  • A non-blocking fallback when immersive WebXR is unavailable The critical product rule is: WebXR is an optional capability. It must never become a mandatory gateway to the MyZubster Metaverse.

Desktop and mobile browsers remain the baseline. VR devices can progressively enhance the experience when supported.
This is important for accessibility, adoption, testing, and compatibility with real-world projects. A LIFE participant, researcher, local community member, or developer should not need a headset simply to inspect a pilot, join a session, or understand a project.

  1. Browser and WebXR Capability Detection The first implementation milestone was tracked in:
  2. PR #1597 — Add non-blocking WebXR capability fallback The browser capability module defines explicit WebXR states: export const WEBXR_STATUS = Object.freeze({ CHECKING: 'checking', SUPPORTED: 'supported', UNSUPPORTED: 'unsupported', ERROR: 'error' }); This is intentionally more precise than a boolean such as hasWebXR. There are several meaningful conditions: checking │ ├── supported │ ├── unsupported │ └── error An unsupported API is different from a capability check that failed. A browser without navigator.xr is different from a browser exposing the API but rejecting the request. The implementation checks the browser baseline separately: export function browserCapabilityBaseline(browser = window) { return [ { label: 'JavaScript', ok: true }, { label: 'Fetch API', ok: typeof browser.fetch === 'function' }, { label: 'Storage locale', ok: Boolean(browser.localStorage) }, { label: 'CSS Grid', ok: Boolean(browser.CSS?.supports?.('display', 'grid')) }, { label: 'Connessione', ok: browser.navigator?.onLine !== false } ]; } WebXR support is detected asynchronously: export async function detectImmersiveVr( navigatorLike = navigator ) { if (!navigatorLike?.xr?.isSessionSupported) { return { status: WEBXR_STATUS.UNSUPPORTED, reason: 'api-unavailable' }; }

try {
const supported =
await navigatorLike.xr.isSessionSupported('immersive-vr');

return {
  status: supported
    ? WEBXR_STATUS.SUPPORTED
    : WEBXR_STATUS.UNSUPPORTED,
  reason: supported ? null : 'session-unsupported'
};
Enter fullscreen mode Exit fullscreen mode

} catch (_error) {
return {
status: WEBXR_STATUS.ERROR,
reason: 'capability-check-failed'
};
}
}
Why dependency injection matters here
The function accepts navigatorLike instead of directly depending on the global navigator internally.
That makes capability behavior testable:
await detectImmersiveVr({
xr: {
isSessionSupported: async () => true
}
});
It also allows us to test unsupported and error conditions without requiring an actual headset in every CI run.

  1. Non-Blocking Fallback UX Capability detection is connected to a React component that communicates the system state to the user. A simplified version of the state mapping is: const XR_COPY = { checking: 'Checking WebXR support…', supported: 'Immersive WebXR is available', unsupported: 'Browser mode active: a headset is not required', error: 'WebXR could not be verified: continue in browser mode' }; The important behavior is not just the message. It is the absence of a blocking redirect or fatal error. const browserReady = checks.every((check) => check.ok);

const ready =
browserReady &&
xr.status !== WEBXR_STATUS.CHECKING;
When immersive VR is unavailable, the interface explicitly keeps the browser path open:
{xr.status !== WEBXR_STATUS.SUPPORTED && (

Neon Plaza remains accessible from desktop and mobile.
The WebXR check never blocks entry.

)}
This approach supports progressive enhancement:
Basic browser
↓
Neon Plaza browser experience
↓
Optional advanced capabilities
├── richer graphics
├── immersive WebXR
├── spatial input
└── device-specific controls
The baseline must remain usable even when one of the enhancement layers is unavailable.

  1. Server-Authoritative Room Lifecycle The metaverse is not controlled only by client-side React state. Rooms and sessions have server-authoritative lifecycle states: const ROOM_TRANSITIONS = Object.freeze({ draft: new Set(['published']), published: new Set(['scheduled', 'live']), scheduled: new Set(['live']), live: new Set(['ended']), ended: new Set(['archive']), archive: new Set([]) }); This gives us a forward-only state machine: draft │ ▼ published │ ├───────────────┐ ▼ ▼ scheduled live │ │ └───────►───────┘ ▼ ended │ ▼ archive Invalid reverse transitions are rejected. For example: if ( patch.state && patch.state !== room.state ) { if (!ROOM_TRANSITIONS[room.state]?.has(patch.state)) { return { valid: false, status: 409, error: Invalid room transition + ${room.state} -> ${patch.state} }; }

room.state = patch.state;
}
This prevents a browser client from arbitrarily changing an ended room back to live.
The client requests a transition. The server decides whether it is valid.

  1. Room Access and Capacity A virtual room can currently use one of three access policies: accessPolicy: { type: String, enum: [ 'public', 'authenticated', 'private' ], default: 'authenticated' } Capacity is bounded at the model and service layers: capacity: { type: Number, min: 1, max: 500, default: 100 } Join validation checks:
  2. Session state
  3. Blocklist
  4. Private-room allowlist
  5. Host access
  6. Current participant count
  7. Existing membership async function validateJoin({ session, room, actorUserId }) { if (session.state !== 'live') { return { valid: false, status: 409, error: 'Session is not live' }; }

const actor = String(actorUserId);

if ((room.blockedUserIds || []).includes(actor)) {
return {
valid: false,
status: 403,
error: 'Access blocked'
};
}

if (
room.accessPolicy === 'private' &&
!(room.allowedUserIds || []).includes(actor) &&
actor !== String(room.hostUserId)
) {
return {
valid: false,
status: 403,
error: 'Private room access denied'
};
}

const current =
new Set(session.participantUserIds || []);

if (
!current.has(actor) &&
current.size >= session.capacity
) {
return {
valid: false,
status: 409,
error: 'Session is at capacity'
};
}

return { valid: true };
}
The server therefore remains authoritative over membership. A modified browser cannot legitimately join a closed or full session simply by changing local state.

  1. Short-Lived Realtime Tokens After a successful join, the backend can mint a short-lived realtime token. The token is scoped to:
  2. The authenticated user
  3. A specific purpose
  4. A session
  5. A room
  6. A scene-manifest version function mintRealtimeToken( session, actorUserId ) { const secret = process.env.REALTIME_TOKEN_SECRET || process.env.JWT_SECRET;

if (!secret) {
throw new Error(
'Realtime token secret is not configured'
);
}

return jwt.sign({
sub: String(actorUserId),
purpose: 'metaverse-realtime',
sessionId: session.sessionId,
roomId: session.roomId,
sceneManifestVersion:
session.sceneManifestVersion
}, secret, {
expiresIn: '5m'
});
}
The five-minute expiration limits the lifetime of captured tokens.
Future work will need to evaluate:

  • Token refresh
  • Revocation semantics
  • Horizontal realtime scaling
  • Redis-backed session coordination
  • Replay resistance
  • Rate limiting
  • Reconnect behavior
  • Multi-region latency
  • Moderation Without Exposing Raw Account IDs Moderation references are generated as opaque identifiers rather than exposing account IDs directly in public session views. function moderationParticipantRef( sessionId, actorUserId ) { return hashRoomInviteCode( ${sessionId}:${actorUserId} ).slice(0, 24); } Room blocklist references are scoped separately: function blockedParticipantRef( roomId, actorUserId ) { return hashRoomInviteCode( blocklist:${roomId}:${actorUserId} ).slice(0, 24); } This does not make the system anonymous by itself, but it reduces unnecessary identifier exposure in moderation interfaces. The server supports actions such as:
  • Removing a participant from a live session
  • Blocking a participant from a room
  • Removing pending stage requests
  • Revoking speaker status
  • Listing blocklist entries through opaque references The public session representation also excludes raw participant arrays: function publicSession(session) { return { id: session.sessionId, roomId: session.roomId, state: session.state, capacity: session.capacity, participantCount: (session.participantUserIds || []).length, sceneManifestVersion: session.sceneManifestVersion, lifecycleVersion: session.lifecycleVersion, startedAt: session.startedAt || null, endedAt: session.endedAt || null }; }
  • Versioned Scene Manifest v1 The second major implementation milestone was delivered through:
  • PR #1601 — Validate versioned scene manifests before publication The manifest establishes an explicit contract between the room configuration, asset pipeline, backend, and client runtime. The current schema version is: const SCENE_MANIFEST_SCHEMA_VERSION = '1'; Supported asset types are intentionally constrained: const ASSET_TYPES = new Set([ 'gltf', 'glb', 'image', 'audio', 'video' ]); The initial contract also defines allowed zone types, lighting presets, and capability requirements: const ZONE_TYPES = new Set([ 'portal', 'stage', 'link' ]);

const LIGHTING_PRESETS = new Set([
'day',
'sunset',
'night',
'studio'
]);

const CAPABILITIES = new Set([
'webgl2',
'webxr-immersive-vr',
'audio',
'video'
]);

  1. Safe Default Manifest
    Every newly created room receives a minimal valid scene manifest:
    function defaultSceneManifest() {
    return {
    schemaVersion:
    SCENE_MANIFEST_SCHEMA_VERSION,

    assetBundleVersion: '1',

    assets: [],

    spawnPoints: [
    {
    id: 'default',
    position: [0, 0, 0]
    }
    ],

    interactionZones: [],

    stage: {
    position: [0, 0, 0],
    capacity: 1
    },

    lighting: {
    preset: 'day'
    },

    capabilities: []
    };
    }
    This provides a known baseline rather than leaving newly created rooms in an undefined state.
    The room creation service persists this default:
    const room = await VirtualRoom.create({
    roomId: crypto.randomUUID(),
    slug: safeSlug,
    name: safeName,
    hostUserId: String(actorUserId),
    accessPolicy,
    capacity: numericCapacity,
    stagePolicy,
    sceneManifest: defaultSceneManifest()
    });

  2. Example of a More Complete Scene Manifest
    A future Neon Plaza manifest could look like this:
    {
    "schemaVersion": "1",
    "assetBundleVersion": "42",
    "assets": [
    {
    "id": "neon-plaza",
    "type": "glb",
    "url": "https://cdn.myzubster.com/scenes/neon-plaza.42.glb"
    },
    {
    "id": "ambient-track",
    "type": "audio",
    "url": "https://cdn.myzubster.com/audio/neon-plaza.7.ogg"
    },
    {
    "id": "life-dashboard",
    "type": "image",
    "url": "https://cdn.myzubster.com/life/pilot-dashboard.12.webp"
    }
    ],
    "spawnPoints": [
    {
    "id": "main-entrance",
    "position": [0, 1.6, 4]
    },
    {
    "id": "life-hub",
    "position": [12, 1.6, -3]
    }
    ],
    "interactionZones": [
    {
    "id": "life-portal",
    "type": "portal",
    "position": [10, 0, -3],
    "radius": 1.5,
    "destination": "life-hub"
    },
    {
    "id": "pilot-details",
    "type": "link",
    "position": [14, 1, -4],
    "radius": 1,
    "destination": "https://www.myzubster.com/life-pilot"
    }
    ],
    "stage": {
    "position": [0, 0, -8],
    "capacity": 30
    },
    "lighting": {
    "preset": "sunset"
    },
    "capabilities": [
    "webgl2",
    "audio"
    ]
    }
    This is a contract, not an arbitrary JSON document.
    The server validates its structure before accepting it.

  3. Input Validation and Security Boundaries
    Manifest IDs must use a constrained format:
    function validId(value) {
    return (
    typeof value === 'string' &&
    /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(value)
    );
    }
    Coordinates must contain exactly three finite values:
    function validVector(value) {
    return (
    Array.isArray(value) &&
    value.length === 3 &&
    value.every(Number.isFinite)
    );
    }
    Asset and external-link URLs must use HTTPS:
    function validHttpsUrl(value) {
    if (
    typeof value !== 'string' ||
    value.length > 2048
    ) {
    return false;
    }

try {
return new URL(value).protocol === 'https:';
} catch {
return false;
}
}
This blocks obvious unsafe values such as:
javascript:alert(1)
http://untrusted.example/asset.glb
invalid-url
HTTPS validation alone does not solve every asset-security problem. Future protections should include:

  • Approved asset origins
  • Content-Type verification
  • Maximum file size
  • Download timeouts
  • Hash or checksum validation
  • Malware scanning where applicable
  • glTF structural validation
  • Texture and geometry limits
  • CORS verification
  • Redirect restrictions
  • Decompression-bomb protections
  • Asset-license metadata
  • Bounded Collections The validator limits the number of objects that can be supplied. For example: if ( !Array.isArray(input.assets) || input.assets.length > 100 ) { errors.push( 'assets must be an array with at most 100 entries' ); } Spawn points are also bounded: if ( !Array.isArray(input.spawnPoints) || input.spawnPoints.length < 1 || input.spawnPoints.length > 32 ) { errors.push( 'spawnPoints must contain between 1 and 32 entries' ); } Bounding collection sizes reduces accidental or malicious resource amplification before scene loading begins. Client-side budgets will still be needed for:
  • Triangle count
  • Texture memory
  • Audio duration
  • Video resolution
  • Animation count
  • Shader complexity
  • Draw calls
  • Concurrent network requests
  • Duplicate and Invalid Spawn Points Spawn-point IDs must be valid and unique: const ids = new Set();

input.spawnPoints.forEach((spawn, index) => {
if (!isRecord(spawn) || !validId(spawn.id)) {
errors.push(
spawnPoints[${index}].id is invalid
);
} else if (ids.has(spawn.id)) {
errors.push(
spawnPoints[${index}].id must be unique
);
} else {
ids.add(spawn.id);
}

if (
!isRecord(spawn) ||
!validVector(spawn.position)
) {
errors.push(
spawnPoints[${index}].position +
'must be a finite xyz vector'
);
}
});
This prevents ambiguous spawn resolution and invalid coordinates such as:
{
"id": "entrance",
"position": [0, null, 4]
}
or:
{
"id": "entrance",
"position": [0, 1]
}

  1. Portal and Link Validation Interaction zones can currently represent:
  2. Portals
  3. Stage areas
  4. External links Portal destinations must be valid internal identifiers: if ( zone?.type === 'portal' && !validId(zone.destination) ) { errors.push( interactionZones[${index}].destination + 'is required for portals' ); } External links must use HTTPS: if ( zone?.type === 'link' && !validHttpsUrl(zone.destination) ) { errors.push( interactionZones[${index}].destination + 'must use https for links' ); } A complete implementation will also need to distinguish: internal room destination internal MyZubster route approved external URL blocked external URL missing destination temporarily unavailable destination
  5. Validation Before Publication The most important server-side behavior is the publication gate. When the client supplies a new manifest—or tries to publish a draft—the backend validates the candidate first: const manifestCandidate = patch.sceneManifest !== undefined ? patch.sceneManifest : room.sceneManifest;

if (
patch.sceneManifest !== undefined ||
patch.state === 'published'
) {
const manifestValidation =
validateSceneManifest(manifestCandidate);

if (!manifestValidation.valid) {
return {
valid: false,
status: 400,
error: 'Invalid scene manifest',
details: manifestValidation.errors
};
}

room.sceneManifest =
manifestValidation.manifest;
}
Only after this check can the room continue through the lifecycle transition.
The intended invariant is:
invalid manifest
│
▼
HTTP 400
│
└── publication rejected
while:
valid manifest
│
▼
lifecycle validation
│
▼
draft → published
This is stronger than validating only in the frontend. Frontend validation improves UX, but it cannot be authoritative because requests can be sent outside the official UI.

  1. Persisting the Scene Contract The room model now stores both the manifest version and manifest content: sceneManifestVersion: { type: String, default: '1' },

sceneManifest: {
type: mongoose.Schema.Types.Mixed,
default: null
}
The public room representation includes the scene configuration:
function publicRoom(room) {
return {
id: room.roomId,
slug: room.slug,
name: room.name,
state: room.state,
accessPolicy: room.accessPolicy,
capacity: room.capacity,
stagePolicy: room.stagePolicy,
sceneManifestVersion:
room.sceneManifestVersion,
sceneManifest:
room.sceneManifest || null,
scheduledFor:
room.scheduledFor || null
};
}
This is the first version of the contract. It is not the final persistence design.
The next implementation should separate the current manifest pointer from immutable historical versions.

  1. Testing the Contract The validator includes tests for:
  2. A safe default manifest
  3. Versioned asset bundles
  4. Valid spawn points
  5. Valid interaction zones
  6. Portal destinations
  7. HTTPS links
  8. Unsupported schema versions
  9. Insecure asset URLs
  10. Malformed vectors
  11. Duplicate IDs
  12. Unsupported capability requirements
    A simplified example:
    test(
    'rejects unsupported schema versions and insecure asset urls',
    () => {
    const manifest = {
    ...defaultSceneManifest(),

    schemaVersion: '2',

    assets: [{
    id: 'plaza',
    type: 'glb',
    url: 'http://cdn.example.com/plaza.glb'
    }]
    };

    const result =
    validateSceneManifest(manifest);

    expect(result.valid).toBe(false);

    expect(result.errors).toEqual(
    expect.arrayContaining([
    'schemaVersion must be 1',
    'assets[0].url must use https'
    ])
    );
    }
    );
    Another test verifies malformed vectors and duplicate IDs:
    test(
    'rejects malformed vectors, duplicate ids and unsupported capabilities',
    () => {
    const manifest = {
    ...defaultSceneManifest(),

    spawnPoints: [
    {
    id: 'same',
    position: [0, 0]
    },
    {
    id: 'same',
    position: [0, 0, Number.NaN]
    }
    ],

    capabilities: ['telepathy']
    };

    const result =
    validateSceneManifest(manifest);

    expect(result.valid).toBe(false);
    }
    );
    The merged changes passed:

  13. Backend lint

  14. Backend unit tests

  15. Frontend unit tests

  16. Backend build

  17. Frontend build

  18. Security audit

  19. Continuous evidence checks

  20. What We Verified in Production
    The current evidence boundary is important.
    We verified the following using a desktop browser against the production environment:

  21. The metaverse page loaded

  22. Authentication was active

  23. Neon Plaza loaded

  24. The session appeared online

  25. Controls were visible

  26. Emotes were available

  27. Chat was available

  28. The browser capability panel rendered

  29. Baseline browser checks passed

  30. WebXR fallback messaging appeared

  31. The experience remained accessible without immersive WebXR
    We have not yet treated the following as fully verified:

  32. Real-device mobile behavior

  33. Real-headset WebXR entry

  34. Real-headset WebXR exit

  35. Controller input across different headsets

  36. Long-running realtime reliability

  37. High-concurrency room behavior

  38. CDN-backed versioned 3D asset loading

  39. Manifest rollback in production

  40. Complete accessibility validation

  41. A full LIFE pilot represented in the scene
    This distinction prevents roadmap intentions from being reported as completed features.

  42. Connecting the Metaverse to LIFE
    The MyZubster Metaverse should not become only a visual or social layer.
    Its more valuable role is to become an interface between:

  43. Real-world pilots

  44. Technical evidence

  45. Community participation

  46. Education

  47. Environmental indicators

  48. Replicable workflows
    The proposed LIFE-oriented flow is:
    Real-world pilot
    ↓
    Observation or measurement
    ↓
    Data provenance
    ↓
    Evidence classification
    ↓
    KPI / MRV calculation
    ↓
    MyZubster project record
    ↓
    Metaverse representation
    ↓
    Mission or educational interaction
    ↓
    Community feedback
    ↓
    Improved pilot
    Example
    Imagine a LIFE-oriented water or circular-agriculture pilot.
    A participant enters Neon Plaza and approaches a portal labelled “Water Pilot”.
    The portal opens a dedicated scene. Inside that scene, the participant can inspect:

  49. The pilot location at an appropriate privacy level

  50. The project methodology

  51. The instruments or sensors used

  52. The observation period

  53. Current evidence status

  54. Relevant KPIs

  55. Data-quality warnings

  56. Missing evidence

  57. Replication instructions

  58. Public contribution tasks
    The scene might include a dashboard, but the dashboard must reference an authoritative evidence record.
    The scene itself is not evidence.
    3D visualization ≠ verified measurement
    simulation ≠ real-world result
    self-declaration ≠ independent validation
    project goal ≠ achieved impact
    The UI should make these boundaries visible.

  59. Evidence States for LIFE Scenes
    A useful next contract could attach evidence states to interactive objects:
    {
    "id": "water-savings-display",
    "type": "evidence-panel",
    "position": [4, 1.5, -2],
    "evidence": {
    "status": "measured",
    "source": "pilot-observation-2026-10",
    "methodologyVersion": "0.2",
    "verifiedBy": null,
    "independentVerification": false
    }
    }
    Possible evidence states could include:
    planned
    self-declared
    observed
    measured
    methodology-reviewed
    independently-verified
    disputed
    superseded
    This would allow the metaverse client to render different visual treatments:
    verified data → solid green indicator
    measured, unverified → amber indicator
    self-declared → blue information indicator
    simulation → purple simulation indicator
    missing evidence → grey warning indicator
    disputed → red review indicator
    The goal is not to gamify uncertainty away. The goal is to make uncertainty understandable.

  60. What Is Still Missing
    The current implementation is a foundation, not a finished metaverse stack.
    22.1 Real mobile-device validation
    We still need structured testing on real devices:

  61. Android Chrome

  62. Android Firefox

  63. iOS Safari

  64. iPadOS Safari

  65. Low-memory devices

  66. Slow or unstable networks

  67. Small screens

  68. Touch-only navigation

  69. Orientation changes

  70. Background and resume behavior
    The UI claims that mobile browser access is supported as the intended baseline, but a complete real-device verification matrix is still required.
    22.2 Real WebXR headset testing
    We need physical tests for:

  71. Entering immersive mode

  72. Exiting immersive mode

  73. Session interruption

  74. Controller input

  75. Focus loss

  76. Guardian or boundary changes

  77. Headset sleep and resume

  78. Unsupported-feature fallback

  79. Audio behavior

  80. Motion comfort

  81. Performance degradation
    Automated capability tests cannot replace physical-device validation.
    22.3 Immutable manifest history
    The current room stores its active manifest.
    The next persistence layer should add immutable versions:
    {
    roomId: 'room-123',
    version: '7',
    schemaVersion: '1',
    assetBundleVersion: '42',
    checksum: 'sha256:...',
    manifest: { /* immutable snapshot */ },
    createdBy: 'account-reference',
    createdAt: '2026-10-12T...'
    }
    A room should point to an active version rather than mutating history:
    VirtualRoom
    activeManifestVersion → 7

SceneManifestVersion
version 5
version 6
version 7
22.4 Safe rollback
Rollback should not destroy newer history.
Instead, it should create a new version based on a previous valid snapshot:
v5 → v6 → v7
│
└── rollback requested
↓
v8
content derived from v6
That preserves auditability.
22.5 CDN and cache invalidation
Versioned asset URLs should be immutable:
/scenes/neon-plaza.42.glb
/textures/plaza-floor.18.webp
/audio/neon-ambient.7.ogg
The manifest can change its pointer to a new asset version, while previous versions remain available for rollback.
The CDN layer needs:

  • Immutable cache headers
  • Content hashes
  • CORS configuration
  • Range requests where appropriate
  • Compression strategy
  • Cache invalidation rules
  • Origin failure handling
  • Asset availability checks 22.6 Graceful asset failure A missing decorative object should not necessarily prevent the whole scene from loading. The client needs failure classes: critical asset failure └── load fallback scene

important asset failure
└── continue with visible warning

optional asset failure
└── omit asset and continue
A future manifest entry could declare this explicitly:
{
"id": "neon-plaza",
"type": "glb",
"url": "https://cdn.myzubster.com/scenes/neon-plaza.42.glb",
"criticality": "critical",
"fallbackAssetId": "basic-plaza"
}
22.7 Resource budgets
Schema validity does not guarantee runtime safety.
We still need budgets for:

  • Asset file size
  • Texture dimensions
  • Total texture memory
  • Polygon count
  • Draw calls
  • Animation complexity
  • Audio memory
  • Video resolution
  • Concurrent downloads
  • Scene initialization time 22.8 Realtime load testing The realtime system needs controlled tests for:
  • Concurrent joins
  • Concurrent leaves
  • Chat bursts
  • Stage requests
  • Participant moderation
  • Token refresh
  • Reconnect storms
  • Server restart
  • Database latency
  • Redis degradation
  • Network partitions 22.9 Accessibility The browser-first experience needs continued testing for:
  • Keyboard navigation
  • Screen-reader labels
  • Focus management
  • Reduced motion
  • Colour contrast
  • Text scaling
  • Captions
  • Non-spatial alternatives
  • Touch targets
  • Cognitive load An immersive view cannot be the only way to access important project information. 22.10 LIFE evidence integration The first complete LIFE scene still needs:
  • A selected pilot
  • A defined methodology
  • A provenance contract
  • Evidence states
  • KPI definitions
  • MRV boundaries
  • Privacy rules
  • A public scene representation
  • A contribution workflow
  • A validation and review process
  • Proposed Next Technical Milestones The next roadmap can be structured into the following slices. C2.1 — Immutable manifest versions Create manifest version model → calculate checksum → store immutable snapshot → point room to active version C2.2 — Manifest rollback Select historical version → validate snapshot → create new version → update active pointer → preserve audit history C2.3 — Asset pipeline Upload → validate → scan → optimize → hash → version → CDN publish → manifest reference C2.4 — Client scene loader Fetch room → fetch manifest → check schema version → check capabilities → load critical assets → load optional assets → expose failures → enter scene C2.5 — LIFE pilot scene Pilot record → evidence contract → KPI/MRV mapping → manifest generation → scene visualization → public test
  • Links to the Work Main repository
  • MyZubster-Ecosystem/myzubster Browser and WebXR fallback
  • PR #1597 — Non-blocking WebXR capability fallback Versioned scene manifest
  • PR #1601 — Scene manifest validation and publication gate Live experience
  • MyZubster Metaverse
  • MyZubster
  • LIFE Pilot
  • How Developers Can Contribute The most useful contributions right now include:
  • Android and iOS browser test reports
  • Real-headset WebXR test reports
  • Scene-manifest reviews
  • glTF and GLB validation
  • 3D performance budgets
  • Asset fallback design
  • Accessibility reviews
  • Realtime load-test scenarios
  • Security reviews
  • CDN architecture
  • Manifest version-history design
  • Rollback implementation
  • LIFE evidence modelling
  • KPI and MRV data contracts
  • Technical documentation A useful contribution should include reproducible evidence: environment browser or device exact version steps performed expected result observed result screenshots or logs when safe related issue or pull request Please do not publish:
  • Passwords
  • Authentication tokens
  • Private wallet keys
  • Seed phrases
  • Private participant information
  • Precise sensitive locations
  • Unredacted personal data Conclusion The MyZubster Metaverse now has two important foundations:
  • A browser-first runtime that treats WebXR as progressive enhancement
  • A server-side scene contract that blocks invalid manifests from publication The completed work is publicly traceable through:
  • PR #1597
  • PR #1601 The next challenge is not simply adding more 3D content. It is building a reliable chain from versioned assets and authoritative sessions to transparent, evidence-aware virtual experiences: code → validated contract → versioned scene → reliable session → real-world pilot → evidence → understandable visualization → community contribution That is the direction of the MyZubster Metaverse: an open, browser-first environment where virtual experiences connect to documented projects, reproducible evidence, and real-world collaboration. Status verified: October 12, 2026.

Top comments (0)