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 B
- Runbook spec:
docs/src/runbook/phases/phase-8.md (committed with the roadmap)
Summary
Expose the already-implemented 3D ParticleEmitter through a new ffi/renderer3d/particles.rs module and all 10 SDKs, and add a new 2D particle emitter built on the Phase 6 instanced sprite core.
Architecture Context
Layer: Layer 4 (Engine, libs/graphics/renderer3d/ — 2D emitter logic reuses the sprite core) for the 2D emitter internals; Layer 5 (FFI) for both new export surfaces.
Modules/types touched:
goud_engine/src/ffi/renderer3d/particles.rs (new) — wraps the existing Renderer3D::create_particle_emitter / set_particle_emitter_position / remove_particle_emitter engine API.
goud_engine/src/libs/graphics/renderer2d/ — new 2D particle emitter type, driven by the Phase 6 instanced sprite core's per-instance buffer rather than a bespoke draw path.
goud_engine/src/ffi/renderer/ — new FFI exports for the 2D emitter (naming to mirror the 3D particles.rs module).
Boundary constraints (only those that apply):
- Raw GPU calls stay in
libs/graphics/backend/ — the 2D emitter must go through the sprite core's existing instance-buffer upload path, not a new wgpu:: call site.
- FFI exports:
#[no_mangle] extern "C", #[repr(C)], null checks, // SAFETY: comments.
Pattern to follow: goud_engine/src/ffi/spatial_grid/mod.rs handle-registry pattern for exposing engine-internal u32 IDs safely across FFI (the 3D emitter already returns opaque u32 IDs from create_particle_emitter — mirror how existing FFI modules validate and scope such IDs per context). For the 2D emitter, follow whatever per-instance buffer API Phase 6's ENG2-P6-02 sprite core exposes internally — do not build a second CPU-side vertex-expansion path.
Scope
Acceptance Criteria
Breaking Change & Throne Follow-up
None — additive/internal (new FFI exports only; no existing exports change).
Blocked By
ENG2-P6-02 (instanced sprite core — the new 2D particle emitter must render through the unified sprite core's per-instance buffer, not a fifth parallel 2D rendering path).
Files Likely Touched
- New:
goud_engine/src/ffi/renderer3d/particles.rs, goud_engine/src/libs/graphics/renderer2d/particles.rs (or equivalent), new 2D particle FFI file under goud_engine/src/ffi/renderer/
- Generated: SDK bindings under
sdks/*/ for both particle FFI surfaces
- Modified:
goud_engine/src/ffi/renderer3d/mod.rs (register particles module), codegen/goud_sdk.schema.json
Agent Notes
ParticleEmitterConfig (goud_engine/src/libs/graphics/renderer3d/types.rs:107-129), Particle (types.rs:267-273), and ParticleEmitter (types.rs:276-283) are fully implemented and already rendered every frame: render_instanced_and_particles (libs/graphics/renderer3d/render_instanced.rs:13) checks self.particle_emitters at lines 23 and 72 and draws them via the instanced path. This is real, working engine functionality, not a stub.
- The engine-level public API already exists and needs no new engine logic for the 3D half of this issue:
Renderer3D::create_particle_emitter (libs/graphics/renderer3d/core_primitives.rs:133-142), set_particle_emitter_position (core_primitives.rs:157-164), and remove_particle_emitter (core_primitives.rs:167-172) are all pub fn on Renderer3D. The work here is exclusively adding the FFI wrapper layer + SDK codegen — confirmed by listing goud_engine/src/ffi/renderer3d/: it contains animation.rs, camera.rs, environment/, lighting.rs, materials.rs, mod.rs, model/, postprocess.rs, primitives.rs, scene.rs, skinned.rs, state.rs and no particles.rs.
- Note the internal types (
ParticleEmitterConfig, Particle, ParticleEmitter) are scoped pub(in crate::libs::graphics::renderer3d) — FFI code cannot touch them directly and must go exclusively through the Renderer3D public methods above (which take/return plain u32 IDs and primitive floats already, so no new #[repr(C)] struct should be needed for the 3D half).
- 2D has no particle system at all today; it must be built new on top of whatever the Phase 6 sprite core (
ENG2-P6-02) exposes for per-instance buffer updates — do not build it against the pre-Phase-6 immediate or rebuild-per-call sprite paths, since those are slated for deletion in ENG2-P6-03.
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
Expose the already-implemented 3D
ParticleEmitterthrough a newffi/renderer3d/particles.rsmodule and all 10 SDKs, and add a new 2D particle emitter built on the Phase 6 instanced sprite core.Architecture Context
Layer: Layer 4 (Engine,
libs/graphics/renderer3d/— 2D emitter logic reuses the sprite core) for the 2D emitter internals; Layer 5 (FFI) for both new export surfaces.Modules/types touched:
goud_engine/src/ffi/renderer3d/particles.rs(new) — wraps the existingRenderer3D::create_particle_emitter/set_particle_emitter_position/remove_particle_emitterengine API.goud_engine/src/libs/graphics/renderer2d/— new 2D particle emitter type, driven by the Phase 6 instanced sprite core's per-instance buffer rather than a bespoke draw path.goud_engine/src/ffi/renderer/— new FFI exports for the 2D emitter (naming to mirror the 3Dparticles.rsmodule).Boundary constraints (only those that apply):
libs/graphics/backend/— the 2D emitter must go through the sprite core's existing instance-buffer upload path, not a newwgpu::call site.#[no_mangle] extern "C",#[repr(C)], null checks,// SAFETY:comments.Pattern to follow:
goud_engine/src/ffi/spatial_grid/mod.rshandle-registry pattern for exposing engine-internal u32 IDs safely across FFI (the 3D emitter already returns opaqueu32IDs fromcreate_particle_emitter— mirror how existing FFI modules validate and scope such IDs per context). For the 2D emitter, follow whatever per-instance buffer API Phase 6'sENG2-P6-02sprite core exposes internally — do not build a second CPU-side vertex-expansion path.Scope
ffi/renderer3d/particles.rs:#[no_mangle]wrappers for create/configure/set-position/remove on the existingRenderer3Dparticle emitter API (core_primitives.rs:133-172).libs/graphics/renderer2d/, spawning/aging/killing particles on the CPU and feeding positions/colors/sizes into the sprite core's per-instance buffer (perENG2-P6-02).lifetime) for both 2D and 3D paths..agents/rules/graphics-patterns.mdgains a short particle-system note once both paths exist.Acceptance Criteria
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; no existing exports change).
Blocked By
ENG2-P6-02 (instanced sprite core — the new 2D particle emitter must render through the unified sprite core's per-instance buffer, not a fifth parallel 2D rendering path).
Files Likely Touched
goud_engine/src/ffi/renderer3d/particles.rs,goud_engine/src/libs/graphics/renderer2d/particles.rs(or equivalent), new 2D particle FFI file undergoud_engine/src/ffi/renderer/sdks/*/for both particle FFI surfacesgoud_engine/src/ffi/renderer3d/mod.rs(registerparticlesmodule),codegen/goud_sdk.schema.jsonAgent Notes
ParticleEmitterConfig(goud_engine/src/libs/graphics/renderer3d/types.rs:107-129),Particle(types.rs:267-273), andParticleEmitter(types.rs:276-283) are fully implemented and already rendered every frame:render_instanced_and_particles(libs/graphics/renderer3d/render_instanced.rs:13) checksself.particle_emittersat lines 23 and 72 and draws them via the instanced path. This is real, working engine functionality, not a stub.Renderer3D::create_particle_emitter(libs/graphics/renderer3d/core_primitives.rs:133-142),set_particle_emitter_position(core_primitives.rs:157-164), andremove_particle_emitter(core_primitives.rs:167-172) are allpub fnonRenderer3D. The work here is exclusively adding the FFI wrapper layer + SDK codegen — confirmed by listinggoud_engine/src/ffi/renderer3d/: it containsanimation.rs, camera.rs, environment/, lighting.rs, materials.rs, mod.rs, model/, postprocess.rs, primitives.rs, scene.rs, skinned.rs, state.rsand noparticles.rs.ParticleEmitterConfig,Particle,ParticleEmitter) are scopedpub(in crate::libs::graphics::renderer3d)— FFI code cannot touch them directly and must go exclusively through theRenderer3Dpublic methods above (which take/return plainu32IDs and primitive floats already, so no new#[repr(C)]struct should be needed for the 3D half).ENG2-P6-02) exposes for per-instance buffer updates — do not build it against the pre-Phase-6 immediate or rebuild-per-call sprite paths, since those are slated for deletion inENG2-P6-03.Verification