Restructure the client into layered crates: state layer, Bevy-free geo crate, and crate renames #16
Labels
No labels
agent
agent:ci
agent:done
agent:failed
agent:needs-input
agent:refined
agent:refining
agent:running
agent:skip
autonomous
driven
local
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jeroen/cartopolis#16
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Two problems with one root cause.
Builds are slow where the incremental cache does not reach. Measured
2026-08-06 (
docs/notes/client-build-cost.md): a leaf edit rebuilds in 3.8 s,but compiling
viberfoxwith a cold incremental cache takes 52.4 s, of whichonly 7.1 s is the frontend — ~45 s is codegen and linking. Everything that
discards the cache pays that in full: CI (
CARGO_INCREMENTAL=0), profileswitches, flipping
solo/fast-dev/scalar-fallback, and everycargo test.Two structural taxes make it worse:
crates/viberfox/Cargo.tomldeclares no[lib], socargo test -p viberfox --bin viberfoxcompiles all ~52k lines asecond time under
cfg(test); anddevanddev-bevyare both maintained, sotarget/measured 170 GB and alternating between the two commands inCLAUDE.mdrebuilds the other set.systems/has become a flat bin of 50 files holding both the logic and thestate that logic coordinates on. Measured over the module graph
(
docs/notes/module-graph.md): 26 mutual dependency cycles, and thehigh-in-degree modules are imported for a single state type each, not for their
behaviour —
free_camera(in-degree 25) forFreeCamera(
crates/viberfox/src/systems/free_camera.rs:16) andWORLD_CELL_EDGE(
free_camera.rs:382);map_stream(24) forMapStream(
map_stream.rs:153);globe(20) forGlobeState(globe.rs:146) andHideInSpace(globe.rs:549);data_layers(17) forDataLayers(
data_layers.rs:305);egui_manager(16) forEguiManager(
egui_manager.rs:45).resources.rsandcomponents.rswere meant to be thatlayer, but 33 of the 50 system files declare
Resource/Componenttypes too.These are the same problem: because the shared types are filed next to their
systems, the crate graph cannot be split (Rust crates cannot be cyclic), and
7,313 Bevy-free lines carrying 69 of the crate's 290 tests are compiled and
linked inside a Bevy binary.
Approach
Four phases, in order. Each is independently landable and independently
valuable; stop after any of them.
Phase 0 — build wins with no architecture risk.
[lib]target tocrates/viberfox;main.rsbecomes a thin bin overit, so tests link the rlib instead of building a second full copy.
devanddev-bevyinto one dev profile; update the commands inCLAUDE.mdanddocs/guides/development.mdto match.debug = "line-tables-only"for workspace crates in dev — keeps backtraces,cuts the codegen that dominates the 45 s. CI already sets this.
Phase 1 — give
systems/a shape. Puremodmoves, no code changes:Phase 2 — the layering. This is where the cycles die.
crates/state/(packageviberfox_state): everyResource/Componenttypeand shared constant, absorbing today's
resources.rsandcomponents.rsplus the hub types listed above. It is a crate rather than a module because
that is the only thing that makes re-filing a type next to its system a
compile error — a module boundary cannot enforce it, which is how the drift
happened.
crates/geo/(packageviberfox_geo): the Bevy-free half —vector_tiles,routing,road_texture,shot_presets,utils/sun. Fixes the invertedarrow at
vector_tiles.rs:2297, where an MVT decoder callsvegetation::scatter_vegetation.vibe_sim→simulator. Rename thevibe_coredirectory tocrates/core/with package nameviberfox_core— a package literallynamed
coreshadows the sysroot crate in every dependent(
docs/notes/module-graph.mdhas the verified error).Phase 3 — only if phase 2's measurements justify it. Split the Bevy half
into
map/nav/uicrates to fan codegen across cores. Deliberately last:it is the most work and the least certain return, and phase 2 may be enough.
Acceptance criteria
cargo test -p viberfoxruns the suite without compiling the crate twicetarget/well under the measured 170 GB after a clean rebuildsystems/has no more than ~8 entries at its top levelcrates/geobuilds with nobevydependency, andcargo test -p viberfox_georuns its 69 tests without building Bevy
ResourceorComponenttype is declared outsidecrates/statedocs/notes/module-graph.md)core(pkgviberfox_core),geo,state,simulator,viberfox,big_spacedocs/notes/client-build-cost.mdupdated with post-change measurements bythe same method
Verification
cargo check --workspace --all-targetscargo test -p viberfoxandcargo test -p big_space --lib— the suite isthe guard here, not
cargo check, which does not compilecfg(test)codecargo shots— the README image set must be byte-comparable in content; thisis a refactor with no intended visual change, so a diff in any of the six
images means something moved
docs/notes/client-build-cost.mdon the same machine(22-core workstation) so the before/after is method-comparable
Out of scope
fixing, that is a separate ticket.
that.
crates/big_spacefork — untouched.web-release), which is separately tuned anddocumented in the workspace
Cargo.toml.Branch:
refactor/16-client-crate-layeringPhase 2's state crate was attempted on 2026-08-06 and reverted. The plan above underestimated it, and the correction is load-bearing for whoever picks this up.
The hub types are not state — they are streamers wearing a
Resourcehat. Counting fields that carry machinery (aCellStream, aHashMapkeyed toEntity, an assetHandle):GlobeStateDataLayersMapStreamDriverStateBuildingCollidersTerrainFieldSo the three highest-in-degree types are exactly the three that cannot move.
GlobeStatealso callsfree_camera::WORLD_CELL_EDGEandutils::sun::smoothstepfrom its own methods; moving it produced 33 errors.The method mistake is worth repeating so it is not repeated: the struct was judged portable by reading its
impl, which is self-contained, without reading its fields, which are not.Revised precondition. Each hub needs a split, not a move — the state half everyone reads separated from the machinery half one system owns, exactly the operation
vegetationneeded (870 pure / 130 ECS). That is three non-trivial refactors ahead of the state crate itself. The genuinely portable types break only 2–3 of the 22 remaining cycles on their own, because most cycles have one of the big three on the other side.Detail and measurements:
docs/notes/module-graph.md.Done so far (all on
main, verified, tests green):fd72850line-tables-only—0f82945systems/grouped into folders —30c3e96crates/geoextracted; edit→60 tests in 2.2 s —3ef118esimulator,viberfox_core—ac06d22Unrelated but measured: KDE's
baloo_fileindexestarget/— 591,959 files here, out of 1,088,961 in its whole index. It ignoresCACHEDIR.TAG. Addingtargettoexclude filtersin~/.config/baloofilercis a free win and makes build timings trustworthy again; load average was 12.7 during this session, which invalidated a full-compile measurement.