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

"My model has no collision even though it loads fine"

✓ verified on engine 26.07lane: Getting art inposted
▸ SYMPTOM
  • Model renders correctly; Model.Load succeeds; IsError is false.
  • Players walk straight through it.
  • Traces / raycasts against the mesh miss every time.
  • Adding a ModelCollider component changes nothing — still no shapes.
▸ CAUSE

A vmdl with only a RenderMeshFile compiles clean but has zero collision. A ModelCollider on that model creates no shapes at all.

"Model loads clean / IsError false" proves render, never collision.

For per-poly collision you need a PhysicsShapeList whose child is a PhysicsMeshFile (concave, static-safe) with the same filename + import_scale as the RenderMeshFile. PhysicsHullFile exists too (convex) — wrong for tapered / shelved shapes.

▸ FIX

Detect hollow models in code before trusting a per-poly branch:

snippet
int physParts = model.Physics?.Parts?.Count ?? 0;
if (physParts == 0)
{
    // fall back to BoxCollider from bounds — do not attach ModelCollider
}

On disk: a compiled .vmdl_c whose PHYS block is a ~323-byte stub is hollow; real mesh physics runs tens of KB (parse the Source 2 block directory at offset 8).

Author physics in the vmdl (same mesh path + scale as render):

snippet
{
  _class = "PhysicsShapeList"
  children =
  [
    {
      _class = "PhysicsMeshFile"
      filename = "models/your-project/foo.obj"
      import_scale = 39.37
      surface_prop = "default"
      collision_tags = "solid"
    },
  ]
}

Prefer hand-sized BoxColliders over ModelCollider for decorative props — per-poly collision on clutter is wasted cost and traces get noisy. Compute the box from model bounds at generation time.

For AI-delivered assets that ship render-only, always add a bounds-derived BoxCollider (or bake a physics node into a forked vmdl). See ai-generated-models.

▸ WHY IT WORKS

Render and physics are separate ModelDoc graphs that happen to share a mesh file. The resource compiler will happily produce a drawable model with an empty physics part list. ModelCollider only instantiates whatever physics parts already exist on the compiled model — it does not invent a hull from the render mesh at runtime.

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