Skip to content

feat(simulation): thermal diffusion, transformation rules, and projected appearances#89

Merged
mannie-exe merged 7 commits into
devfrom
feat/essence-first-world-semantics
Mar 23, 2026
Merged

feat(simulation): thermal diffusion, transformation rules, and projected appearances#89
mannie-exe merged 7 commits into
devfrom
feat/essence-first-world-semantics

Conversation

@mannie-exe
Copy link
Copy Markdown
Contributor

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.

  • Wave 1: Temperature accessors on VoxelCell and MatterState. MaterialRegistry populated with thermal conductivity, melt point, and boil point for all 7 base materials. Default temperature (100) assigned in cellForMaterial().
  • Wave 2: WorldRuleEngine with flat 14-byte WorldRule struct. 8 default transformation rules covering freeze, thaw, boil, vitrify, stone melt, magma cool, water+magma contact, and near-heat evaporation. Linear scan query with priority ordering.
  • Wave 3: TransformationPass inserted as Phase 3c between boundary drain and epoch advance. Sub-pass A runs fixed-function thermal diffusion (min-of-pair conductivity, Gauss-Seidel immediate-mode reads, skip-stable optimization). Sub-pass B evaluates WorldRuleEngine rules with budget cap (64 transforms per chunk) and stochastic firing. ProjectionRuleTable populated with ice (blue-white), glass (amber), and magma (orange-red) projected appearances.

Known issue

WorldRuleEngine::query() uses a shared mutable queryResults_ scratch buffer. When called from parallelFor workers in TransformationPass::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 build compiles cleanly
  • mise run test passes (2330 tests, 3 skipped for Vulkan runtime)
  • mise run test:filter WorldRuleEngine validates rule lookup, temperature gating, priority ordering, wildcards
  • mise run test:filter TransformationPass validates thermal diffusion, skip-stable optimization, rule firing, budget cap, projection table entries
  • Review WorldRuleEngine::query() thread-safety note above

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Mar 23, 2026

🤖 Augment PR Summary

Summary: 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:

  • Introduces temperature accessors on VoxelCell/MatterState by storing temperature in the spare byte, and initializes default temperature (100) in cellForMaterial().
  • Extends MaterialRegistry with thermal properties (conductivity, melt/boil points) for the 6 base materials.
  • Adds WorldRuleEngine with a fixed-size (14-byte) WorldRule and 8 default transformation rules (freeze/thaw/boil/vitrify/melt/cool/contact/evap).
  • Adds TransformationPass (inserted between boundary drain and epoch advance) that runs per-chunk thermal diffusion and then rule evaluation with a per-chunk budget cap.
  • Updates ProjectionRuleTable with projected appearances for transformation products (ice/glass/magma) that are outside the current material registry range.
  • Adds unit tests for WorldRuleEngine and TransformationPass; adjusts existing material registry tests to match updated essence distributions.

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 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/recurse/simulation/WorldRuleEngine.cc Outdated
Comment thread src/recurse/simulation/WorldRuleEngine.cc Outdated
Comment thread src/recurse/simulation/TransformationPass.cc Outdated
Comment thread src/recurse/simulation/TransformationPass.cc
Comment thread include/recurse/simulation/CellAccessors.hh
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 23, 2026

Greptile Summary

This PR delivers the Wave 1–3 foundation for material transformation: thermal properties on MaterialRegistry, a flat 14-byte WorldRuleEngine with 8 default rules, and TransformationPass (Phase 3c) running thermal diffusion + stochastic rule evaluation per chunk per tick.

All seven blocking issues flagged in the previous review round are resolved:

  • Ghost-cell boil bugPhase::Unchanged (value 7) is now the "no-op" sentinel; Phase::Empty in R3/R8 correctly clears the cell.
  • displacementRank for synthetic essences — guard changed from < material_ids::COUNT to != 255, so ICE/GLASS/MAGMA displacement ranks are pulled from MaterialRegistry.
  • Data race on queryResults_query() now takes a caller-owned std::vector<WorldRule>&; each parallel worker holds its own matchBuffer on the stack.
  • tracker_ never calledruleEvaluation collects SubRegionActivation entries and flushes them to tracker_ on the main thread after parallelFor completes.
  • probability = 255 sentinelexecute() correctly short-circuits with rule.probability == 255 || roll < rule.probability.
  • R7 intra-tick re-melt — R7 now writes resultTempB = 150, quenching the ex-magma stone below R5's 196° threshold; R7QuenchCoolsBelowMeltThreshold validates this.
  • Density mismatchpopulateFromRegistry iterates to registrySize() so ICE/GLASS/MAGMA density is derived from MaterialRegistry, not hardcoded.

