GraphOS Skill: graph-world-bevy
Implement GraphOS world runtime in Rust with Bevy ECS/time only, without Bevy rendering, windowing, UI, or asset pipeline.
This skill depends on graph-world for graph modeling and closure validation, and on graph-management for graph inspection, validation, and atomic graph mutations. Use graph-world first whenever the request changes World/Context/Variant/System/Event/EventSystem topology, then use graph-management to read or apply the resulting graph changes.
Scope Boundary
- This skill covers GraphOS project bootstrap plus Rust runtime initialization.
- This skill does not include TypeScript implementation code.
- This skill does not include Bevy rendering stack (
bevy_render,bevy_winit,bevy_pbr,bevy_ui,bevy_sprite, etc.). - If the request includes rendering or frontend integration, hand off to another presentation/runtime skill.
When to Use
- You need a GraphOS world project with Rust runtime instead of TypeScript runtime.
- You need Bevy as ECS/runtime scheduler and time source only.
- You need a deterministic, headless app loop for world/domain logic.
Preconditions
- Complete graph design with
graph-worldfirst. - Finish graph closed-loop validation before runtime coding.
- Use
graph-managementto inspect graph state, validate node types/plugins, and apply any graph mutations before changing Rust runtime bindings. - If Graph changed, regenerate/update GraphOS outputs before changing Rust runtime bindings.
Project Bootstrap (GraphOS, No TypeScript Code)
Use this when initializing a new world project that still needs GraphOS CLI and graph metadata, but no TS runtime code.
- Initialize npm metadata and GraphOS tooling:
npm init -y
npm i -D graphos-world-plugin graphos-cli
- Ensure
package.jsoncontains GraphOS config with TS generators disabled:
{
"type": "module",
"graphos": {
"world": {
"genTypeScript": {
"enabled": false,
"outDir": "gen"
},
"genWebTypeScript": {
"enabled": false,
"outDir": "app"
},
"genCocosCreator": {
"enabled": false,
"outDir": "../cocos/assets/gen"
},
"genBevy": {
"enabled": true,
"outDir": "src/gen"
}
}
}
}
- Ensure
package.jsonscripts includes GraphOS, wasm, and cross-compile commands:
{
"scripts": {
"graphos": "graphos",
"build:wasm": "wasm-pack build . --target web --release --out-dir dist",
"build:wasm:bundler": "wasm-pack build . --target bundler --release --out-dir dist",
"build:wasm:node": "wasm-pack build . --target nodejs --release --out-dir dist",
"build:ios:arm64": "cargo build --release --target aarch64-apple-ios",
"build:ios:x86_64": "cargo build --release --target x86_64-apple-ios",
"build:ios:arm64_sim": "cargo build --release --target aarch64-apple-ios-sim",
"build:android:armv7": "cargo ndk -t armeabi-v7a build --release",
"build:android:arm64": "cargo ndk -t arm64-v8a build --release",
"build:macos:x86_64": "cargo build --release --target x86_64-apple-darwin",
"build:macos:arm64": "cargo build --release --target aarch64-apple-darwin",
"build:windows:x86_64": "cargo zigbuild --release --target x86_64-pc-windows-gnu",
"build:linux:x86_64": "cargo zigbuild --release --target x86_64-unknown-linux-gnu"
}
}
- Create
World.graph.jsonin project root:
{
"id": "main",
"name": "World",
"nodes": [],
"edges": []
}
- Optional verification:
npm run graphos -- --help
npm run build:wasm:node
npm run build:macos:arm64
Completion checks:
- GraphOS CLI is available.
- Graph file exists.
- No TypeScript build/runtime files are required by this skill.
- wasm-pack build scripts are available in
package.json. - Cross-compile scripts exist for iOS/Android/macOS/Windows/Linux targets.
Rust + Bevy Initialization (Latest, Headless)
Goal: initialize a Rust runtime that uses Bevy ECS/time scheduling only.
Step 1: Initialize Rust crate in current directory
# Run this in the project root directory.
cargo init --bin .
If a Cargo project already exists in the current directory, skip this step.
Cargo.toml Rules (Mandatory)
Apply these rules in Cargo.toml for this skill:
package.nameMUST be fixed to"app".- Library output MUST support both crate types:
"cdylib"and"rlib"and"staticlib".
Example:
[package]
name = "app"
[lib]
crate-type = ["cdylib", "rlib", "staticlib"]
Step 2: Add world-bevy and Bevy runtime dependencies
cargo add world-bevy
cargo add bevy --no-default-features --features bevy_app,bevy_ecs,bevy_time
Fallback (if feature flags change in newer Bevy releases):
cargo add world-bevy
cargo add bevy_app bevy_ecs bevy_time
Rules:
- Always use the latest published versions.
- Do not enable rendering/window/UI-related features.
- Keep dependency set minimal: app, ecs, time.
world-bevyis a mandatory dependency; do not skip it.- See world-bevy Dependency below for what the crate provides.
world-bevy Dependency
cargo add world-bevy
What world-bevy provides
| Item | Path | Role |
|---|---|---|
Context |
world_bevy::core::Context |
Core entity identity component (id, table, pid) |
IVariant |
world_bevy::core::IVariant |
Trait for graph variant components; implement this on generated variant types |
IEvent |
world_bevy::core::IEvent |
Trait for graph event types; implement this on generated event types |
Message |
world_bevy::core::Message |
Inbound/outbound message enum (Spawn, Despawn, Change, Event, Reset) |
WorldResource |
world_bevy::core::WorldResource |
Global runtime resource managing entity registry, variant tables, event dispatch, and message queues |
next_id() |
world_bevy::core::next_id |
Generate unique entity IDs |
reg() |
world_bevy::core::reg |
Register core world systems (PreUpdate message pump, PostUpdate spawn/change/despawn ordering) |
reg_veriant() |
world_bevy::core::reg_veriant |
Register a variant table entry for CBOR-driven component changes |
reg_event() |
world_bevy::core::reg_event |
Register an event type for CBOR-driven event dispatch |
ffi |
world_bevy::ffi |
C FFI bindings for wasm host integration (ffi_app_create, ffi_app_update, ffi_app_inbound, ffi_app_outbound, ffi_app_exit) |
The project's src/core/mod.rs re-exports world-bevy types and adds project-specific helpers:
// src/core/mod.rs
pub use world_bevy::core::*;
use bevy_ecs::prelude::*;
#[derive(Event)]
pub struct WorldLogEvent {
pub log: String,
}
This ensures that all world_bevy::core::* imports (used throughout src/gen and src/app) resolve to world_bevy::core types plus any project-local extensions.
Dependency rules:
world-bevyMUST be listed inCargo.tomlbefore generating or compilingsrc/gen.- Generated
src/gen/world.rscode usesworld_bevy::core::Context,world_bevy::core::IVariant, andworld_bevy::core::IEvent— these resolve through thesrc/core/mod.rsre-export chain. - Do not manually re-implement
Context,Message,WorldResource, or the variant/event registration infrastructure — they are provided byworld-bevy. - The
world-bevycrate already depends onbevy_app,bevy_ecs, andbevy_time; the project crate only needs a directbevydependency when using additional Bevy plugins not covered by world-bevy's transitive dependencies.
Step 3: Minimal runtime app (no renderer)
Before wiring systems, initialize base source files for generated modules.
src/gen/mod.rs:
pub mod world;
src/lib.rs:
pub mod r#gen;
pub mod app;
Then implement runtime entry logic.
src/main.rs example:
use bevy_app::{App, Startup, Update};
use bevy_ecs::prelude::*;
use bevy_time::{Time, TimePlugin};
#[derive(Component, Default)]
struct TickAge(f32);
fn setup(mut commands: Commands) {
commands.spawn(TickAge::default());
}
fn tick(time: Res<Time>, mut q: Query<&mut TickAge>) {
for mut age in &mut q {
age.0 += time.delta_secs();
}
}
fn main() {
let mut app = App::new();
app.add_plugins(TimePlugin);
world_bevy::core::reg(&mut app);
app.add_systems(Startup, setup);
app.add_systems(Update, tick);
app.run();
}
Important: world_bevy::core::reg(&mut app) must be called before any other system registration that depends on WorldResource. It inserts the WorldResource and sets up the message pump (PreUpdate) and spawn/change/despawn ordering (PostUpdate).
Step 4: Build verification
cargo check
cargo run
npm run build:wasm:node
Workflow
File Organization Rules (Mandatory)
Every graph System and EventSystem MUST follow these rules with no exceptions:
- One file per System/EventSystem — each System or EventSystem lives in its own
.rsfile. Never put multiple Systems/EventSystems in the same file. - File naming: lowercase + underscores (snake_case) — match the graph node name converted to snake_case. Example: graph node
StartGameEventSystem→ filestart_game_event_system.rs. - Directory:
src/app/— all System and EventSystem implementation files go undersrc/app/. Do not place them insrc/gen/,src/core/, or any other directory. - Registration:
src/app/mod.rs— every System/EventSystem module must be declared (pub mod ...) and itsreg(app)called insidesrc/app/mod.rs. No implicit or auto-registration. - World startup spawn hook (mandatory):
src/app/mod.rs—reg(app)MUST includeapp.add_systems(Startup, on_world_spawn);, andsrc/app/mod.rsMUST definefn on_world_spawn(mut commands: Commands)that spawnsWorldContext. - Types & enums:
src/app/types.rs— define all shared types, enums, and constants here. This includes: singleton entity IDs (e.g.const GAME_ID: &str = "game"), state enums (e.g.enum GameState { ... }), event type enums, and any constant strings used across multiple Systems/EventSystems. Do not scatter these definitions across individual System files. - Config definitions:
src/app/config.rs— define configuration structs here. Every tunable logic parameter (speeds, durations, thresholds, sizes, probabilities) must live in a config struct, never as a hardcoded magic number in System logic. Example:struct GameConfig { pub move_speed: f32, pub jump_force: f32 }. - Shared helper functions:
src/app/common.rs— all reusable helper functions shared by multiple Systems/EventSystems MUST be implemented insrc/app/common.rs. Do not duplicate common logic across System files. - Config instances:
src/config/*.rs+src/config/mod.rs— concrete config values go undersrc/config/as separate files, split by domain (e.g. one file per map/scene:src/config/map_city.rs,src/config/map_dungeon.rs).src/config/mod.rsaggregates all config modules (pub mod map_city; pub mod map_dungeon; ...) and optionally re-exports them. Systems read config via BevyResource, never by importing config files directly — this keeps Systems testable with different configs.
Directory layout example:
src/
app/
mod.rs # declares all modules + calls reg(app) for each
types.rs # shared types, enums, constants (rule 6)
config.rs # config struct definitions (rule 7)
common.rs # shared helper functions (rule 8)
world_bootstrap_system.rs # System: WorldBootstrapSystem
game_system.rs # System: GameSystem
scene_system.rs # System: SceneSystem (if needed)
start_game_event_system.rs # EventSystem: StartGameEventSystem
config/
mod.rs # aggregates all config modules
game_config.rs # global gameplay params
map_city.rs # city map config
map_dungeon.rs # dungeon map config
gen/
mod.rs
world.rs # generated — never edit
core/
mod.rs # re-exports world_bevy::core::* + project-local helpers (WorldLogEvent, etc.)
lib.rs
main.rs
When adding a new System/EventSystem:
- Create
src/app/<snake_case_name>.rs - Add
pub mod <snake_case_name>;tosrc/app/mod.rs - Add
<snake_case_name>::reg(app);inside thereg()function insrc/app/mod.rs
Ordering rule: Before implementing any System or EventSystem logic, src/app/types.rs, src/app/config.rs, and src/app/common.rs must be fully defined first. Systems consume shared types, config, and helper functions; never write System logic against missing shared modules. The correct workflow order is: types → config → common → systems.
Step 1: Sync Graph Output Into Rust
- Confirm Graph changes are complete and validated in
graph-world. - Regenerate
src/genif Graph changed. - Re-read generated
src/gen/*.rsbefore implementing runtime logic.
Completion checks:
- Generated
Context,Variant, andEventtypes match the current graph. - No
src/appcode is written against stale generated APIs.
Step 2: Implement Systems In src/app/
Implement concrete lifecycle System behavior under src/app/ after Graph ownership is finalized.
Prerequisite: src/app/types.rs, src/app/config.rs, and src/app/common.rs must be fully defined before writing any System logic (see Ordering rule).
Follow the File Organization Rules strictly. Each graph System gets its own file in src/app/, named in snake_case, and registered in src/app/mod.rs.
Reference implementation for this skill lives in:
skills/graph-world-bevy/src/app/- structure aligned with
/Volumes/SSD_1T/src/abpilot-cc/RPG-Platformer/src/app
Lifecycle systems in Bevy should be wired with observers and ECS queries, not by editing src/gen.
use world_bevy::core::Context;
use crate::r#gen::world::*;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_time::{Time, Virtual};
pub fn reg(app: &mut App) {
app.add_systems(Update, on_update_system);
app.add_observer(on_spawn_system);
app.add_observer(on_despawn_system);
}
fn on_update_system(
mut query: Query<(&Context, &mut GameTimeComponent), With<GameContext>>,
time: Res<Time<Virtual>>,
) {
for (_context, mut game_time) in &mut query {
game_time.0 += time.delta_secs_f64();
}
}
fn on_spawn_system(query: Query<(Entity, &Context), Added<GameContext>>) {
for (_entity, _context) in &query {
// TODO system-specific spawn logic
}
}
fn on_despawn_system(removed: RemovedComponents<GameContext>) {
for _entity in removed.read() {
// TODO system-specific cleanup logic
}
}
Implementation guidance:
- Put each graph
Systeminto its own Rust module undersrc/app/. - Use snake_case file names for Rust modules and keep them aligned with graph names.
- Example: graph node
GameSystem->src/app/game_system.rs. - Register per-system observers from
pub fn reg(app: &mut App). - Use
Added<YourContext>for spawn semantics. - Use
RemovedComponents<YourContext>for despawn semantics. - Prohibited: never use
EventReader<T>orEventWriter<T>. Handle events exclusively withOn<T>triggers +app.add_observer(...). - Use
Res<Time<Virtual>>for deterministic update timing. - For simulator-visible runtime logs, emit
world_bevy::core::WorldLogEventviaCommands:
commands.trigger(world_bevy::core::WorldLogEvent {
log: "World context added".to_string(),
});
- This event-based logging is the canonical way to produce records visible from
GET /api/world/log; do not rely onprintln!for simulator log verification. - Query generated variant components directly; do not mirror generated state into ad-hoc global caches.
- Keep temporary working data local to the observer function or a dedicated Bevy
Resourceowned by runtime code. - Do not modify generated files under
src/gen.
Completion checks:
- Implemented
Systemmodules compile against the current generated Rust types. Systemlogic remains insrc/app/and does not leak intosrc/gen/.- After
Systemchanges, runnpm run build:wasm:node.
Step 3: Implement World Startup Entry
Goal: define a deterministic startup entry that bootstraps required root/domain contexts.
You must implement startup initialization in a dedicated bootstrap module under src/app/.
use world_bevy::core::Context;
use crate::r#gen::world::*;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
const GAME_ID: &str = "game";
const SCENE_ID: &str = "scene";
pub fn reg(app: &mut App) {
app.add_observer(on_world_spawn_system);
}
fn on_world_spawn_system(
query: Query<&Context, (With<WorldContext>, Added<WorldContext>)>,
existing_game: Query<&Context, With<GameContext>>,
existing_scene: Query<&Context, With<SceneContext>>,
mut commands: Commands,
) {
for world in &query {
let has_game = existing_game.iter().any(|ctx| ctx.id == GAME_ID);
if !has_game {
GameContext::spawn(
&mut commands,
Some(world.id.clone()),
GAME_ID,
GameTimeComponent(0.0),
GameStateComponent(0),
);
}
let has_scene = existing_scene.iter().any(|ctx| ctx.id == SCENE_ID);
if !has_scene {
SceneContext::spawn(
&mut commands,
Some(world.id.clone()),
SCENE_ID,
SceneGridComponent(SceneGridSchema {
width: 0,
height: 0,
tile_size: 1.0,
tiles: Vec::new(),
}),
);
}
}
}
Startup guidance:
- Create a bootstrap
SystemunderWorldin graph design. - Register its module in
src/app/mod.rs. - Use generated
Context::spawn(...)helpers fromsrc/gen. - Keep initialization idempotent by checking existing singleton ids before spawning.
- Prefer fixed ids for singleton/root contexts.
- Use
pidto attach child contexts to the owning parent context id. - Do not resolve singleton contexts by positional child order.
Step 4: Implement EventSystems In src/app/
Implement graph EventSystem handlers as Bevy observers over generated events.
Prerequisite: src/app/types.rs, src/app/config.rs, and src/app/common.rs must be fully defined first (see Ordering rule).
Follow the File Organization Rules strictly. Each EventSystem gets its own file in src/app/, named in snake_case (e.g. StartGameEventSystem → start_game_event_system.rs), and registered in src/app/mod.rs.
use world_bevy::core::Context;
use crate::r#gen::world::*;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
pub fn reg(app: &mut App) {
app.add_observer(on_start_game_event_system);
}
fn on_start_game_event_system(
trigger: On<StartGameEvent>,
mut query: Query<(&Context, &mut GameStateComponent), With<GameContext>>,
) {
let event = trigger.event();
for (_context, mut game_state) in &mut query {
game_state.0 = match event.game_type.as_str() {
"running" => 1,
"paused" => 2,
_ => 0,
};
}
}
Implementation guidance:
- Put each graph
EventSysteminto its own Rust module undersrc/app/. - Example: graph node
StartGameEventSystem->src/app/start_game_system.rs. - Prohibited: NEVER use
EventReader<T>orEventWriter<T>to read/write events. UseOn<T>withapp.add_observer(...)as the only event handling mechanism. - Register event handlers with
app.add_observer(...). - Use
On<GeneratedEvent>as the trigger type. - Read payload from
trigger.event(). - If the handler needs to expose runtime behavior to simulator logs, call
commands.trigger(world_bevy::core::WorldLogEvent { ... })in the handler path you want to verify. - Fetch target contexts/components via ECS
Query; mutate only the scope owned by the graph. - If event payload schema changes in Graph, regenerate
src/genfirst, then update handler signatures. - Do not invent new topology in runtime code to compensate for missing graph nodes; go back to
graph-world.
Completion checks:
- Implemented
EventSystemmodules compile against the current generated event payloads. - Event handlers only mutate graph-owned scope and stay under
src/app/. - After
EventSystemchanges, runnpm run build:wasm.
Step 5: Register Runtime Wiring In src/app/mod.rs
After implementing handlers, wire the runtime explicitly in src/app/mod.rs.
use bevy_app::prelude::*;
pub mod config;
pub mod game_system;
pub mod start_game_system;
pub mod types;
pub mod world_bootstrap_system;
pub fn reg(app: &mut App) {
world_bootstrap_system::reg(app);
game_system::reg(app);
start_game_system::reg(app);
}
Registration guidance:
- Declare
pub mod types;,pub mod config;, andpub mod common;insrc/app/mod.rs. - Register every implemented
Systemmodule insrc/app/mod.rs. - Register every implemented
EventSystemmodule insrc/app/mod.rs. - Insert concrete config instances (from
src/config/) as BevyResourceinmain.rs, not insidesrc/app/mod.rs. - Keep
src/lib.rsorsrc/main.rsresponsible only for high-level runtime assembly:
use bevy_app::prelude::*;
use bevy_time::TimePlugin;
mod config;
fn main() {
let mut app = App::new();
app.add_plugins(TimePlugin);
// Register world-bevy core infrastructure (WorldResource, message pump, spawn/change/despawn systems)
world_bevy::core::reg(&mut app);
// Insert concrete config instances as Resources (rule 7)
app.insert_resource(config::game_config::GAME_CONFIG);
world_bevy::core::reg(&mut app);
crate::r#gen::world::reg(&mut app);
crate::app::reg(&mut app);
app.run();
}
Completion checks:
- Every implemented
SystemandEventSystemmodule is registered. src/app/mod.rsmatches current graph contracts.- Runtime assembly does not require edits inside
src/gen. - After runtime wiring changes, run
npm run build:wasm.
Step 5: wasm-pack packaging
- Install wasm target and wasm-pack:
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
- Ensure root
Cargo.tomlexposes cdylib:
[lib]
crate-type = ["cdylib", "rlib"]
- Build/package with npm scripts:
npm run build:wasm
# or
npm run build:wasm:bundler
npm run build:wasm:node
- Optional direct command:
wasm-pack build . --target web --release --out-dir pkg
Completion checks:
- Runtime compiles and runs.
- ECS systems execute.
- Time resource updates normally.
- No Bevy render/window/UI modules are linked.
pkg/contains wasm-pack artifacts (.wasm, JS glue, package metadata).
Step 6: Cross-compile setup on macOS
- Install Rust targets:
rustup target add \
aarch64-apple-ios \
x86_64-apple-ios \
aarch64-apple-ios-sim \
armv7-linux-androideabi \
aarch64-linux-android \
x86_64-apple-darwin \
aarch64-apple-darwin \
x86_64-pc-windows-gnu \
x86_64-unknown-linux-gnu
- Install cross-compile helpers:
brew install zig
cargo install cargo-zigbuild
cargo install cargo-ndk
- Configure Android NDK (required by
cargo ndk):
export ANDROID_NDK_HOME=/path/to/android-ndk
export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME"
- Run platform builds:
# iOS
npm run build:ios:arm64
npm run build:ios:x86_64
npm run build:ios:arm64_sim
# Android
npm run build:android:armv7
npm run build:android:arm64
# macOS
npm run build:macos:x86_64
npm run build:macos:arm64
# Windows / Linux (cross on macOS via zig)
npm run build:windows:x86_64
npm run build:linux:x86_64
Output notes:
- Rust artifacts are generated under
target/<triple>/release/. - Android
cargo ndkoutputs ABI-specific artifacts forarmeabi-v7aandarm64-v8a. - For iOS universal libs, combine device/simulator outputs later with Xcode tooling as needed.
GraphOS Integration Notes for Rust Runtime
- Keep graph topology/design operations in
graph-worldworkflow. - Keep runtime implementation in Rust under this skill.
- If Graph model changes, apply Graph changes first, then update Rust side adapters/systems.
- Do not introduce TypeScript runtime code in this workflow.
- Implement manual runtime logic under
src/app/; keep generated code isolated undersrc/gen/. - Use the reference modules in
skills/graph-world-bevy/src/app/as the baseline pattern for System/EventSystem wiring.
Quality Gates
- Graph-first gate: runtime coding starts only after graph closure validation passes.
- No-TS gate: no TypeScript runtime implementation is introduced.
- Headless-Bevy gate: no rendering/window/UI Bevy modules enabled;
world-bevydependency is present inCargo.toml. - Minimal-runtime gate: app, ecs, time capabilities are present and verified;
world_bevy::core::reg(&mut app)is called inmain.rs. - Generated-code gate:
src/genhas been regenerated and re-read after Graph changes. - App-wiring gate: all manual runtime logic lives in
src/app/, notsrc/gen/; each System/EventSystem is in its own file with snake_case naming. - Registration gate: every System/EventSystem module is wired through
src/app/mod.rs. - Event-observer gate:
EventReader<T>andEventWriter<T>are prohibited. All event handling MUST useOn<T>triggers registered viaapp.add_observer(...). NoEvents<T>resource reads/writes anywhere in the codebase. - Types gate: shared types, enums, constants, and singleton IDs are centralized in
src/app/types.rs; not scattered across System files. - Common gate: reusable helper logic is centralized in
src/app/common.rs; duplicated helper implementations across System/EventSystem files are prohibited. - Config gate: all tunable logic parameters live in config structs (
src/app/config.rs); no magic numbers in System logic; concrete config instances exist undersrc/config/. - Startup gate: world bootstrap logic is idempotent and singleton-safe.
- Build gate:
cargo checksucceeds for the Rust runtime crate. - wasm wiring gate: after any
System/EventSystem/src/app/mod.rschange,npm run build:wasmsucceeds. - wasm gate:
wasm-pack buildsucceeds and outputs package files. - cross-compile gate: all required target scripts complete successfully on macOS toolchain.
Failure Recovery
world_bevy::core::Contextorworld_bevy::core::IVariantnot found: ensuresrc/core/mod.rscontainspub use world_bevy::core::*;andCargo.tomllistsworld-bevyas a dependency.world_bevy::core::regnot called:WorldResourcewill be missing, causing spawn/change/despawn systems to panic. Always callworld_bevy::core::reg(&mut app)inmain.rsbefore any system that queriesRes<WorldResource>.cargo add bevy --no-default-features ...fails due to feature changes: use split cratesbevy_app,bevy_ecs,bevy_timeinstead.- Generated Rust types do not match expected Context/Event names:
regenerate
src/genfirst, then reopen the generated files before editingsrc/app. - Runtime logic was added into generated files:
move that code into
src/app/modules and keepsrc/gengenerated-only. SystemorEventSystemedits compile in native Rust but fail in wasm packaging: runnpm run build:wasm:node, fix target-specific issues, and do not treat the work as complete until wasm packaging passes.- Singleton bootstrap creates duplicates:
add fixed-id existence checks before calling generated
Context::spawn(...). - Event handler needs data that is not present in the graph payload or contexts:
stop Rust patching and return to
graph-worldto fix topology/schema first. - Runtime compiles but time is not advancing:
verify
TimePluginis added and systems run onUpdateschedule. - Rendering-related crate unexpectedly appears:
remove it and re-check
Cargo.tomlfeatures/dependencies. wasm-packbuild fails due to missing wasm target: runrustup target add wasm32-unknown-unknownand retry.wasm-packbuild fails because crate type is not compatible: add[lib] crate-type = ["cdylib", "rlib"]to rootCargo.toml.- Android build fails with NDK not found:
set
ANDROID_NDK_HOMEandANDROID_NDK_ROOT, then reruncargo ndkscripts. - Windows/Linux cross compile fails on macOS linker:
install
zigand usecargo zigbuildscripts (do not use plaincargo buildfor those targets). - iOS build fails due to missing target:
run
rustup target add <ios-target>and retry. - Request needs graph topology change:
pause Rust edits and switch back to
graph-worldfirst. EventReader<T>orEventWriter<T>found in code: replace withOn<T>trigger +app.add_observer(...).EventReader/EventWriterare legacy patterns incompatible with the observer-based event dispatch used byworld-bevy.
Simulator Log Verification Loop
When implementing or fixing Systems/EventSystems, use the simulator API to verify runtime behavior in a tight iteration loop without restarting the GraphOS service.
Verification flow: pause → inspect logs
When the simulator is running and you need to check runtime behavior:
- Pause the simulator to freeze state at the current tick:
POST /api/world/pause— stops the clock, preserving all runtime state and accumulated logs.
- Inspect logs via
GET /api/world/logto verify expectedevent,get,set,add, anddelrecords were produced up to the pause point. - Analyze the log entries against expected behavior. For each log entry, check:
- Does the operation type (
event/get/set/add/del) match what the System/EventSystem should produce? - Do the target entity/component IDs and values match expectations?
- Is the ordering of operations correct relative to system execution order?
- Does the operation type (
- If logs are correct,
POST /api/world/resumeto continue. If incorrect, proceed to the fix cycle below.
Why pause first: pausing before log inspection ensures you're reading a stable snapshot at a known point in time, rather than chasing logs that are still being written. This is especially important for systems with fast tick rates or async event flows.
Fix cycle: code change → reset → start
When logs show incorrect or missing behavior, apply the fix and restart the simulator without restarting the service:
- Modify code — edit the Rust System/EventSystem code in
src/app/. - Rebuild wasm — run
npm run build:wasm:nodeto compile changes. The simulator picks up the new wasm automatically on next reset. - Reset —
POST /api/world/resetclears all runtime state, entities, and accumulated logs, returning the simulator to initial state. - Start —
POST /api/world/startstarts the simulator clock from time zero, re-running bootstrap systems and initializing fresh state with the updated wasm. - Drive scenario — send events through
POST /api/world/eventto trigger the specific code path being verified (e.g.{ "type": "MyEvent", "payload": { ... } }). - Pause and inspect —
POST /api/world/pausethenGET /api/world/logto verify the fix. - If logs still show incorrect behavior, return to step 1 and repeat.
Why reset-then-start (not just resume): after a code change, the old runtime state, entities, and component values may be incompatible with the updated wasm logic. Reset ensures a clean slate; start re-runs all bootstrap/Startup systems with the new code so you're testing against fresh, consistent state. Skipping reset can produce false positives or crashes from stale entity data.
API reference (from graph-world skill)
| Endpoint | Method | Purpose |
|---|---|---|
/api/world/state |
GET | Get simulator state: { duration, current, state, scale, fps } |
/api/world/start |
POST | Start simulator clock from time zero |
/api/world/pause |
POST | Pause simulator clock, preserving state and logs for inspection |
/api/world/resume |
POST | Resume paused simulator from where it stopped |
/api/world/reset |
POST | Reset to initial state, clear all entities and logs |
/api/world/event |
POST | Send event payload into simulator |
/api/world/log |
GET | Query logged records (supports startTime/endTime params) |
Key constraints
- Do not use
cargo runfor simulation log verification.cargo runlaunches a native binary that does not integrate with the GraphOS simulator API (/api/world/*). All log verification must go through the simulator API vianpm run build:wasm:node+ HTTP endpoints. - Logs that must appear in
/api/world/logshould be emitted from runtime code withcommands.trigger(world_bevy::core::WorldLogEvent { log: ... }); stdout logging is not a substitute for simulator records. - Do not use the skill reference implementation's wasm for simulation. The simulator runs the project's own wasm built from the current workspace; verify via the project's
npm run build:wasm:node, not by running the skill'ssrc/app/reference code directly. - Never restart the GraphOS service to pick up wasm changes;
npm run build:wasm:node+POST /api/world/resetthenPOST /api/world/startis sufficient. - After
npm run build:wasm:nodesucceeds, immediately verify through the simulator API before declaring the fix complete. - If the log payload is large, generate a focused analysis script rather than reading the full response manually (see graph-world skill for
/api/world/logquery patterns). - Always pause before log inspection when the simulator is running; never read logs on a running simulator — the log tail may be incomplete or still being written.
Example Requests
- Bootstrap a GraphOS world project for Rust runtime with no TypeScript runtime code.
- Initialize a Bevy headless app using only app/ecs/time and add a basic tick system in
src/app/game_system.rs. - Implement
WorldBootstrapSystemand register it insrc/app/mod.rs. - Implement a generated
StartGameEventSystem insrc/app/start_game_system.rs. - Migrate existing world runtime from TS to Rust + Bevy ECS/time while keeping graph workflow unchanged.