4.4 KiB
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-
Sendufbx 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.