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.
- Browser and WebXR Capability Detection The first implementation milestone was tracked in:
- 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'
};
} 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.
- 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.
- 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.
- 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:
- Session state
- Blocklist
- Private-room allowlist
- Host access
- Current participant count
- 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.
- Short-Lived Realtime Tokens After a successful join, the backend can mint a short-lived realtime token. The token is scoped to:
- The authenticated user
- A specific purpose
- A session
- A room
- 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'
]);
-
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()
}); 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.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]
}
- Portal and Link Validation Interaction zones can currently represent:
- Portals
- Stage areas
- 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 - 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.
- 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.
- Testing the Contract The validator includes tests for:
- A safe default manifest
- Versioned asset bundles
- Valid spawn points
- Valid interaction zones
- Portal destinations
- HTTPS links
- Unsupported schema versions
- Insecure asset URLs
- Malformed vectors
- Duplicate IDs
-
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: Backend lint
Backend unit tests
Frontend unit tests
Backend build
Frontend build
Security audit
Continuous evidence checks
What We Verified in Production
The current evidence boundary is important.
We verified the following using a desktop browser against the production environment:The metaverse page loaded
Authentication was active
Neon Plaza loaded
The session appeared online
Controls were visible
Emotes were available
Chat was available
The browser capability panel rendered
Baseline browser checks passed
WebXR fallback messaging appeared
The experience remained accessible without immersive WebXR
We have not yet treated the following as fully verified:Real-device mobile behavior
Real-headset WebXR entry
Real-headset WebXR exit
Controller input across different headsets
Long-running realtime reliability
High-concurrency room behavior
CDN-backed versioned 3D asset loading
Manifest rollback in production
Complete accessibility validation
A full LIFE pilot represented in the scene
This distinction prevents roadmap intentions from being reported as completed features.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:Real-world pilots
Technical evidence
Community participation
Education
Environmental indicators
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:The pilot location at an appropriate privacy level
The project methodology
The instruments or sensors used
The observation period
Current evidence status
Relevant KPIs
Data-quality warnings
Missing evidence
Replication instructions
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.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.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:Android Chrome
Android Firefox
iOS Safari
iPadOS Safari
Low-memory devices
Slow or unstable networks
Small screens
Touch-only navigation
Orientation changes
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:Entering immersive mode
Exiting immersive mode
Session interruption
Controller input
Focus loss
Guardian or boundary changes
Headset sleep and resume
Unsupported-feature fallback
Audio behavior
Motion comfort
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)