Skip to content

101 F2 + F7: objects and materials are validated on receive and on load - #561

Open
AlexZ005 wants to merge 8 commits into
mainfrom
feat/101-schema
Open

AlexZ005 wants to merge 8 commits into
mainfrom
feat/101-schema

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

Roadmap 101 F2 + F7: objects and materials are validated on receive and on load.

Do not merge before 17 Nov (roadmap 101 order).

What changes

  • src/lib/sceneSchema.js (new, pure): the allowlist for three.js object JSON, materials payloads and glTF elements, built on the v1.65.1 load check (loadGuardCore.js).
    • Object, material and geometry types come from fixed lists. An unknown type becomes Object3D, the standard fallback material or an empty BufferGeometry.
    • Each type has a table of the fields three writes for it, each with a value rule. Unknown or invalid fields are stripped.
    • Images are embedded only and size-capped.
    • userData and glTF extras must be plain JSON under a size cap. The keys core and the shipped modules write keep a per-key kind.
    • There are caps on depth, list lengths, numbers, vertices and nodes.
    • Every change is a {path, reason} row, counted in the wire diagnostics with one toast per session.
  • src/lib/loadGuard.js: checkObjectJson, checkMaterialSet and checkGltf run the schema. parseCheckedMaterials is now the one parse for the materials message.
  • wireValidate: the object row (ObjectLoader form) and the objectParameters materials row call the schema's non-mutating verdicts.
  • scripts/check-loaders.cjs (npm run check:loaders, plus a CI step): fails on ObjectLoader(, .parseMaterials( or MaterialLoader( anywhere in src/ outside loadGuard.js.
  • F7: importModuleText is removed (exported, no callers).
  • OPEN-CORE.md: new section "6. Object schema". It is a contract, not a hook: the inventory shows the modules need no registration call.

Evidence

  • Inventory: scripts/schema-inventory.cjs, output kept as tests/fixtures/schema-1.65/inventory.json. It covers 55 files and 2,348 elements: 9 object types, 10 material types, 17 geometry types and 37 userData keys.
  • Fixtures: tests/e2e/schema-capture.test.cjs (a generator, excluded from the battery) wrote capture.tpscene, prefab.json and wire.json on v1.65.0. They cover every Add-menu object, 10 material types, maps, map slots, tiling, params, a multi-slot material and the handshake sync.
  • Unit tests (tests/unit/sceneSchema.test.js, checkLoaders.test.js):
    • Real content gives 0 drops and byte-identical JSON. That covers the templates, the fixtures, the scenes repo, the 37 captured glTF messages and every shipped .glb.
    • An out-of-schema fixture set is cleaned and counted.
    • Full suite: 434 files / 4,968 tests pass.
  • e2e:
    • schema-ab (A/B driver against a v1.65.1 snapshot): 45 scenes (templates, the capture, the scenes repo). The load check changed nothing on any of them. 38 scenes render the same on two base runs. On 32 of those the lane frame is identical to the base (0.000 %), and the worst of the rest is 0.156 %. The football frame differs only in toasts. The other 7 scenes (fluid, water, jelly, castle, forest, film set, game plot) differ between two base runs too, so frames cannot judge them.
    • schema-two-peers (new; battery: optional): green on the lane and on v1.65.1. The connected peer matches exactly. The late joiner matches too, apart from gaps the glTF handshake already had on v1.65.1, with identical counts on both builds: non-PBR materials arrive as MeshStandardMaterial, meshes with a Normal or Depth material are absent, and spaces in names become _. These are a follow-up, not changed here.
    • Touching suites and module flights: wire-hardening, object-sync, load-check, tpscene, sessions, session-zip, late-joiner-floor, dialback-sync, material-types, material-sharing, uv-slots and duplicate-parity are green. placeholders-peers has the same 3 reds on v1.65.1. Module flights (run with the modules helpers' connect step updated for 1.65's Join… button, local copy only): dungeon, dungeon-realms, door-keypad, module-collectible and tutorial-room are green. football, fx-car-blocks, fps-player, module-health and module-waves fail the same checks on v1.65.1. untangle is flaky on this machine: a clean snapshot of this branch passed 2/2, and one without the schema failed 1/2.
  • Checks: check-ratchet 274/37 (the baseline), check:tokens clean, npm run build rc=0, check:loaders clean.

🤖 Generated with Claude Code

AlexZ005 and others added 8 commits November 11, 2026 10:26
…tures

scripts/schema-inventory.cjs lists every object, material, geometry and texture
type, image form and userData key the app constructs, sends and saves (src/,
the modules repo, the templates, the scenes repo, the test fixtures, captured
wire messages). tests/e2e/schema-capture.test.cjs builds every Add-menu object
and every switchable material kind on the base build and records the saved
scene, a prefab element and the outgoing object messages under
tests/fixtures/schema-1.65/ (excluded from the battery: a fixture generator).
inventory.json is the fixture list the object schema is written from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
userModules.importModuleText had no callers in core, the tests or the modules
repo; behaviours compile through behaviours/source.js compileBehaviour with
the caller's importer. CLAUDE.md now names that loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One pure module, built on loadGuardCore's rules, validates every ObjectLoader
JSON and every materials payload in place: object, material and geometry types
from fixed lists (an unknown material type becomes the standard fallback, an
unknown object type an Object3D, an unknown geometry type an empty buffer
geometry), per-type field tables with value rules (unknown or invalid fields are
stripped), embedded images under a size cap, userData keys with their inventoried
kinds and plain JSON under a size cap, and depth / list caps. Every change is a
dropped row with its path and reason.

tests/unit/sceneSchema.test.js: every template, every test fixture (the 1.65
capture included) and, with SCENES_DIR, every scene of the scenes repo
round-trips with nothing dropped and byte-identical JSON; a set of out-of-schema
payloads is cleaned, counted and still parses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ial load

loadGuard.checkObjectJson / checkMaterialSet now run the schema, so every route
that already goes through parseCheckedObject (peer objects, scene loads, imports,
prefabs, levels, the library, history, autosave) and the new
parseCheckedMaterials (the materials message) is validated and counted, with the
schema's changes in the same counters and the same one toast per session. The
thumbnail fallback in sessions.js parses through the checked helper too.

wireValidate: the object row (ObjectLoader form) and the objectParameters
materials row call the schema's verdicts instead of allowing anything.

scripts/check-loaders.cjs (CI + npm run check:loaders): a generic loader call
anywhere in src/ outside src/lib/loadGuard.js fails the build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validateGltfElement (sceneSchema.js): loadGuardCore's glTF rules first, then every
extras block three copies into userData (scenes, nodes, meshes, primitives,
cameras, animations, materials, textures, punctual lights, the document) under
the ObjectLoader userData rules. A parsed document is cleaned in place; a JSON
text or .glb that breaks a rule is refused. loadGuard.checkGltf runs it, so every
glTF load path is counted the same way. Unit: the 37 glTF messages of the 1.65
handshake capture and every shipped .glb pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…schema

tests/e2e/schema-ab.test.cjs: every template, the 1.65 capture and the scenes repo
load with the load check changing nothing, and the first frame (frozen clock, the
saved view) is compared with the base build's (AB_WRITE=1 on the base first).
Battery: excluded as an A/B driver.
tests/e2e/schema-two-peers.test.cjs: A builds every Add-menu kind, ten material
types, maps, a map slot with tiling, params, a multi-slot material, a group and
its duplicate; B (connected) has the same scene, C (late joiner) the same objects,
slots and maps; the load check changes nothing on B or C. Battery: optional.
OPEN-CORE.md section 6: the object schema is a contract, not a hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The handshake sync sends a mesh as glTF, so on the v1.65.1 base too a late
joiner gets non-PBR materials as MeshStandardMaterial, a space in a name as _,
and no mesh with a Normal / Depth material (measured: the same 6 / 2 / 2 on both
builds). The suite now fails on any difference of another kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant