# 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.