feat(simulation): thermal diffusion, transformation rules, and projected appearances#89
Conversation
🤖 Augment PR SummarySummary: This PR adds an initial material-transformation layer to the voxel simulation, centered around temperature, a compact rule engine, and a new “Phase 3c” transformation pass. Changes:
Technical Notes: The diffusion step uses a min-of-pair conductivity heuristic with immediate-mode reads from the write buffer, and rule firing is stochastic with priority ordering and a per-chunk transform budget. 🤖 Was this summary useful? React with 👍 or 👎 |
Greptile SummaryThis PR delivers the Wave 1–3 foundation for material transformation: thermal properties on All seven blocking issues flagged in the previous review round are resolved:
One new P1 issue remains: the per-chunk RNG seed Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Tick as VoxelSimulationSystem::tick()
participant TP as TransformationPass::execute()
participant TK as thermalKernel()
participant RE as ruleEvaluation()
participant WRE as WorldRuleEngine::query()
participant Grid as SimulationGrid
participant Tracker as ChunkActivityTracker
Tick->>TP: execute(active, scheduler, worldSeed_)
Note over TP: seed = worldSeed ^ spatialHash(pos)<br/>[frameIndex_ NOT included]
TP->>TK: thermalKernel(chunkPos)
TK->>Grid: readFromWriteBuffer(wx, wy, wz)
TK->>Grid: writeCell(wx, wy, wz, updatedCell)
TP->>RE: ruleEvaluation(chunkPos, rng, activations)
RE->>WRE: query(essence, neighbor, phase, temp, matchBuffer)
Note over WRE: caller-owned matchBuffer<br/>(thread-safe)
WRE-->>RE: matching rules (priority-sorted)
RE->>Grid: writeCell(self + neighbor transforms)
RE-->>TP: activations collected
TP->>Tracker: markSubRegionActive (flush on main thread)
Reviews (5): Last reviewed commit: "fix(simulation): cool R7 quench output b..." | Re-trigger Greptile |
…d thermal systems Five fixes from Augment, Greptile, and Macroscope reviews: - Thread-safe query(): replace shared mutable queryResults_ with caller-owned output buffer, eliminating data race in parallelFor - Phase::Unchanged sentinel (value 7): resolve Phase::Empty dual-use that produced liquid-air ghost cells from boil/evaporation rules - MaterialRegistry synthetic entries: ICE, GLASS, MAGMA now have full registry records (density, conductivity, melt/boil points), removing safeThermalConductivity() fallback and COUNT guards - Self-transform matching: 255 means self-transform only, not wildcard; contact queries no longer inflate self-transform firing rates - Thermal diffusion: skip empty neighbors in averaging, reduce AIR conductivity from 255 to 25; air gaps are thermal insulators
…ules Flat 14-byte WorldRule struct with linear-scan query. Default rules: R1 water freeze, R2 ice thaw, R3 water boil, R4 sand vitrify, R5 stone melt, R6 magma cool, R7 water+magma contact, R8 near-heat evaporation. WorldRuleEngine member added to VoxelSimulationSystem (not yet wired into tick loop). 10 unit tests covering lookup, temperature gating, priority ordering, wildcards, and budget cap.
…ule evaluation Phase 3c runs between boundary drain and epoch advance. Two sub-passes: thermal kernel (min-of-pair conductivity diffusion) and WorldRuleEngine rule evaluation (budget-capped at 64 transforms/chunk). Populates ProjectionRuleTable with ice, glass, and magma projected appearances.
…d thermal systems Five fixes from Augment, Greptile, and Macroscope reviews: - Thread-safe query(): replace shared mutable queryResults_ with caller-owned output buffer, eliminating data race in parallelFor - Phase::Unchanged sentinel (value 7): resolve Phase::Empty dual-use that produced liquid-air ghost cells from boil/evaporation rules - MaterialRegistry synthetic entries: ICE, GLASS, MAGMA now have full registry records (density, conductivity, melt/boil points), removing safeThermalConductivity() fallback and COUNT guards - Self-transform matching: 255 means self-transform only, not wildcard; contact queries no longer inflate self-transform firing rates - Thermal diffusion: skip empty neighbors in averaging, reduce AIR conductivity from 255 to 25; air gaps are thermal insulators
9615198 to
c72bca2
Compare
…fter transforms Two fixes from second-round PR #89 review: - ProjectionRuleTable: extend populateFromRegistry() loop to cover all REGISTRY_SIZE entries, replacing manual setRule() blocks for synthetics with display-only overrides. Density, baseColor, moveType now derived from MaterialRegistry (single source of truth). Fixes GLASS density mismatch (140 vs 160) and MAGMA (190 vs 210). - TransformationPass: call tracker_.markSubRegionActive() after transforms that produce Liquid or Powder cells, so settled chunks re-activate for FallingSand processing. Covers self-transform, contact-transform self, and contact-transform neighbor paths.
Three fixes from Greptile and Macroscope round 3 review: - Fix #9: probability=255 sentinel guard ensures 100% fire rate. Both check sites in ruleEvaluation() now use `rule.probability == 255 || roll < rule.probability`, fixing the 1/256 miss rate on always-fire rules like R7 (water+magma contact). - Fix #11: TransformationPass promoted from per-tick stack local to persistent member of VoxelSimulationSystem. config() accessor now reachable; constructor INFO log fires once at startup, not per frame. - Fix #12: tracker_.markSubRegionActive() calls moved out of parallelFor workers into per-worker SubRegionActivation vectors, flushed on the main thread after dispatch. Eliminates UB from concurrent std::unordered_map::operator[] access. Also adds FABRIC_LOG_DEBUG for cross-chunk contact rule skip (Finding #10).
R7 (water+magma contact) left the neighbor cell at magma temperature after converting it to stone. Since ruleEvaluation reads from the write buffer, R5 (stone melt, 6%, T >= 196) could immediately re-melt the stone within the same tick, producing one-frame magma ghosts. Set resultTempA and resultTempB to 150 on R7, placing both quenched cells below R5's melt threshold (196) and R6's solidification ceiling (194). Also documents WorldRule sentinel conventions (essence 255, Phase::Unchanged, temp 0) in the struct comment.
The per-chunk RNG seed worldSeed ^ spatialHash(pos) was constant across ticks. Cells that missed a stochastic roll on tick 1 would always miss it under identical conditions, making probabilistic rules (freeze 50%, boil 25%, vitrify 12%) deterministic in steady state. XOR frameIndex into the seed so each tick produces a unique random sequence per chunk. Pass frameIndex_ from VoxelSimulationSystem::tick() through to TransformationPass::execute().
Summary
Adds the foundation for material transformation in the simulation pipeline: thermal properties, a rule engine, and a transformation pass that evaluates rules per cell per tick.
cellForMaterial().Known issue
WorldRuleEngine::query()uses a shared mutablequeryResults_scratch buffer. When called fromparallelForworkers inTransformationPass::ruleEvaluation(), this is a data race. The race window is small (results consumed immediately per cell), but it is UB under C++ memory model. Fix planned for Wave 5 when the rule engine dispatch is reworked for full FallingSand absorption.Test plan
mise run buildcompiles cleanlymise run testpasses (2330 tests, 3 skipped for Vulkan runtime)mise run test:filter WorldRuleEnginevalidates rule lookup, temperature gating, priority ordering, wildcardsmise run test:filter TransformationPassvalidates thermal diffusion, skip-stable optimization, rule firing, budget cap, projection table entriesWorldRuleEngine::query()thread-safety note above