s&box/field-guide
the symptom, in your words

"s&box won't load my OBJ/GLB"

✓ verified on engine 26.07lane: Getting art inposted
▸ SYMPTOM
  • Dropping an .obj / .glb into Assets/ does nothing useful in a scene.
  • Model.Load("models/foo.obj") fails or never resolves as a usable model.
  • The .vmdl compiles but materials error with something like: unable to resolve X.vmat.
  • Model shows untextured / ERROR after a scripted re-export.
▸ CAUSE

s&box does not load raw OBJ/GLB in scenes. The engine compiles a .vmdl wrapper on load. The mesh file is only a RenderMeshFile (and optionally physics) input referenced by that wrapper.

Material remaps must map both names — "MatName" and "MatName.vmat" — in the DefaultMaterialGroup remaps, or the compiler errors with unable to resolve X.vmat. A MaterialOverride on the renderer is not a substitute.

Related trap: Blender's OBJ exporter with export_materials=False silently drops the usemtl line too (not just the .mtl file). Remaps key off that material-group name, so the mesh compiles untextured.

▸ FIX
  1. Put the mesh under Assets/models/<your-project>/foo.obj (and .mtl if needed).
  2. Emit a sibling foo.vmdl that references it.

Minimal wrapper shape (KV3 text — copy structure from any working hand-authored vmdl in your install):

snippet
<!-- kv3 encoding:text:version{...} format:modeldoc29:version{...} -->
{
  rootNode =
  {
    _class = "RootNode"
    children =
    [
      {
        _class = "MaterialGroupList"
        children =
        [
          {
            _class = "DefaultMaterialGroup"
            remaps =
            [
              { from = "MatName"      to = "materials/your/mat.vmat" },
              { from = "MatName.vmat" to = "materials/your/mat.vmat" },
            ]
          },
        ]
      },
      {
        _class = "RenderMeshList"
        children =
        [
          {
            _class = "RenderMeshFile"
            filename = "models/your-project/foo.obj"
            import_scale = 39.37
            import_rotation = [ 0, 0, 0 ]
            // translation origin as needed
          },
        ]
      },
    ]
  }
}

Exact header GUIDs: copy line 1 from a known-good hand-authored .vmdl (citizen addon or your own pipeline output) — do not invent format versions. See also the modeldoc32 trap in ai-generated-models.

After any scripted OBJ export, grep the output for usemtl and vt . If usemtl is missing, re-inject usemtl <name> before the first f line rather than re-enabling material export (which can write a stray .mtl into a delivery folder you don't want).

Load in code with a root-relative vmdl path, forward slashes, no Assets/ prefix:

snippet
var model = Model.Load("models/your-project/foo.vmdl");

Kick the editor if a new vmdl doesn't show (hot-compile on focus).

▸ WHY IT WORKS

ModelDoc is the compile unit. OBJ/GLB are mesh sources; without a vmdl there is nothing for the resource system to compile into a Model. Remap keys must match whatever string the mesh importer emits for the material slot — some paths emit the bare name, some append .vmat — so mapping both closes the gap. MaterialOverride only swaps at runtime on an already-resolved material group; it cannot fix a failed compile-time remap.

Verified on engine 26.07 — seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?