LempoLempo Particle FX Docs

Testing

CI runs the parity, behavior, and contract checks on every push (.github/workflows/ci.yml).

Naming note: the editor UI is built on Dear PyGui, which wraps Dear ImGui — that is why the UI test files are prefixed test_imgui_.

Headline numbers

99/99 parity

Python vs C++ simulation output — every case matches

27 templates

Trails load/search/apply + thumbnails and user-preset round-trip

Perf gate

C++ throughput floor enforced (tools/check_perf.py)

Full test matrix

TestCovers
core/test_parity.pyPython vs C++ simulation output — 99/99 passing
core/test_behavior.pySimulation behavior, morph-window keys, performance (~2–3 ms @ 2,000 particles)
tests/test_imgui_build.pyUI builds without errors (blend dropdown, seed box, force-field widgets)
tests/test_imgui_logic.pyHeadless simulation logic (C++ path)
tests/test_imgui_nav.pyViewport navigation (WASD/arrows, Q/E, F, Shift×3) + sidebar splitter hover/drag
tests/test_imgui_color.pyPer-state color persistence across birth/death switches
tests/test_imgui_mesh.pyUploaded models render as meshes, tint, culling, LOD
tests/test_imgui_morph.pyShape cross-fade window (edges, split alpha, legacy fallback)
tests/test_imgui_upload.pyUpload chain, OBJ parsing, big-JSON node names, bone-node fallback
tests/test_imgui_raster.pyRaster path: PPM conversion, GL orientation, auto-switch, no-GL fallback
tests/test_preview_blobs.pyModel-blob embedding for the browser preview
tests/test_contracts.pyGenerated bindings = source, C++ order, schema accept/reject, migration
tests/test_gl_blend.pyPixel-level GL formulas per blend mode + fallbacks
tests/test_blend_modes.pyBlend round-trip, sanitize, sample layers
tests/test_seed.pySeed replay identical (Python + C++), divergence, seed-0 legacy
tests/test_fields.pyField formulas, off-identical, perturb/replay, collision, attractor (Py + C++)
preview/test_blend.mjsExtension blend mappings + resolution (stubbed runtimes)
preview/test_seed.mjsPreview seeded replay and extension RNG helper behavior (not cross-runtime particle-record parity)
preview/test_fields.mjsExtension helpers == Python + preview field behavior
preview/test_trails.mjsPreview trails: bake parity vs Python (exact), store semantics, engine wiring 2D/3D, style helpers
preview/test_ext_trails.mjsExtension trails: shipped-helper parity, 3D ribbons/hide (real three.js), 2D ribbons/hide (stub PIXI), hybrid + legacy
preview/test_ext_runtime.mjsShipped 3D runtime headless (stub gdjs + real three.js)
preview/test_engine.mjs, test_engine3d.mjs, test_guides.mjs, test_server.pyBrowser preview engine, 2D/3D scene layers, guides
preview/test_frame_buffers.mjsTyped-array frame contents and stable buffer reuse
preview/test_engine_parity.mjs + engine_parity_baseline.jsonSeeded 120-frame 2D/3D snapshots against the pre-refactor browser simulation; this does not assert parity with GDevelop
preview/test_models.mjsModel-blob caching and live-push behavior
preview/test_morph.mjsPreview-side morphAt() cross-fade sampling
preview/test_bake.mjs / test_ext_bake.mjsSkinned-mesh rest-pose baking (preview + shipped extension)
tests/test_trails_schema.pyTRAIL_SCHEMA ↔ defaults sync, clamps, LUT sizes
tests/test_trails_roundtrip.pyTrail files byte-stable round-trip + old-file heal
tests/test_trails_parity.pyPython ↔ C++ trail width/gradient/scalars
tests/test_trails_perf.pyTrail update/bake perf budgets
tests/test_trails_flow.pyTime/distance emission, exact spacing, tick FIFO, trail death
tests/test_trails_render.pyRibbon-strip math + viewport smoke
tests/test_trails_templates.py27 templates load/search/apply, thumbnails, user-preset round-trip
tests/test_view2d_zoom.pyWorld-space 2D zoom math, anchors, raster agreement
tools/check_perf.pyCI perf gate: C++ throughput floor
render/test_gl.py, test_clip.py, test_cost.pyGL context init, projection parity, render-cost profiling

Running locally

python tools/generate_contracts.py --check   # contract drift gate
python core/test_parity.py                   # Python vs C++
python -m pytest tests/ -q                   # editor + schema suite
node preview/test_ext_trails.mjs             # extension trails headless
python tools/check_perf.py                   # throughput floor

The packaged app has its own smoke path: python editor/studio_imgui.py --smoke 120 boots the UI for 120 frames and reports particle count + FPS.