--- name: simulation-dev description: General simulation development best practices for correctness, stability, and debuggability. Use when implementing or modifying simulation systems, solvers, constraints, or GPU kernels, especially for index safety, NaN/Inf issues, and diagnostics. --- # Simulation Development Best Practices General rules for building simulation code that is correct first, debuggable second, and fast third. ## When to Use Use this skill when: - adding or modifying simulation systems, solvers, integrators, constraints, or contact logic, - touching CPU/GPU compute paths that can fail from invalid indices or unstable numerics, - debugging crashes, divergence, NaN/Inf, or non-deterministic behavior, - improving diagnostics, assertions, and reproducibility. ## Core Principles 1. Correctness before performance. 2. Fail fast on invalid states. 3. Prefer deterministic behavior during debugging. 4. Prefer MUDA-native diagnostics for GPU paths whenever possible. 5. Validate data at subsystem boundaries. 6. Instrument first, optimize after evidence. ## Safety Rules (Always Apply) ### Input and shape validation - Validate counts, offsets, dimensions, and index ranges before compute. - Verify assumptions at boundaries (API input, scene load, assembly, solver entry). - Use clear sentinel conventions and assert them (`-1`, empty, null, etc.). ### Index safety for GPU/parallel code - Guard every computed index before read/write. - Return early on invalid thread work instead of continuing with partial writes. - Prefer host-side prevalidation to avoid propagating bad mappings into kernels. ### Numerical robustness - Check for finite values (`isfinite`) at key checkpoints. - Assert on first invalid scalar near source (residuals, energies, step sizes, norms). - Add conservative guards for divide-by-near-zero and invalid square roots/logs. ## Assertion Best Practices Every assertion should answer: 1. What failed? 2. Where in the pipeline? 3. With what values? 4. What is the likely cause? Checklist for high-quality assert messages: - include stage/iteration/frame context, - include offending value and expected range/invariant, - include at least one hint for root-cause direction, - keep wording short and actionable. ## Diagnostics and Observability - Use MUDA diagnostics as the default for GPU debugging: - attach file/line metadata on launches, - assign explicit names to views/buffers, - prefer MUDA launch wrappers over raw CUDA launch sites when practical. - Attach source location metadata to kernel/parallel launches when available. - Name critical buffers/views to make reports readable. - Keep optional debug dumps for important intermediates (vectors, matrices, topology maps). - Use scoped timers around major phases and hot sub-phases. - Make debug instrumentation easy to toggle with config flags. ## MUDA Debug Checklist For each touched GPU code path: - Do: add launch source mapping (for example, `file_line(__FILE__, __LINE__)`). - Do: name captured views/buffers (for example, `viewer().name("x")`) for readable diagnostics. - Do: use MUDA launch/parallel wrappers as first choice for new or modified kernels. - Do: use `muda::debug_sync_all()` as a fast-fail barrier while debugging broken kernels. - Do: keep index guards at the top of kernel bodies before any memory access. - Do: preserve a low-overhead path when debug flags are off. - Don't: add raw CUDA launch sites unless there is a clear, documented need. - Don't: leave anonymous captures for critical buffers in hot or unstable paths. - Don't: rely on post-mortem logs only; add immediate context at the failure point. ## Deterministic Debug Workflow 1. Reproduce on the smallest case that still fails. 2. Freeze nondeterminism where possible: - fixed seeds, - stable ordering, - reduced parallel variability. 3. Enable strict checks and diagnostic output. 4. Bisect by pipeline stage (assemble -> solve -> update). 5. Narrow to first bad value and assert there. ## Simulation Debug Loop (Iterative) Use this loop as the default process for debugging simulation issues: 1. Build a minimal reproducible scene/case that is easy to debug. 2. Use diagnostics to understand behavior, form a concrete assumption, then add debug code to validate that assumption. 3. After validating the assumption, implement the fix in the minimal case first. 4. Re-run the original scene/case to verify the real issue is resolved. 5. Add a regression test for the confirmed fix. If any step fails or new evidence contradicts the assumption, return to step 1 or 2 and iterate until root cause and fix are both confirmed. During active GPU debugging, insert `muda::debug_sync_all()` at key boundaries to stop immediately when a kernel fails, then remove or gate it once diagnosis is complete. ## Convergence and Stability Guidelines - Separate stopping criteria from failure criteria. - Distinguish "did not converge yet" from "invalid state". - Track monotonic indicators where expected (energy, residual envelopes). - For iterative methods, sample/check periodically to limit synchronization overhead. - Provide explicit fallback or abort policy for non-convergence in strict mode. ## Change Workflow 1. Write/modify logic with invariants first. 2. Add assertions at boundaries and first-use sites. 3. Add diagnostics (timers, buffer names, optional dumps). 4. Verify on: - minimal case, - representative case, - stress case. 5. Only then profile and optimize.