core
A separate file is useful when it isolates behavior or protects a dependency boundary. It does not necessarily introduce another domain. A boundary should reduce what a maintainer must understand to change that behavior, with tests covering its observable contract.
The root publishes tracked readiness before accepting more configuration. Synchronous renderer creation stays synchronous. Async factories delay configuration and rendering until their renderer is ready. A cancelled unmount keeps the root, while final teardown waits for accepted configuration and React effect cleanup before releasing renderer resources.
Configuration owns declarative inputs. Equal inputs preserve runtime edits, including camera, frame mode, and renderer settings. The store applies size and pixel ratio changes to the camera and renderer.
Renderer ownership belongs to root lifetime. R3F disposes renderers it creates, including factory results. For caller-provided instances, v9 releases render lists and loses the context without disposing the renderer. An async disposal finishes before the unmount callback runs.
Activity connectivity does not own renderer lifetime. Hiding disconnects scene effects and frame subscriptions while preserving scene state and the renderer. Final removal releases the root, including removal while hidden. Suspense gates renderer readiness and scene loading separately, so waiting does not dispose a live renderer.
Promise helpers in core/utils/promise.ts and React helpers in core/utils/react.tsx are infrastructure. They support these contracts rather than forming separate application domains. core/utils.tsx retains the shared scene utilities and compatibility exports. JSX types in three-types.ts describe the public element surface.