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
jdwith aprocesssubcommand alongsidebuild. 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.