One new P1 issue remains: the per-chunk RNG seed worldSeed ^ spatialHash(pos) is constant across all ticks because worldSeed_ never changes and frameIndex_ is not threaded through to execute(). Cells that miss a stochastic roll on tick 1 will always miss it, making probabilistic rules (freeze, boil, evaporate) deterministic in steady state.

Confidence Score: 3/5

  • Good forward progress — all prior blocking issues are resolved — but the deterministic RNG seed causes stochastic rules to silently stop working in steady-state, which affects the primary simulation path before this can ship.
  • Seven previously blocking issues are cleanly addressed, and the new code is well-tested. The single remaining P1 (constant RNG seed across ticks) directly breaks the 50%/25%/12% probabilistic phase-change rules in production — water at freezing temperature will permanently fail to freeze once it misses its first roll. This warrants a 3 rather than 4 because it breaks the primary behavior the PR is designed to deliver.
  • src/recurse/simulation/TransformationPass.cc (RNG seeding) and src/recurse/simulation/VoxelSimulationSystem.cc (call site needs to pass frameIndex_)

Important Files Changed

Filename Overview
src/recurse/simulation/TransformationPass.cc Core transformation pass — thermal diffusion and rule evaluation. Correctly resolves previous blocking issues (Phase::Unchanged sentinel, displacementRank update, tracker activation, probability=255 guard), but the per-chunk RNG seed omits frameIndex so stochastic rules produce deterministic outcomes across ticks.
src/recurse/simulation/WorldRuleEngine.cc 8 default transformation rules (freeze/thaw/boil/vitrify/melt/cool/quench/evaporate). All previously flagged issues resolved: probability=255 is gone in query() (caller-owned buffer), R7 sets resultTempB=150 to prevent intra-tick re-melt, query() is now thread-safe via caller-provided buffer.
src/recurse/simulation/ProjectionRuleTable.cc Density mismatch previously flagged is resolved: populateFromRegistry now iterates to registrySize() so ICE/GLASS/MAGMA density comes directly from MaterialRegistry. applyDisplayOverride only sets displayName and reductionTiebreak, never hardcodes density.
src/recurse/simulation/VoxelSimulationSystem.cc Wires TransformationPass into tick() as Phase 3c between boundary drain and epoch advance. frameIndex_ is incremented after the transformation pass but is not passed to execute(), which is the root cause of the deterministic RNG issue.
tests/unit/simulation/TransformationPassTest.cc 10 well-structured tests covering thermal diffusion, budget cap, rule firing, sub-region activation, and projection table. Test #8 (Probability255AlwaysFires) correctly validates the 100% sentinel. However, RuleFiringBasic/BudgetCap tests use manually varied seeds per attempt, masking the deterministic-seed production bug.

Sequence Diagram

sequenceDiagram
    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)
Loading

Reviews (5): Last reviewed commit: "fix(simulation): cool R7 quench output b..." | Re-trigger Greptile

Comment thread src/recurse/simulation/WorldRuleEngine.cc
Comment thread src/recurse/simulation/TransformationPass.cc
Comment thread include/recurse/simulation/WorldRuleEngine.hh
mannie-exe added a commit that referenced this pull request Mar 23, 2026
…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
@mannie-exe mannie-exe force-pushed the feat/essence-first-world-semantics branch from 9615198 to c72bca2 Compare March 23, 2026 06:29
Comment thread src/recurse/simulation/ProjectionRuleTable.cc Outdated
Comment thread src/recurse/simulation/ProjectionRuleTable.cc Outdated
Comment thread src/recurse/simulation/ProjectionRuleTable.cc Outdated
Comment thread src/recurse/simulation/TransformationPass.cc Outdated
…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.
Comment thread src/recurse/simulation/TransformationPass.cc
Comment thread src/recurse/simulation/WorldRuleEngine.cc
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).
@inherent-design inherent-design deleted a comment from greptile-apps Bot Mar 23, 2026
Comment thread src/recurse/simulation/TransformationPass.cc
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.
@inherent-design inherent-design deleted a comment from greptile-apps Bot Mar 23, 2026
@mannie-exe
Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread src/recurse/simulation/TransformationPass.cc Outdated
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().
@mannie-exe mannie-exe merged commit fd70fe3 into dev Mar 23, 2026
5 of 6 checks passed
@mannie-exe mannie-exe deleted the feat/essence-first-world-semantics branch March 23, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant