Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Open challenges

Work that is unfinished, partly done, or hard. Nothing here is shipped. If you want to take a swing at any of it, please file an issue first so the approach can be talked through.

Windows dylib hardening

The editor loads extension code as dylibs built against its SDK proxy. On Windows, a PE export table addresses its entries with a 16-bit ordinal, so 65,535 is the ceiling and no linker escapes it. Builds split the runtime into bevy_dylib and jackdaw_dylib beside the SDK facade, so each library has its own table. Measured on the current split, the hottest table is bevy_dylib (~46k exports in the workspace debug profile, ~41k in release); the Jackdaw runtime and the facade are a few thousand and a handful respectively. The release job measures every table and fails above 60,000. Two gotchas the split codified: LTO stays off for Windows binaries, because a PE file cannot import data across a DLL boundary once inlining creates direct references to another library’s statics, and the SDK build disables incremental codegen, which otherwise leaves undefined hidden symbols across the dylib boundary.

What remains is headroom and those link-model gotchas: Bevy’s export surface grows with the engine, and a regression that merges the runtimes again (or drops the split on one profile) would put Windows back against the ceiling.

Where to dig in: keep the release export check green, and watch bevy_dylib’s count when bumping Bevy or widening what extensions share.

Play-In-Editor (PIE) depth

PIE is the “click play to run your game” flow. The process model is settled: the game always runs out of process as its own cargo binary over IPC, with zero play-time compilation once built. Frame streaming into the Game panel, input capture, click-to-select picking, and the Live entity tree all shipped; see Play-in-editor.

What’s not done: deeper live editing (a broader set of component edits riding back into the running game and into the authored scene), richer widget metadata for live values, and protocol maturity across more component types.

Where to dig in: pick one component family that doesn’t round-trip yet and follow it through the IPC lanes.

Upstream BSN alignment

Jackdaw’s scene document is BSN: .bsn files are the authored format, the live in-editor document is the BSN AST, and .jsn survives only as a read-only importer. What remains is staying aligned with the upstream Bevy scene work as its APIs settle, and upstreaming the pieces of jackdaw’s writer that make sense there.

Where to dig in: track the upstream scene-notation APIs and diff them against crates/jackdaw_bsn as they move.

Engine-feature gaps

Compared to other game engines, jackdaw is missing a bunch. None of these are blockers; they’re places where someone with taste in the area could lead. One line each:

  • Animation graph editor. Started in crates/jackdaw_animation, not finished.
  • Particle / VFX editor. Not started.
  • Material graph editor (shader-graph style). Not started.
  • Light baking and lightmap pipeline. Not started.
  • Cinematics / cutscene editor. Not started.
  • Audio mixer. Not started.
  • Localization (i18n). Not started.
  • In-editor profiler / frame-time inspector. Not started.
  • Asset import beyond GLTF (FBX, USD, batch texture compression). Not started.
  • Level streaming for large open worlds. Not started.

If you care about any of these, opening a small “here’s what I’d do” issue is the best starting point.

Asset processing pipeline

Asset processing happens only at editor runtime. To pre-process textures or bake meshes for a CI build, you have to start the editor headlessly.

The second shape below is half built: jd drives the editor’s build machinery from a terminal with build and run. What is missing is a process step and the asset-processing pipeline behind it. The remaining shapes:

  • Split the user’s game into a library plus multiple binaries (run, process), with processing driven from the project’s own binaries. Invasive for the project template.
  • Extend jd with a process subcommand alongside build. Less invasive but more code in jackdaw.

Where to dig in: pick one shape and prototype it against a small game. We’d like to see the workflow before locking in the design.

Single-entity editor-only ergonomics

EditorOnly skips the whole entity from save, so to have a PlayerSpawn marker that ships and a visual indicator that doesn’t, you author a parent (with PlayerSpawn) and a child (with EditorOnly + a mesh).

A single entity cannot carry both, because the save filter is at entity granularity. An EditorOnlyVisuals marker that strips visual components (Mesh3d, MeshMaterial3d, etc) at save time but keeps the entity and its non-visual components would enable single-entity authoring. The cost is a small allowlist of “visual” component types that grows as bevy adds new ones.

Where to dig in: design the allowlist, file an issue, then implement. The semantics are harder than the code.

Brush face children as a custom relationship

Each brush spawns N face child entities for rendering. They carry EditorHidden (so they’re not in the outliner) and NonSerializable (so they’re not in the save). But Children queries on the brush still enumerate them, which means user code that walks brush children sees jackdaw’s implementation detail.

A custom Bevy relationship (not ChildOf) for face entities would solve this. The face entities would be reachable through the relationship but invisible to standard Children queries. The cost is a small per-frame propagation system that reads the brush’s GlobalTransform and writes the face’s.

Where to dig in: the relationship API in Bevy 0.19, and whether this can be done without breaking BrushFaceEntity queries that already work.

A material override on a part of a model

The Material row on the inspector writes a handle onto the entity it is showing. When that entity is a part of a loaded glTF, the scene document has no node for it: the model file is the whole of what the document says about the instance, and the parts under it are spawned by the loader. So the row falls back to wear_until_reloaded, which swaps the handle on the live entity, mints an undo entry, and warns that the pick is kept only until the model is loaded again. Reopening the scene brings back the material the glTF names.

What the document is missing is a way to address a part. The shape that fits the rest of the format is a path of node names from the model root – the Name each glTF node is spawned with, joined by / – carried by a component on the entity that holds GltfSource, so one entity’s overrides sit in one patch under the node the document already has:

jackdaw_scene_types::types::GltfSource { path: "models/town.glb", scene_index: 0 }
jackdaw_scene_types::types::ModelPartMaterials {
    parts: map[("Roof/Tiles", "materials/slate.bsn")],
}

Four pieces make it work. A component of that shape, registered and reflected like the rest of jackdaw_scene_types. A walk that turns an entity under a model instance into its path of names, and the reverse walk that finds the entity a path names. An observer on the spawned instance that re-applies every override once the model’s entities are there, which is the same moment WorldAssetRoot is derived, and again whenever the file changes on disk. And a writer on the Material row that, for an entity with no node of its own, walks up to the model root and writes the entry rather than wearing the handle.

The parts that need care are the ones that decide whether it is worth having. A name is not unique in a glTF, so two nodes called Tiles under different parents are told apart only by the path above them, and a model with two identical siblings is not addressable at all – the override has to refuse that case rather than pick one. A model that is re-exported with renamed nodes leaves entries naming nothing, which wants the same “names nothing the project holds” reporting a missing asset path gets rather than a silent drop. Undo has to take the document entry and the live handle together. And the row has to read the override back, so what it shows after a reload is the path the document holds rather than the material the glTF names.

Where to dig in: the name-path walk and its refusal on ambiguous siblings, which is where the design either holds or needs a stable per-node id from the loader instead.