Perf: per-frame asset re-scan and linear GUID lookups in the render hot path

Part of #158. Phase 2 — no fidelity change.

Problem

assets.uploadNewAssets (subsystems/render/assets.zig:144) runs a full asset re-scan every frame, long after everything has been uploaded and nothing can change.

For each mesh renderer it calls uploadMaterialTextures on every material GUID — 206 of them on Bistro. Each of those calls resolveMaterial, a linear scan of up to 277 cached entries with a memcmp per entry, and then findGpuTexture for each of 5 map slots — a linear scan of up to 633 textures.

All three caches are O(n) scans:

  • findGpuMeshassets.zig:15
  • findGpuTextureassets.zig:21
  • the resolved-material cache — assets.zig:84

That is roughly 3×10⁵ string comparisons per frame to establish that nothing has changed since the scene loaded. It also runs once per renderScene call, so any layout that renders the scene more than once multiplies it.

Scope

  • uploadNewAssets early-outs unless a dirty flag is set. Bumped by: scene load, import completion, invalidateMaterial, invalidateAllMaterials, and the live material override path.
  • Replace the three linear caches with GUID-keyed hash maps.

Acceptance

  • Steady-state frames do no asset scanning at all
  • Newly imported / reimported assets still appear without a scene reload
  • The live material-inspector override still previews unsaved edits every frame
  • Before/after render.upload zone time from #159 (closed)'s harness recorded