S4 Projection Layer — USD Scene Authorship: Lessons Learned
Audience: Engineers new to the GaiaFTCL S4 / C4 spatial projection pipeline.
Updated: 2026-05-05
---
What the S4 layer is
The M⁸ manifold has eight dimensions: four structural (S1–S4) and four constitutional (C1–C4).
S4 is the observable dimension — the thing you can *see* in the 3-D window.
Every USD scene is a projection surface for the S4 dimension. RealityKit renders it; the C4 system overlays live constitutional state on top of it.
A scene is not decorative. It is the spatial substrate that findEntity(named: entry.scope) anchors the CatalogAnchor entity onto. If the USD hierarchy is wrong the app enters TERMINAL STATE: REFUSED and shows nothing.
---
The catalog contract
FranklinSceneDirector.catalog is the single source of truth. Each entry has:
| field | purpose |
|---|---|
sceneID |
key used by activeSceneID, domain portals, and the NATS routing layer |
file |
base name with .usda extension — stripped by dropLast(5) in loadStrictS4Projection |
scope |
must exactly match the second-level def Xform name inside the USDA file |
franklinCue |
the single line of text rendered at the bottom of FranklinSceneView — write it for a researcher, not a developer |
Lesson 1 — scope mismatch is silent until runtime.
The first LithographyDomain.usda was authored with def Xform "LITHO" but the catalog had scope: "LITHOGRAPHY". findEntity(named:) returns nil, the scene throws topologyAnchorNotFound, and the app shows REFUSED. The catalog is the contract; the USD must spell the name the same way.
---
Required USD hierarchy for every scene
def Xform "<FileName>" (kind="group") ← defaultPrim
def Xform "<SCOPE_NAME>" (kind="group") ← scope — must match catalog entry
def Xform "CatalogAnchor" ← this is where manifold sphere + portals attach
…geometry children…
CatalogAnchor is found by name in materializeS4Projection and passed to ensureOverlayEntities. If it is absent, the manifold sphere and domain portals are mounted on the scope root instead — they still appear, but their spatial origin is wrong.
Lesson 2 — CatalogAnchor is the M⁸ origin, not just a placeholder.
The sphere that shows live C4 state, the cone of safety, and the domain portal planes are all parented to CatalogAnchor. Place it at the physical centroid of the domain's primary geometry.
---
macOS vs visionOS loading
#if os(visionOS)
rootScene = try await Entity(named: fileName, in: realityKitContentBundle)
#else
guard let url = Bundle.module.url(forResource: fileName, withExtension: "usda") else {
throw S4ProjectionError.sceneLoadFailed(fileName, "not found in module bundle")
}
rootScene = try await Entity(contentsOf: url)
#endif
Lesson 3 — Bundle.module is the SPM package bundle, not Bundle.main.
Resources declared as .process("Resources") in the target's Package.swift are copied into a <TargetName>_<TargetName>.bundle sub-bundle at build time. Always use Bundle.module to locate them. Bundle.main will miss them in both debug and release builds.
Lesson 4 — #if canImport(RealityKitContent) does not work as a platform guard.
On macOS, RealityKitContent is a local SPM package that *does* import. The canImport check therefore passes on macOS and the code attempts Entity(named:in:realityKitContentBundle) — which fails because there is no compiled .rkassets bundle on macOS. Use #if os(visionOS) instead.
---
USD geometry: write for researchers, not for fill
Every quantum scene must make the algorithm *legible* from the geometry alone. A researcher opening the scene in Reality Composer Pro or usdview should understand which algorithm family they are looking at before reading any label.
Circuit family (QC-CIRCUIT-001 · 5 algorithms)
- Five horizontal wire buses (one per qubit) spanning the full scene width.
- Gate boxes positioned along the wires: Hadamard (blue), RZ rotation (orange), T-gate (gold), CNOT (two-qubit vertical connector), Measurement (detector cone at output).
- The wire layout is the same circuit topology used in quantum advantage demonstrations — not schematic, not symbolic.
Variational family (QC-VARIATIONAL-001 · 4 algorithms)
- A height-field mesh of a Rastrigin energy landscape (the canonical VQE/QAOA cost function).
- A parametric optimizer trajectory path (VQE gradient descent spiral) embedded in the landscape.
- Ansatz layer boxes behind the landscape showing the depth-4 circuit structure.
- Lesson 5: The energy surface mesh has
~3600 faces. RealityKit on macOS renders this without issue at 60 fps. Do not pre-decimate unless you measure a frame-time regression.
Linear algebra family (QC-LINALG-001 · 3 algorithms)
- A 4×4 grid of spheres representing matrix elements (HHL / QSVT / qPCA input).
- Radial arms showing eigenvalue spectrum (QPE register output).
- The matrix grid makes block-encoding immediately visible to anyone who has read the QSVT paper.
Simulation family (QC-SIMULATION-001 · 2 algorithms)
- A 4×4 Heisenberg spin lattice with nearest-neighbour bond cylinders.
- Trotter step markers (horizontal planes) stacked above the lattice to show time evolution depth.
- Lesson 6: Trotter step count appears in the contract's
aesthetic_rules_jsonasmax_trotter_steps. The geometry depth (10 planes) encodes this frontier directly in the S4 projection.
Bosonic family (QC-BOSONIC-001 · 2 algorithms)
- A harmonic potential surface (Fock space ground).
- Six flat planes at integer heights representing Fock state occupation levels |0⟩–|5⟩.
- A beam splitter geometry (two intersecting cylinders at 45°) for GBS interferometer topology.
Error-correction family (QC-ERRORCORR-001 · 3 algorithms)
- A 5×5 surface code lattice: data qubits (cyan spheres) and syndrome qubits (orange spheres) in the standard checkerboard pattern.
- Stabilizer plaquette frames (wireframe boxes) around X and Z stabilizers.
- Red/blue error markers placed at known error positions so the syndrome extraction is legible.
Quantum proof scene (QUANTUM-PROOF-001 · projection probe)
- A Bloch sphere (tomography surface) at centre.
- Four S4 measurement arms at ±45° (structural dimensions).
- Four C4 projector planes orthogonal to the arms (constitutional dimensions).
- This scene does not represent an algorithm — it is the M⁸ proof surface for sovereignty attestation (OQ-QM-001 through OQ-QM-007).
---
The 19-algorithm count
The OQ test OQ-QM-007 asserts SUM(algorithm_count) = 19 across all language_game_contracts rows where domain LIKE 'quantum%'.
| family | algorithms | count |
|---|---|---|
| QC-CIRCUIT-001 | Shor, Grover, QFT, QPE, AmplitudeAmplification | 5 |
| QC-VARIATIONAL-001 | VQE, QAOA, VariationalClassifier, QuantumAnnealing | 4 |
| QC-LINALG-001 | HHL, QSVT, qPCA | 3 |
| QC-SIMULATION-001 | QuantumWalk, HamiltonianSimulation | 2 |
| QC-BOSONIC-001 | BosonSampling, GaussianBosonSampling | 2 |
| QC-ERRORCORR-001 | SteaneCode, SurfaceCode, TopologicalQEC | 3 |
| Total | 19 |
The algorithm_count column is set in SubstrateSchema.swift migration v11_language_game_algorithm_count. The USD scenes and catalog entries are the spatial face of this data — the geometry count must agree with the database count.
Lesson 7 — USD scenes, catalog entries, and language_game_contracts rows form a three-way contract.
If you add an algorithm: (1) update the algorithm_count in the migration, (2) add or update the corresponding USD scene, (3) update the franklinCue in the catalog. All three must change together or the S4 projection diverges from the substrate truth.
---
Adding a new scene
1. Write the USDA file following the hierarchy above (<FileName>/<SCOPE>/<CatalogAnchor>/…).
2. Place it in Sources/GaiaFTCL/Resources/ (macOS SPM bundle, loaded by Bundle.module).
3. Copy it to Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/ (visionOS Reality Composer Pro bundle).
4. Add a SceneEntry to FranklinSceneDirector.catalog with the correct scope name.
5. If it is a quantum domain: add a language_game_contracts row in a new SubstrateSchema migration with algorithm_count set correctly.
6. Run swift build --target GaiaFTCLScene — zero errors = catalog is syntactically correct.
7. Run swift run GaiaFTCLApp — select the new scene from the domain portal — confirm no REFUSED state.
---
Common failure modes
| symptom | root cause | fix |
|---|---|---|
REFUSED: topologyAnchorNotFound |
scope in catalog ≠ def Xform name in USDA |
Align the two strings |
REFUSED: sceneLoadFailed … not found in module bundle |
File not in Sources/GaiaFTCL/Resources/ or wrong extension |
Check filename + Resources directory |
| Manifold sphere appears at origin (0,0,0) | CatalogAnchor entity absent from scene |
Add def Xform "CatalogAnchor" as child of scope |
| Scene loads on visionOS, fails on macOS | #if canImport(RealityKitContent) guard used instead of #if os(visionOS) |
Replace with os(visionOS) |
OQ-QM-007 FAIL: algorithm_count sum ≠ 19 |
New algorithm added without updating migration | Add UPDATE language_game_contracts SET algorithm_count = N WHERE game_id = '…' |
---
USD REFUSED state declarations (2026-05-05)
When a USD prim references external assets or capabilities that are not yet
authored, the correct approach is to declare an honest REFUSED terminal state
in the USD — not to omit the prim.
Lesson 8 — An absent prim is worse than a REFUSED prim.
An absent prim silently changes the scene graph topology. REFUSED is a first-class
terminal state. A REFUSED prim with a vqbit:closureCondition tells every future
engineer exactly what is needed to close it to CALORIE.
Avatar prim pattern (Franklin.usda)
def Xform "Avatar" (
doc = "Franklin avatar slot. Currently REFUSED — placeholder geometry."
customData = {
string "vqbit:primID" = "FRANKLIN-AVATAR-PLACEHOLDER-001"
string "vqbit:rigState" = "REFUSED"
string "vqbit:rigStateReason" = "asset_not_authored"
string "vqbit:closureCondition" = "Replace Cube with rigged FranklinFigure.usdz; flip rigState to CALORIE"
bool "vqbit:requiresClosureReceipt" = 1
}
) {
custom token vqbit:rigState = "REFUSED" (
allowedTokens = ["CALORIE", "CURE", "REFUSED", "BLOCKED"]
)
def Cube "Placeholder" { double size = 0.4 }
}
Attachment state pattern (VQbitUIProtocol.usda)
For portal prims whose RealityKit .attachments binding is not yet implemented:
customData = {
string "vqbit:attachmentState" = "REFUSED"
string "vqbit:attachmentStateReason" = "single_realityview_owner_refactor_pending"
string "vqbit:closureCondition" = "Promote to single RealityView with .attachments anchored to portal entity; flip to CALORIE"
}
custom token vqbit:attachmentState = "REFUSED" (
allowedTokens = ["CALORIE", "CURE", "REFUSED", "BLOCKED"]
)
Lesson 9 — allowedTokens makes terminal states machine-readable.
Tools, test suites, and the qualification runner can grep for the set of allowed
values and assert exactly one is present. Without allowedTokens, the token
field is free text and cannot be validated programmatically.
Integrity guard
Scripts/check-no-duplicate-franklin-usda.sh fails the build if more than one
Franklin.usda exists in the repo. The authoritative copy is always
Sources/GaiaFTCL/Resources/Franklin.usda. The Packages/RealityKitContent/
copy was deleted in the 2026-05-05 session.
Lesson 10 — Two copies of a sovereign scene file is a data integrity violation.
Both copies diverge on the first edit. Delete the non-authoritative copy and
add a build guard that fails if it reappears.
---
S4 as world — the architectural correction (2026-05-10)
The first AlignmentImmersiveScene used Franklin.usda's Sovereign group as a
backdrop: a 0.24m-radius floating plinth with a rotating disc, AuraRing, and four
S4 arms. Instruments were spawned on top of the plinth. This was the wrong
abstraction at every level.
S4 is not a stage. S4 is the world the language game inhabits.
A child measuring soil temperature stands on a farm. A plasma physicist stands in
a control room. A doctor stands in an ED bay. The S4 projection space IS what the
operator can see. The whole visible world of the game. C4 does not live at the
centre — C4 enters from above as sky, ceiling, light.
The floating plinth abstraction smuggled the old "stage performance" metaphor into
the substrate: instrument on pedestal, abstract background, operator watching. This
is the wrong model. The operator IS in the world. The instrument is grounded in the
world's surface. The world's physical details encode S4 dimensions.
Lesson 11 — S4 is the world, not a stage.
Every USD scene serving as a language game backdrop must be a physical environment
the operator inhabits — not a platform they observe. The test: can the operator
describe what room they are in without being told? If not, the scene fails.
Lesson 12 — USD world authorship principles for S4 environments.
1. Ground at y=−0.475. All world scenes use soil/floor at y=−0.475. Instruments
land with bulb/base at y≈−0.46 (touching or slightly below the surface).
2. C4 is the sky. The SkyDome (Y-scale=−1 inverted sphere, radius 7–10m) at
world origin IS the C4 layer. Light enters from above. Do not put C4 geometry
on the floor or at the sides.
3. S4 dimensions encode in scene elements. Map every world element to an S4
or C4 dimension explicitly in the USD prim name and in this doc. S1 = structure
(soil, walls), S2 = temporal (clock, sun, mist), S3 = spatial (fence, room
boundary), S4 = observable (thermometer, display, diagnostic output).
4. No floorPlane() when a world is loaded. The world file provides the floor.
floorPlane() is a fallback for turns with no world file.
5. Supporting elements are not decoration. Farmhouse, tractor, fence — each
one places the operator in a context that justifies why this particular
measurement matters. The geometry is a constitutional argument.
6. Author at 1:1 scale. A 9×8m field is 9 metres wide. A thermometer tube
is 0.55m tall. Real-world scale in a 1.2m-from-camera scene forces the operator
to feel the measurement, not observe it abstractly.
Lesson 13 — FarmDawn.usda as the canonical example.
Sources/GaiaFTCL/Resources/FarmDawn.usda is the reference implementation of
a sovereign S4 world. Read it before authoring ControlRoom.usda or ED.usda.
Pay attention to:
- How the SkyDome radius (7.5m) is sized to surround all scene geometry
- How the MeasurementStick prim marks the instrument landing point
- How Mist and Bird encode s2_temporal and s4_observable without commentary
- How Fence at z=−2.90 creates a spatial boundary without enclosing the camera
ea0df4d9f9a65c89e68e5dd6f2618c65f43dfefb050cbfdfc7700d91cb415678.
This page serves with a substrate-honest pending-signature notice until the operator's Franklin signer cosigns it.