# ADR 0044: Sandboxed FBX External Texture Dependencies

## Status

Accepted

## Context

FBX texture references may use sibling folders such as `textures/`, exporter-created `.fbm/`
folders, absolute workstation paths, Windows separators, or repeated ufbx texture elements that
resolve to the same file. Blacksite previously discovered only files already present in a sibling
`{model}.fbm/` directory. The committed painted-chair FBX instead declares three files under
`textures/`; none were committed, so source-material thumbnails queued the same missing Bevy image
paths more than once.

Import, manifest generation, project validation, editor previews, and the runtime loader must agree
on one dependency graph. That graph also crosses a security boundary: an imported source must not
read or copy a parent-traversing path or an arbitrary absolute path outside its bundle. A missing
source texture is required when any stable model slot selects Source, but it can be an intentional
authoring condition when every slot selects a project Material/Instance or Default.

## Decision

The local `bevy_ufbx` compatibility crate owns FBX external-texture path discovery and
normalization. It converts separators to `/`, preserves safe sibling and `.fbm/` layouts,
deduplicates normalized paths, and rejects parent traversal, asset-path syntax, and absolute paths.
An absolute exporter path may be portably rebased only when it contains a `.fbm/` suffix; only that
sidecar-relative suffix is retained.

The FBX loader performs a discovery parse, drops the non-`Send` ufbx scene, reads every unique safe
dependency through Bevy's `LoadContext`, and then performs normal synchronous scene processing.
External images become FBX labeled assets from those bytes. Repeated texture elements share one
image handle. Missing, rejected, or undecodable references are skipped with one consolidated
loader warning instead of deferred asset-server requests.

Static-mesh manifests are the authoritative model import dependency graph. Their existing
`source.dependencies` list records every parsed FBX external texture, including unavailable files.
Project validation checks that list without loading native assets. Missing FBX textures are a
blocking consolidated finding while any slot selects Source and an informational consolidated
finding when every slot selects Project/Default. The former whole-model Authoring Override value is
accepted only as registry-v1 migration input and expands to explicit Default selections. Unsafe
paths remain blocking under either selection state. Model registry records
mirror the list for editor details, but validation does not duplicate it when a generated static
manifest exists.

File -> Import Assets treats an FBX plus its referenced textures as one bundle. The importer
preflights every source path and canonical containment before touching the project, rejects
destination paths with symlinked ancestors, stages only the referenced files, preserves their
normalized relative layout, backs up overwritten destinations, and rolls back a partial publish.
Unreferenced `.fbm` contents are not copied.

The Asset Browser renders FBX model and mesh thumbnails through the neutral direct mesh path.
Source-material thumbnails preflight the authoritative dependency resolver first and cache one
non-retryable actionable failure when required textures are unavailable. Dependency rows expose
present/missing state. The committed painted-chair fixture's legacy Authoring Override migrates to
per-slot Default selections and remains untextured; its three source paths stay visible in
manifests, validation, and asset details.

## Consequences

- FBX sibling `textures/` and `.fbm/` bundles import and validate with identical normalized paths.
- Missing texture files are visible before native loading and cannot create repeated Bevy
  asset-server errors.
- Any Source slot fails release validation when its FBX textures are absent. An all-Project/Default
  model remains release-valid but retains an informational dependency finding.
- Absolute exporter paths, traversal attempts, and destination symlink redirects cannot read or
  copy arbitrary host files.
- FBX files with external textures are parsed twice during loading so no non-`Send` ufbx scene
  crosses an async read. Files without external textures retain a single parse.
- External images are labeled children of the FBX and reload the owning FBX through loader
  dependency tracking. Per-image import metadata is not yet exposed.
