Skip to content

ENG2-P8-03: World snapshot save/load (serde over the component registry) #801

Description

@aram-devdocs

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

  • World-wide snapshot: walk all live entities, call the existing per-entity serialize_entity for each, and wrap the result with enough entity/hierarchy metadata to reconstruct parent/child relationships (Hierarchy component) on load.
  • Snapshot format versioning: include a schema/version tag so future component additions don't silently corrupt old snapshots.
  • Load path: clear or diff against the current World, recreate entities in a stable order, call deserialize_entity_components per entity, then re-run transform propagation once.
  • FFI exports: goud_world_snapshot_save (context -> bytes), goud_world_snapshot_load (context, bytes -> success/error code), matching the ownership/error conventions used by goud_scene_load/goud_scene_unload (ffi/scene_loading.rs:47-179).
  • Tests: spec test + round-trip unit tests (save then load reproduces identical entity/component state) including hierarchy relationships.
  • Docs/rules updates: mdBook page documenting the snapshot format and its versioning guarantee; note the register_serializable::<T>() type-name fragility caveat already documented in serialize_entity.rs:19-25.

Acceptance Criteria

  • A world with entities across multiple component types, including parent/child hierarchies, round-trips to an identical world_state_hash (per ENG2-P5-04's hashing hook) after save then load.
  • Snapshot save/load works at S1 scripted-scene scale (per perf-dod.md) without exceeding a documented time budget.
  • FFI snapshot exports have wrappers in all 10 SDKs and pass python codegen/validate_coverage.py.
  • cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings clean; cargo test green; ./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_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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions