RFD 1107 details: layout, manifest, XR mapping, and the API calls
Layout
worlds/
my-world-v1/
world.manifest.json
reference.jpg
environment.ply
props/
lamp.glb
Manifest (world.manifest.json)
{
"id": "my-world-v1",
"version": 1,
"name": "My World",
"spawn": { "position": [0, 0, 0], "rotation_y": 0, "player_height": 1.6 },
"environment": {
"type": "gaussian_splat",
"url": "environment.ply",
"format": "ply",
"renderer": "spark"
},
"props": [
{
"id": "lamp",
"role": "interactable",
"mesh_url": "props/lamp.glb",
"transform": { "position": [1, 0, -1], "rotation_y": 0, "scale": 1 },
"interaction": { "type": "grabbable", "collider": "auto_bbox" }
}
]
}Scene layers
| Layer | Root | Contents |
|---|---|---|
| Player | playerRoot |
The rigged avatar (VRM or GLB) |
| World | worldRoot |
The environment splat |
| Props | propsRoot |
Interactable mesh props |
An avatar load never replaces the world or its props; a world load never replaces the avatar.
XR interaction, Galaxy XR
Mesh props are grabbable in the main app (/), through SceneManager’s IWSDK Option A: distance and proximity grab, thumbstick locomotion, and a grip that opens a context menu on a hit or pans on a miss.
| Input (Galaxy XR) | Main / session |
|---|---|
| Trigger (select) | Grab, distance and proximity |
| Grip (squeeze) | A ray hit opens a right-click/model menu; a miss pans |
| Right stick | Locomotion, or teleport aim |
The /xr IWSDK lab stays for regression testing (iwsdkWorldPackage.js):
| Input | IWSDK component | Galaxy XR action |
|---|---|---|
| Far grab | DistanceGrabbable |
Aim, then trigger |
| Near grab | OneHandGrabbable |
Walk up, then grip squeeze |
Environment splats are visual only, through Spark.js. An optional environment.collider_url supplies a walk mesh for locomotion, since a splat alone has none.
Open a world directly on a headset:
https://<PC-IP>:3000/?worldManifest=/worlds/my-world/world.manifest.json
Or from World Library’s own “XR” button. Implementation: worldSceneLoader.js for the main / session, iwsdkWorldPackage.js for the /xr lab.
API jobs and the Redis TTL
POST /api/v1/world-generation/image-to-world registers the job in Redis, with roughly a 24-hour TTL; the on-disk outputs stay under 3DAIGC-API/outputs/worlds/{job_id}/ regardless of that TTL.
A walked, Galaxy-XR physical-replica scan uses POST /api/v1/world-generation/environment-scan (LingBot-Map). Passing metric_calibration makes environment.transform.scale read in real meters, one to one; the manifest’s own metadata then carries metric_calibration.one_to_one and coordinate_units: "meters".
Phase A and Phase B: refine_to_3dgs: true gives isotropic Spark Gaussians (Phase A). train_3dgs: true (or a later POST /train-3dgs call) runs a photometric gsplat train, at 7 or 10,000 steps, densify off. The door metric uses mode: reference_length, axis: horizontal, true_meters: 0.762 (30 inches), measured against recon_length. Gravity alignment uses floor RANSAC by default (prefer_floor); a wall-heavy close-up falls back to camera-up instead. The client loads LingBot Gaussians with orientationMode: 'none'. See 3DAIGC-API/docs/LINGBOT_MAP_ENVIRONMENT_SCAN.md for the full scan pipeline.
worldSceneLoader.js routes by source type, not by pipeline name. An environment.type of gaussian_splat or spark, or any Gaussian source, loads through the Spark loader only; routing it through the point-cloud loader instead scatters the Gaussian PLY. point_cloud or points loads through the XYZRGB point loader instead. A gravity-aligned LingBot Gaussian world skips anchorObjectBottomToFloor; running it hoists the whole room, since the scan is already floor-aligned. LingBot Gaussians also skip TripoSplat’s own 180-degree X flip.
Floor-Y computation, world layers only
computeXrFloorAlignmentY reads bounds from playerRoot, worldRoot, and propsRoot only, never every child of the XR scene wrapper; including viewportGridHelper or viewportAxesHelper in that bounds pass reintroduces a roughly 1 m lift. shouldSkipXrFloorWrap() excludes the grid, the axes, the skybox, and other helpers from XR wrapping for the same reason. A rigged or VRM avatar’s own floor bounds come from getViewportFloorAnchorBounds(..., { meshFeetOnly: true }), not a full-scene setFromObject; a multi-skin VRM0 passthrough upload needs its mesh-feet bounds specifically, since an armature-only or hips-only bounds box misplaces the feet. A correct computation logs [XR][floor] with boundsMinY near zero and the wrapped source names; a boundsMinY near 1, or a wrapped-object count that includes a helper, is the regression signal.
If the API answers 404 for a job whose files still exist on the DGX, rehydrate Redis there directly:
/home/sifr/3DAIGC-API/venv/bin/python \
/home/sifr/Weftspun3DStudio/scripts/dgx-rehydrate-world-job.py <job_id>The client (worldPackage.js) builds manifest URL candidates itself and surfaces a clearer 404 hint when a rehydrate may help.
API summary
POST /api/v1/world-generation/image-to-world: a DGX-local pipeline, TripoSplat plus optional TRELLIS props.
POST /api/v1/world-generation/environment-scan: a walk video, or three or more frames, into a LingBot-Map world package, with an optional Phase A/B 3DGS pass and metric_calibration for real, one-to-one meters.
POST /api/v1/world-generation/train-3dgs: a Phase B gsplat train, against an existing environment-scan world’s own gs_dataset/.