Move asset-import staleness cache (hash/size/mtime, importer_version) out of committed .meta into .cache/

Problem

editor/types/MetaFile.zig's MetaFile mixes two very different kinds of data into the same <path>.meta file, which lives in assets/ and is git-tracked:

  • Authored/durable: guid, asset_type, import_settings, sub_assets — identity and artist-configured settings. These must be shared across the team (a stable GUID is required for scene references to resolve identically for every clone), so committing them is correct.
  • Pure staleness cache: source_hash, source_size, source_mtime_ns, importer_version — exist only so AssetImporter.needsReimport/importAsset's fast path can skip rehashing an unchanged source file. Nothing reads these as "designed" data; they're fully recomputable from the source file and the current importer code.

Keeping the cache fields inside the committed file causes real problems:

  1. Git doesn't preserve mtime. Every fresh clone/checkout gets checkout-time mtimes, which won't match whatever a teammate's .meta last recorded source_mtime_ns as. The very first import after any clone is guaranteed to see a stat mismatch and rewrite the .meta — a real tracked-file diff caused purely by checking the repo out, with zero relation to any actual asset edit.
  2. Any bug that causes a spurious ensureMeta/writeArtifact rewrite pollutes git status for the whole team, not just the machine that triggered it, because the rewritten file is committed. We just hit exactly this: a case-sensitivity bug in AssetRegistry.lookupByFilename classified uppercase-extension textures (M_StatueGlass_*.TGA, common in real-world asset packs like Bistro) as .unknown forever, which made AssetMeta.ensureMeta mark them dirty and rewrite their .meta on every single scan, which the Studio hot-reload watcher (AssetWatcher.zig) then saw as an external change, re-triggering refreshComponents → a full reflect compile → an endless "Compile scripts: done" toast storm and ~6 FPS on the Bistro sample. The classifier bug is fixed, but the fact that a rewrite loop in cache bookkeeping was even capable of dirtying a tracked file is the deeper issue — the next bug of this shape (e.g. #146 (closed)'s already-noted 0-byte-hash duplicate .meta from FBX backslash paths) will have the same blast radius.
  3. Noisy diffs/reviews. Any reimport (importer version bump during active development, Reimport All, a differently-behaving OS/filesystem) touches every asset's committed .meta, even when the meaningful fields (guid, settings) didn't change — real diffs get buried in cache-stamp churn.

Proposed direction

Split MetaFile in two:

  • <path>.meta (committed, unchanged shape minus the four cache fields): guid, asset_type, import_settings, source_deps, artifact_deps, sub_assets.
  • A new per-project stamp store under the already-gitignored .cache/ (sibling to the existing .cache/assets/{guid}{ext} artifacts), keyed by GUID, holding source_hash, source_size, source_mtime_ns, importer_version. Could be one file per GUID (matches the existing artifact-file convention, avoids single-file contention) or one small index file — either is fine as an implementation detail.

Migration is naturally self-healing: since these fields are pure cache, dropping them from the MetaFile schema means old committed .meta files simply stop carrying them on next write (serde ignores/omits unknown fields), and every asset gets treated as needing a fast-path stat check once (worst case: one extra hash pass), never a correctness issue. A follow-up cleanup pass could strip the fields from existing tracked .meta files in one commit for a clean diff, but isn't required for correctness.

Acceptance

  • Cloning the repo fresh and opening the project shows no .meta diffs from import alone (only .cache/ changes, which are gitignored).
  • Bumping an importer version (AssetImporter.VERSION_*) or running Reimport All no longer touches committed .meta files unless guid/asset_type/import_settings/sub_assets actually changed.
  • git status stays clean after opening/importing an already-up-to-date project.
  • #146 (closed) (asset import full-hash pass on open) — same area, different axis: that issue is about when hashing runs, this one is about where the resulting staleness bookkeeping is stored. Worth fixing both, independently.