Forward-looking work, roughly ordered by what unblocks or de-risks the most. Present tense; this is intent, not history.
The toolchain is pinned to nightly-2026-05-01 (rustc 1.97). atomics + -Z build-std are nightly-only (still true in 2026 — unavoidable, not debt), so a nightly pin is required. (The dep stack — hecs 0.11, rand 0.9, wgpu 29, wasm-bindgen 0.2.122, no indexmap pin — is current; see Cargo.toml.)
WASM threads on this nightly require the full link-arg set in .cargo/config.toml. lld no longer auto-exports the TLS-init symbols, and wasm-bindgen-rayon needs an imported shared memory. The wasm target therefore builds with target-feature=+atomics,+bulk-memory plus link args --shared-memory --max-memory=1073741824 --import-memory and explicit --export= of __heap_base, __wasm_init_tls, __tls_size, __tls_align, __tls_base — mirroring wasm-bindgen's own threading reference build. Without the --export=__wasm_init_tls family, wasm-bindgen fails with failed to find __wasm_init_tls; without --import-memory/--shared-memory, initThreadPool fails at load (DataCloneError: #<Memory> could not be cloned). The CI workflow's pinned nightly must stay in sync with rust-toolchain.toml: the workflow installs rustfmt/clippy/wasm components for its own pinned value, so a mismatch fails the build.
The world.get::<&T>() storm is gone: the grid rebuild also builds a per-tick NeighborCache (systems/mod.rs) of each entity's hot fields, so movement, interaction, and nearest-N selection read one cached snapshot per neighbour instead of a scatter of component fetches. The grid itself is a HashMap of cell → entities, rebuilt serially.
What remains is a smaller, more invasive refinement: replace the cell HashMap with a dense fixed grid built by counting sort (lock-free, contiguous per-cell runs, no hashing), keyed by a dense slot id so neighbour data is fully contiguous (true SoA). The world is bounded, so a flat grid fits. This is the step from "good locality" to "optimal locality" — measure it against the headless bench harness before taking on the churn.
The crate is cdylib-only, so there's no native way to profile or benchmark. Add "rlib" to crate-type, an examples/headless.rs that runs N ticks from a fixed seed (now possible — the sim is deterministic), and criterion benches over the per-tick hot loop. Prerequisite for measuring any of the performance work above.
Reaching 100K–1M is a separate GPU-compute engine, not an optimization of the current one: ping-pong storage buffers, a counting-sort spatial grid + prefix sum on the GPU, force/movement compute shaders, and indirect draw. CPU + rayon cannot reach that scale. This sim's per-entity logic (predation, energy transfer, births/deaths = structural mutation needing GPU compaction) makes the port heavier than a plain particle-life sim. Revisit only if the scale is genuinely wanted.
- Replace the
sedcache-busting.scripts/build-web.shinjects a git-SHA?v=query via a chain ofsedrewrites — brittle string-surgery on generated output, and largely redundant withweb/_headers. As a first step, deduplicate it: run every rewrite onpkg/first, thencp -r pkg web/last so the web copy inherits the patched files (removes the duplicatedweb/pkgrewrite blocks). Longer term, fix the one load-bearing worker-import path viawasm-bindgen-rayon'sno-bundlerfeature, or move to content-hashed filenames / an import map. - One wasm-pack source. CI installs wasm-pack via
curlandpackage.jsonlists it as a devDependency;npm run buildwould prefer the npm copy. Pick one (drop the devDependency, or switch the script tonpx wasm-packand drop the curl step) so the build uses a single known version.
The public URL is the custom domain https://evo.tre.systems (in the README). The underlying Cloudflare Pages project is evo-dgc (Cloudflare suffixed evo because the name was taken) and evo-dgc.pages.dev also serves the app. Optional cleanup: rename the Pages project to reclaim evo.pages.dev, or leave it — the custom domain is the canonical entry point.
- WebGPU-unavailable UX. Replace the 5-second error toast with a persistent "WebGPU required" message, and request
downleveldevice limits so low-end adapters degrade rather than fail. (The renderer is WebGPU-only — thewgpuwebglfeature has been dropped — so a real WebGL2 fallback would be a deliberate re-addition, only worth it for broad reach.)
- Split
systems/movement/mod.rs(~390 lines, over the 200-line guideline). Extract the neighbour-accumulation loop and the flocking/solitary force helpers fromupdate_movement. Pure refactor — preserve arithmetic order so seed-determinism is unchanged. (simulation/mod.rsalready had its seeding helpers pulled intosimulation/rng.rs.) - Trim unused stats.
SimulationStats::from_worldcomputesentity_counts, per-colour classification, several averages, andworld_center_drifton everyget_stats(), but the UI consumes none of it (the population HUD that briefly usedtotal_entitieswas removed). Either surface them in the UI or trim the struct (crosses the WASM boundary, so updatelib.rs/JS/tests together).
- Split the remaining event wiring. Sliders are now table-driven (the
SLIDERStable →setupSliders()), butsetupEventListenersstill inlines the panel-drag, keyboard, and camera wiring; extract those into focused methods too. - Consider TypeScript + prettier for
web/js/app.js(bundle via a build-step migration, not standalone).
- Convert the remaining drift/bias harnesses to assertions. The simulation-level ones (
test_simulation_clustering,test_drift_direction_analysis) now assert bounded drift via a sharedcentroidhelper, and the no-optest_simulation_entity_processingis gone. The movement/interaction drift + bias harnesses still onlyprintln!— give them the same treatment (assert "drift < ε over N ticks"; seed any that usethread_rng). - Property tests (
proptest) for gene mutation bounds, energy clamping, and HSV↔RGB round-trip. - A single Playwright smoke test in CI (page loads, canvas present,
#seed-displayset, no console errors).
Longer-horizon mechanics, in rough order of appeal: environmental terrain and localized resource patches; aging and disease/parasites; mating rituals and territorial behaviour; and multi-species symbiosis / food webs. Each extends the systems in src/systems/ and the gene model in src/genes/.
A change is done when:
- It passes the verification gate in AGENTS.md (the pre-commit hook enforces it).
- Docs describing affected behaviour are updated to match.
- For user-visible changes: pushed, CI green, and smoke-tested on the live site.