Add a scientific route
verifiedAgainst 9a2d070 · verifiedOn 2026-09-10 · the automated staleness banner is planned, not built.
This page authors one route — a substance-and-reaction pair — end to end, then strips the steps into a checklist. The reference edits to read alongside it are the two hardest authored substances (GraphiteSolid.asset, a self-derived Shomate fit; MalachiteSolid.asset, a flat-Cp approximation with a deliberately short validity range) and the ReactionCatalogTests.cs fixtures — the only worked reactions in the repo today.
What you will build
Section titled “What you will build”The malachite roasting route:
Cu2CO3(OH)2(s) -> 2 CuO(s) + CO2(g) + H2O(g) ΔrH°(298 K) = +104 kJ/molConcretely:
- A new
CopperOxideSolid(CuO, tenorite)SubstanceDefinition, stable id 11, with sourced ΔfH° / S° and a Shomate fit. - A new
WaterVaporGasSubstanceDefinition— stable id 4 (the same substance as liquid water) in theGasphase, from NIST’s published gas-phase Shomate set. The roast evolves steam, not liquid. - The project’s first
ReactionDefinition(MalachiteRoasting.asset) and firstReactionCatalog.asset— atom- and charge-balanced, thermodynamically real, with the ArrheniusA/Eaexplicitly deferred the same wayDocs/CHEMISTRY_SOURCES.mddefers the Boudouard and combustion cluster. - EditMode tests mirroring
SubstanceCatalogTestsandReactionCatalogTests.
You will also find that the route’s honest usable temperature window today is only 500–570 K — narrower than the reaction’s real onset — and the page explains why that is the correct state to ship.
The natural-deposits rule does not constrain this page (no new block generates); the binding rule here is Docs/MASTERPLAN.md §28.5 — A and Ea are never invented to hit a play time — and §36.3’s “every thermodynamic value is sourced”.
Where this sits
Section titled “Where this sits”VoxelSandbox.Data.Chemistryowns the authoring layer:SubstanceDefinition/SubstanceCatalogandReactionDefinition/ReactionTermAuthoring/ReactionCatalog. These are ScriptableObjects a designer edits.VoxelSandbox.Chemistryowns the immutable baked targets —ThermodynamicPhaseDefinition,SubstanceTable,ChemicalReaction— and the math:ShomateThermodynamics,ReactionThermodynamics,ReactionKineticsMath,AtomBalance/ChargeBalance. It has noUnityEnginereference.- The bake direction is one way:
Data.Chemistry→Chemistry.Chemistrynever sees a ScriptableObject. See Assemblies & boundaries.
flowchart LR SD["SubstanceDefinition\nCuO, H2O(g) — authored"] SC["SubstanceCatalog\nvalidated list"] ST["SubstanceTable\nbaked, immutable"] RT["ReactionTermAuthoring\nsubstance + coefficient + order"] RD["ReactionDefinition\nterms + kinetics + citation"] RC["ReactionCatalog\nvalidated list"] CR["ChemicalReaction\nbaked, atom/charge-balanced"] TH["ReactionThermodynamics\nΔrH° ΔrS° ΔrG° K"] KI["ReactionKineticsMath\nk(T)=A·exp(-Ea/RT)"] SD --> SC --> ST SD --> RT --> RD --> RC --> CR ST --> TH CR --> TH CR --> KI KI -. blocked: no sourced A/Ea .-> CR
Before you start
Section titled “Before you start”Read these Docs/ sections:
Docs/CHEMISTRY_FOUNDATIONS.md— R1 (deterministic transcendentals), D1 (the 60× process-rate factor; thermodynamic state is never scaled), C0 sub-stepping.Docs/CHEMISTRY_SOURCES.md— “Kinetics and reaction affinity”, “Reactions — none authored yet” (read this in full — it is the template for your deferral paragraph), and “Known gaps”.Docs/MASTERPLAN.md§28.5 (kinetics sourcing bar), §28.6 (redox, if your route is electrochemical), §36.1–36.3 (the authoring layer and the copper chain).- The existing citations in
Assets/_Game/Data/Chemistry/Substances/GraphiteSolid.assetandMalachiteSolid.asset— the standard this project holds asourceCitationto.
Intake decisions for this route:
| Decision | Choice | Why |
|---|---|---|
| New substances needed? | CuO(s) (new id 11); H2O(g) (new phase of id 4) |
neither is in the catalog; the liquid-water Shomate is capped at 500 K and is the wrong phase for a roast |
| Thermodynamics | sourced | ΔfH° / S° from NIST-JANAF / CRC; Shomate from a published set or a documented fit |
Kinetics (A, Ea) |
deferred | no single citable rate constant for malachite decomposition — TGA studies span a wide Ea; §28.5 forbids picking one |
| Redox? | no | not an electron-transfer reaction |
| Surrogate? | yes | a deferred-kinetics reaction is excluded from accuracy claims (isSurrogate = true) |
Validation: the Chemistry Lab module (Tools ▸ Voxel Sandbox ▸ Voxel Workshop ▸ Chemistry Lab) validates the SubstanceCatalog and evaluates one substance’s Cp / S / ΔfH°(T). It has no reaction preview yet — the reaction is validated by an EditMode test.
The build, step by step
Section titled “The build, step by step”1. Author CuO — the hard substance
Section titled “1. Author CuO — the hard substance”SubstanceDefinition (SubstanceDefinition.cs:16) carries the fields below, and TryBuildDefinition (:97) rejects it if the sourceCitation (:48) is blank, if the formula-derived molar mass disagrees with the authored value by more than 1 %, or if the Shomate range is unordered.
Create Assets/_Game/Data/Chemistry/Substances/CopperOxideSolid.asset (menu: Voxel Sandbox/Chemistry/Substance Definition), matching the field shape of MalachiteSolid.asset:
# Assets/_Game/Data/Chemistry/Substances/CopperOxideSolid.asset — the authored fieldsstableId: 11displayName: Copper(II) Oxideformula: CuOphase: 0 # SolidmolarMassGramsPerMol: 79.545standardEnthalpyOfFormationKilojoulesPerMol: -157.3 # measured — NIST-JANAF / CRC 97th ed.standardEntropyJoulesPerMolKelvin: 42.6 # measured — samedensityKilogramsPerCubicMeter: 6315 # tenorite, calculated from the unit cellshomate: minimumTemperatureKelvin: 298.15 maximumTemperatureKelvin: 1300 a: 44.0 # illustrative — fit these from the NIST-JANAF CuO Cp(T) rows b: 12.0 # the same least-squares way GraphiteSolid.asset was fit; c: 0.0 # do NOT ship these placeholder numbers. d: 0.0 e: -0.4 f: -170.6 g: 90.0 h: -157.3 # = ΔfH°, by the Shomate conventionThe shomate block above is illustrative. Follow GraphiteSolid.asset’s citation: NIST-JANAF publishes CuO as a row-by-row Cp / S / H−H298 table, not pre-fit Shomate coefficients — fit the Shomate Cp polynomial to those rows by unweighted least squares over 298–1300 K, record the max relative error, and calibrate F and G so H(T)−H298 and S match the table at 298.15 K. H is the standard enthalpy of formation.
The sourceCitation is required and is prose, not a URL list. Name every number’s source, its uncertainty, and — as MalachiteSolid.asset does — say plainly where the data is an approximation and what a future pass should replace.
2. Author H2O(g) — the easy substance
Section titled “2. Author H2O(g) — the easy substance”Steam has a published Shomate set (NIST-JANAF via the NIST WebBook, Chase 1998), so no fitting is needed. It is the same substance as WaterLiquid.asset — reuse stableId: 4 and change only the phase. SubstanceCatalog.ValidateCatalog (SubstanceCatalog.cs:46) keys uniqueness on (SubstanceId, Phase) — (4, Gas) and (4, Liquid) coexist cleanly.
# Assets/_Game/Data/Chemistry/Substances/WaterVaporGas.assetstableId: 4displayName: Water Vapourformula: H2Ophase: 2 # GasmolarMassGramsPerMol: 18.0153standardEnthalpyOfFormationKilojoulesPerMol: -241.8264 # measured — NIST-JANAF (Chase 1998)standardEntropyJoulesPerMolKelvin: 188.835 # measured — samedensityKilogramsPerCubicMeter: 0.804shomate: minimumTemperatureKelvin: 500 # NIST publishes the 500–1700 K set maximumTemperatureKelvin: 1700 a: 30.09200 b: 6.832514 c: 6.793435 d: -2.534480 e: 0.082139 f: -250.8810 g: 223.3967 h: -241.8264Those nine numbers are copied verbatim from the NIST WebBook water (gas) page — cite it as such, with the CAS number, in the sourceCitation. (evidence: measured)
3. Register both in the catalog — SubstanceCatalog.asset
Section titled “3. Register both in the catalog — SubstanceCatalog.asset”Add both .asset GUIDs to the definitions: list in Assets/_Game/Data/Chemistry/SubstanceCatalog.asset (the Inspector’s list, or the YAML directly):
definitions: - {fileID: 11400000, guid: <existing…>, type: 2}+ - {fileID: 11400000, guid: <CopperOxideSolid guid>, type: 2}+ - {fileID: 11400000, guid: <WaterVaporGas guid>, type: 2}TryBuildTable (SubstanceCatalog.cs:87) refuses to bake if any entry has a validation issue, so a half-authored citation fails the whole table rather than silently dropping one row.
4. Author the reaction — MalachiteRoasting.asset
Section titled “4. Author the reaction — MalachiteRoasting.asset”ReactionDefinition (ReactionDefinition.cs:16) holds a List<ReactionTermAuthoring> plus the kinetics block (:27). Each ReactionTermAuthoring (ReactionTermAuthoring.cs:13) is a SubstanceDefinition reference, a signed stoichiometric coefficient (negative = reactant, positive = product, never zero), and a rate-law order used only for reactants.
The four terms:
| Substance | Coefficient | Formula contribution |
|---|---|---|
MalachiteSolid (id 9) |
-1 |
Cu₂ C₁ O₅ H₂ |
CopperOxideSolid (id 11) |
+2 |
Cu₂ O₂ |
CarbonDioxideGas (id 2) |
+1 |
C₁ O₂ |
WaterVaporGas (id 4, Gas) |
+1 |
H₂ O₁ |
Reactant and product atom sums are both Cu₂ C₁ O₅ H₂; every formal charge is 0. The build in tests looks like the ReactionCatalogTests fixture (ReactionCatalogTests.cs:33):
// illustrative — the authoring call, mirroring CreateHydrogenCombustion()var terms = new[]{ ReactionTermAuthoring.CreateDefault(malachite, -1), ReactionTermAuthoring.CreateDefault(copperOxide, +2), ReactionTermAuthoring.CreateDefault(carbonDioxide, +1), ReactionTermAuthoring.CreateDefault(waterVapour, +1),};
var roasting = ReactionDefinition.CreateDefault( stableId: 1, "Malachite roasting", terms, preExponentialFactor: 1d, // deferred — see the citation activationEnergyKilojoulesPerMol: 0d, isHeterogeneous: true, // a solid decomposing with gas products isSurrogate: true, // excluded from accuracy claims until Ea is sourced sourceCitation: "<the deferral paragraph — see below>");TryBuildReaction (ReactionDefinition.cs:90) forwards to ChemicalReaction.TryCreate (ChemicalReaction.cs:193), which runs AtomBalance.Check and ChargeBalance.Check on the supplied formulae and fails with "Reaction does not conserve atoms" (:243) or "…charge" (:250) otherwise. The balance is proved at construction — you cannot get an unbalanced ChemicalReaction.
5. The kinetics deferral — the crux of this page
Section titled “5. The kinetics deferral — the crux of this page”ReactionDefinition requires a non-empty sourceCitation (:53) for its kinetics — the field docstring quotes §28.5: “A and Ea are never invented merely to hit a desired play time.” There is no single citable Arrhenius pair for malachite (basic copper carbonate) thermal decomposition: reported activation energies from TGA / DSC studies span roughly 80–170 kJ/mol depending on sample, atmosphere, and the model fitted, and the reaction proceeds through hydroxide loss then carbonate loss rather than one elementary step.
So the honest state — matching how CHEMISTRY_SOURCES.md treats Boudouard, combustion, slaking and calcination — is:
- author the reaction with
isSurrogate: trueand placeholderA = 1,Ea = 0; - write a
sourceCitationthat states, in prose: the balanced equation and its thermodynamic sources; that the kinetics are deferred; the literature situation (theEaspread, the multi-step mechanism); and what a future pass must find (a primary TGA study for one well-characterised malachite, or a defensible multi-step scheme); - keep the
ReactionCatalog.assetout of any solver-loaded path until real kinetics land.VesselRuntimetakes optional catalog references — do not wire this one in.
A reader who wants the number later has the balanced, thermodynamically-anchored reaction ready; only the rate law is missing, and its absence is documented at the point of use.
6. The first ReactionCatalog.asset
Section titled “6. The first ReactionCatalog.asset”Create Assets/_Game/Data/Chemistry/ReactionCatalog.asset (menu: Voxel Sandbox/Chemistry/Reaction Catalog) and add MalachiteRoasting.asset to its definitions: list. TryBuildReactions (ReactionCatalog.cs:69) validates every entry, then enforces reaction-id uniqueness at bake time (matching VesselSolver’s own duplicate check). This asset exists so the authoring path is exercised and the reaction is diffable — not so the solver runs it.
7. What the thermodynamics say — and their ceiling
Section titled “7. What the thermodynamics say — and their ceiling”ReactionThermodynamics.TryEvaluate (ReactionThermodynamics.cs:72) sums Σνᵢ(ΔfH°298 + [H°(T)−H°298]) and ΣνᵢS°(T) in the supplied term order, then derives ΔrG° = ΔrH° − TΔrS° and K = exp(−ΔrG°/RT). From the catalogued ΔfH° values:
ΔrH°(298 K) = 2(-157.3) + (-393.5224) + (-241.8264) - (-1053.9) = +103.95 kJ/mol(evidence: calculated from the catalogued standard enthalpies of formation) — endothermic, as a roast must be.
The ceiling. ReactionThermodynamics.TryEvaluate calls the strict ShomateThermodynamics.TryEvaluate (ShomateThermodynamics.cs:129) for every term and returns false if any one is out of range. The per-species valid ranges here are:
| Species | Shomate range | Set by |
|---|---|---|
Cu2CO3(OH)2(s) |
298.15 – 570 K | MalachiteSolid.asset — a flat-Cp approximation, capped below the roast onset on purpose |
H2O(g) |
500 K – 1700 K | NIST’s published set starts at 500 K |
CuO(s) |
298.15 – 1300 K | your fit |
CO2(g) |
298 – 1200 K | CarbonDioxideGas.asset |
The intersection is 500–570 K — a 70 K window, and the reaction physically runs above ~600 K. This is the correct thing to ship: the malachite entry’s own citation says its high-temperature Cp is unsourced and “a future pass should replace [it] with a sourced polynomial before trusting this substance above room temperature.” Your route inherits that gap; document it, don’t paper over it.
8. Tests — Tests/EditMode/Data/
Section titled “8. Tests — Tests/EditMode/Data/”Add MalachiteRouteTests.cs (or extend ReactionCatalogTests), following the fixture pattern — build the definitions with CreateDefault, assert, and DestroyImmediate in [TearDown] (ReactionCatalogTests.cs:13):
// illustrative[Test]public void MalachiteRoasting_BalancesAndBuilds(){ ReactionDefinition roasting = CreateMalachiteRoasting(); Assert.That(roasting.TryBuildReaction(out ChemicalReaction reaction, out string error), Is.True, error); Assert.That(reaction.SpeciesCount, Is.EqualTo(4));}
[Test]public void MalachiteRoasting_ThermodynamicsEvaluateInsideTheSharedWindow(){ // 540 K is inside 500–570 K, the only band where every species has valid Shomate data. var terms = BuildThermodynamicTerms(); Assert.That(ReactionThermodynamics.TryEvaluate(terms, 540d, out ReactionThermodynamicState state), Is.True); Assert.That(state.EnthalpyKilojoulesPerMol, Is.GreaterThan(0d)); // endothermic}
[Test]public void MalachiteRoasting_ThermodynamicsFailAboveTheMalachiteCeiling(){ var terms = BuildThermodynamicTerms(); Assert.That(ReactionThermodynamics.TryEvaluate(terms, 650d, out _), Is.False);}Also extend SubstanceCatalogTests with a build assertion for each new .asset, mirroring ValidDefinition_BuildsIntoASubstanceTable (SubstanceCatalogTests.cs:36).
Verify
Section titled “Verify”Run the Data tests headlessly (path from CLAUDE.md):
cd /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD"/home/soulwax/Unity/Hub/Editor/6000.6.0f1/Editor/Unity" -runTests -batchmode \ -projectPath . -testPlatform EditMode \ -testFilter "VoxelSandbox.Tests.ReactionCatalogTests|VoxelSandbox.Tests.SubstanceCatalogTests|VoxelSandbox.Tests.MalachiteRouteTests" \ -testResults ./Logs/route.xml -logFile ./Logs/route.logThen, in the open Editor — Chemistry Lab:
- The validation summary reads “12 substance(s), all valid.” (10 existing + CuO + steam), no error rows.
- Select Copper(II) Oxide (CuO, Solid) — Cp / S / ΔfH°(T) evaluate at 298.15 K and at 1000 K.
- Select Water Vapour (H2O, Gas) — evaluates at 1000 K; at 298.15 K it reports “outside the Shomate range” (the set starts at 500 K). That message is expected, not a bug.
“Done” = a green EditMode run, both substances valid in Chemistry Lab, and the reaction building + balancing + evaluating inside 500–570 K in the test.
Now do your own
Section titled “Now do your own”- Name the route and write the balanced equation first. State symbols on every species. Confirm the atom and charge sums by hand before you touch code —
ChemicalReaction.TryCreatewill reject an unbalanced set, but knowing why it balances is the point. - List the substances the route needs and check the catalog. For each missing one: a
SubstanceDefinitionwithstableId(a genuinely new substance takes the next free id; a new phase of an existing substance reuses its id), formula, phase, molar mass (within 1 % of the formula), ΔfH°, S°, density, a Shomate set, and a prosesourceCitation. - Source, don’t fit, when you can. A published Shomate set (NIST WebBook gas-phase species) is copied verbatim. A row-by-row
Cp(T)table (JANAF solids, minerals) is least-squares fit — record the max error and calibrateF,Gat 298.15 K, asGraphiteSolid.assetdoes. A single 298 KCppoint is a flat-Cp approximation with a short, honest validity cap, asMalachiteSolid.assetdoes. - Register every new
.assetinSubstanceCatalog.asset.TryBuildTableis all-or-nothing. - Author the
ReactionDefinition: signed integer coefficients, oneReactionTermAuthoringper species, rate-law orders on reactants. - Kinetics — the decision that varies most:
- sourced — a primary-source Arrhenius
A/Eain the right units (check cal vs J onEa), converted into this codebase’s dimensionless-activity, whole-vessel-rate contract (r = k·Πaᵢ^νᵢ·S·(1−Q/K)) and checked against the C0 acceptance values. Real work; see the CO-oxidation note inCHEMISTRY_SOURCES.md. - deferred —
isSurrogate: true, placeholderA/Ea, asourceCitationthat says what is missing and why, and the.assetkept out of any solver-loaded catalog.
- sourced — a primary-source Arrhenius
- Redox routes additionally set
isRedox,electronsTransferred, and a sourcedstandardCellPotentialVolts(§28.6; see the Electrochemistry Lab module andElectrochemicalSeries). - Check the shared Shomate window.
ReactionThermodynamicsneeds every species valid at the evaluation temperature. Intersect the ranges; if the window doesn’t cover where the reaction runs, that is a finding to document. - Tests mirroring
SubstanceCatalogTests/ReactionCatalogTests— build, balance, evaluate in-window, fail out-of-window. CHANGELOG.md+VERSIONbump; update the “Reactions — none authored yet” section ofCHEMISTRY_SOURCES.mdif you authored a real one.
Pitfalls
Section titled “Pitfalls”ΔfH° and the Shomate H from different sources.
Symptom: ΔfH°(T) drifts from the literature as T leaves 298 K; a reaction’s ΔrH°(T) is wrong only away from standard temperature.
Cause: the Shomate H term and the standalone standardEnthalpyOfFormation were taken from datasets with different reference conventions.
Fix: by the Shomate convention H is the standard enthalpy of formation — set them from the same source, equal.
Inventing A / Ea to make the route “work in-game”.
Symptom: code review or a science audit flags the reaction; the citation can’t be defended.
Cause: a plausible-looking Arrhenius pair was chosen to hit a play time — exactly what §28.5 forbids.
Fix: defer it (isSurrogate, documented, out of the solver path). A missing rate law that is honest beats a present one that is fiction.
An unbalanced equation “almost” reaching the solver.
Symptom: TryBuildReaction returns false with "does not conserve atoms".
Cause: a coefficient sign or magnitude is wrong, or a formula string doesn’t parse the way ChemicalFormula.Parse reads it (check parentheses like Cu2CO3(OH)2).
Fix: the balance proof is the feature. Fix the stoichiometry; the reaction cannot exist unbalanced.
A Shomate fit used outside its validity range.
Symptom: ReactionThermodynamics.TryEvaluate returns false at the temperature the reaction actually runs.
Cause: one species’ Shomate range (often a flat-Cp approximation deliberately capped, like malachite’s 570 K) doesn’t reach the reaction temperature.
Fix: intersect all species’ ranges; either source a wider fit for the limiting species or ship the route with its window documented (as this page does).
Reusing a substance id for a genuinely different substance.
Symptom: the catalog reports a duplicate (SubstanceId, Phase), or worse, silently maps two materials to one table row.
Cause: stableId is the substance’s identity. A new phase of water is still id 4; copper oxide is a new substance and takes a new id.
Fix: next free id for a new substance; the same id + a different phase for a new phase.
Skipping the sourceCitation.
Symptom: TryBuildDefinition / TryBuildReaction returns false with "no source citation"; Chemistry Lab shows an error row.
Cause: the field is required by design — there is no valid unsourced entry.
Fix: write the prose citation. If you can’t source a number, you can’t author the entry yet.
See also
Section titled “See also”- Assemblies & boundaries — why
VoxelSandbox.Chemistrycarries noUnityEnginereference and never sees a ScriptableObject. - Add a voxel block type — its deferred
BlockDefinition.compositionstep is completed by authoring the substance here. Docs/CHEMISTRY_SOURCES.md“Reactions — none authored yet” and “Known gaps”;Docs/CHEMISTRY_FOUNDATIONS.mdR1 / D1 / C0;Docs/MASTERPLAN.md§28.5, §36.1–36.3.
User-contributed notes
Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works:
**bold**,*italic*,`code`, and links.Notes policy
Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked
nofollow.