Parent
- Program: ENG2 — GoudEngine v2 Rebuild (see the pinned master tracking issue)
- Phase / Milestone: Phase 8 — Capability Gaps (
eng2-p8-capabilities)
- Batch / Group: Batch 8.1 — Highest-demand capabilities, Group C
- Runbook spec:
docs/src/runbook/phases/phase-8.md (committed with the roadmap)
Summary
Add a full World snapshot save/load API (serde over the component registry) that round-trips an entire world's entities and components to/from bytes, exposed via FFI to all 10 SDKs.
Architecture Context
Layer: Layer 3 (Services, ecs/world/) for the snapshot walk/apply logic; Layer 5 (FFI) for the new export surface.
Modules/types touched:
goud_engine/src/ecs/world/ — new snapshot module (e.g. snapshot.rs) that iterates all live entities and all component storages registered via register_serializable::<T>().
goud_engine/src/core/serialization/ — reuse existing serde/byte-encoding conventions (delta.rs) where they fit; a full snapshot is not a delta, so this is a new encode/decode path, not a repurposing of DeltaEncode.
goud_engine/src/ffi/scene_loading.rs or a new ffi/world_snapshot.rs — new FFI exports (today this file only has goud_scene_load/goud_scene_unload).
Boundary constraints (only those that apply):
- FFI exports:
#[no_mangle] extern "C", #[repr(C)], null checks, // SAFETY: comments.
- Generated
*.g.rs/*.g.cs/*.g.ts are never hand-edited; change Rust + FFI + schema, rerun ./codegen.sh.
Pattern to follow: goud_engine/src/ecs/world/serialize_entity.rs — already implements per-entity serialize_entity/deserialize_entity_components against types registered via World::register_serializable::<T>(). The world snapshot is this same per-entity mechanism looped over every live entity plus the entity/hierarchy graph, not a new serialization scheme.
Scope
Acceptance Criteria
Breaking Change & Throne Follow-up
None — additive/internal (new FFI exports only; existing goud_scene_load/goud_scene_unload are untouched).
Blocked By
None.
Files Likely Touched
- New:
goud_engine/src/ecs/world/snapshot.rs, goud_engine/src/ffi/world_snapshot.rs (or exports added to ffi/scene_loading.rs)
- Generated: SDK bindings under
sdks/*/ for the new snapshot FFI exports
- Modified:
goud_engine/src/ecs/world/mod.rs (register snapshot module), codegen/goud_sdk.schema.json
Agent Notes
- The engine's only existing persistence paths today are one-way:
core/serialization/delta.rs implements DeltaEncode/DeltaPayload for compact network delta updates (bitmask + changed-field bytes) — this is a network-delta mechanism, not a full-world snapshot format, and reusing it directly would be a category error since a snapshot has no "baseline" to diff against. Separately, ffi/scene_loading.rs:47 (goud_scene_load) and its neighbor at line 123 (goud_scene_unload) only load/unload a scene from a JSON string — grep of the file confirms there is no goud_scene_save-equivalent export anywhere in it (only these two pub unsafe extern "C" fn entries exist in the whole 179-line file).
- There IS already real per-entity serialization infrastructure to build on:
goud_engine/src/ecs/world/serialize_entity.rs (367 lines) implements World::register_serializable::<T>(), serialize_entity, and deserialize_entity_components, keyed by std::any::type_name::<T>(). This is the actual reusable "component registry" the roadmap refers to — a full-world snapshot is this mechanism looped over every live entity plus the Hierarchy component graph, not new serialization machinery from scratch.
- The doc comment at
serialize_entity.rs:19-25 explicitly warns that type_name::<T>() embeds full module paths, so refactoring component module locations breaks old snapshot compatibility — carry this caveat into the new snapshot format's versioning story rather than silently inheriting the fragility.
Verification
cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings
cargo test
./codegen.sh && git diff --exit-code
Parent
eng2-p8-capabilities)docs/src/runbook/phases/phase-8.md(committed with the roadmap)Summary
Add a full
Worldsnapshot save/load API (serde over the component registry) that round-trips an entire world's entities and components to/from bytes, exposed via FFI to all 10 SDKs.Architecture Context
Layer: Layer 3 (Services,
ecs/world/) for the snapshot walk/apply logic; Layer 5 (FFI) for the new export surface.Modules/types touched:
goud_engine/src/ecs/world/— new snapshot module (e.g.snapshot.rs) that iterates all live entities and all component storages registered viaregister_serializable::<T>().goud_engine/src/core/serialization/— reuse existing serde/byte-encoding conventions (delta.rs) where they fit; a full snapshot is not a delta, so this is a new encode/decode path, not a repurposing ofDeltaEncode.goud_engine/src/ffi/scene_loading.rsor a newffi/world_snapshot.rs— new FFI exports (today this file only hasgoud_scene_load/goud_scene_unload).Boundary constraints (only those that apply):
#[no_mangle] extern "C",#[repr(C)], null checks,// SAFETY:comments.*.g.rs/*.g.cs/*.g.tsare never hand-edited; change Rust + FFI + schema, rerun./codegen.sh.Pattern to follow:
goud_engine/src/ecs/world/serialize_entity.rs— already implements per-entityserialize_entity/deserialize_entity_componentsagainst types registered viaWorld::register_serializable::<T>(). The world snapshot is this same per-entity mechanism looped over every live entity plus the entity/hierarchy graph, not a new serialization scheme.Scope
serialize_entityfor each, and wrap the result with enough entity/hierarchy metadata to reconstruct parent/child relationships (Hierarchycomponent) on load.World, recreate entities in a stable order, calldeserialize_entity_componentsper entity, then re-run transform propagation once.goud_world_snapshot_save(context -> bytes),goud_world_snapshot_load(context, bytes -> success/error code), matching the ownership/error conventions used bygoud_scene_load/goud_scene_unload(ffi/scene_loading.rs:47-179).register_serializable::<T>()type-name fragility caveat already documented inserialize_entity.rs:19-25.Acceptance Criteria
world_state_hash(perENG2-P5-04's hashing hook) after save then load.perf-dod.md) without exceeding a documented time budget.python codegen/validate_coverage.py.cargo check && cargo fmt --all -- --check && cargo clippy -- -D warningsclean;cargo testgreen;./codegen.sh && git diff --exit-code(drift gate)Breaking Change & Throne Follow-up
None — additive/internal (new FFI exports only; existing
goud_scene_load/goud_scene_unloadare untouched).Blocked By
None.
Files Likely Touched
goud_engine/src/ecs/world/snapshot.rs,goud_engine/src/ffi/world_snapshot.rs(or exports added toffi/scene_loading.rs)sdks/*/for the new snapshot FFI exportsgoud_engine/src/ecs/world/mod.rs(register snapshot module),codegen/goud_sdk.schema.jsonAgent Notes
core/serialization/delta.rsimplementsDeltaEncode/DeltaPayloadfor compact network delta updates (bitmask + changed-field bytes) — this is a network-delta mechanism, not a full-world snapshot format, and reusing it directly would be a category error since a snapshot has no "baseline" to diff against. Separately,ffi/scene_loading.rs:47(goud_scene_load) and its neighbor at line 123 (goud_scene_unload) only load/unload a scene from a JSON string — grep of the file confirms there is nogoud_scene_save-equivalent export anywhere in it (only these twopub unsafe extern "C" fnentries exist in the whole 179-line file).goud_engine/src/ecs/world/serialize_entity.rs(367 lines) implementsWorld::register_serializable::<T>(),serialize_entity, anddeserialize_entity_components, keyed bystd::any::type_name::<T>(). This is the actual reusable "component registry" the roadmap refers to — a full-world snapshot is this mechanism looped over every live entity plus theHierarchycomponent graph, not new serialization machinery from scratch.serialize_entity.rs:19-25explicitly warns thattype_name::<T>()embeds full module paths, so refactoring component module locations breaks old snapshot compatibility — carry this caveat into the new snapshot format's versioning story rather than silently inheriting the fragility.Verification