Skip to content
Edit on GitHub

Author a quantitative ore assay and vessel charge profile

verifiedAgainst 05ea923 · verifiedOn 2026-09-10.

In Voxamine, mining an ore block does not yield a pure, magical metal nugget. An ore block is an authentic geological composite composed of primary mineral phases and inert waste rock (gangue). Furthermore, natural deposits exhibit continuous grade gradients: deposit fringes are lean, while deep vein cores are rich (MASTERPLAN.md §30.3 / §36.4).

You will author a complete mineral assay and vessel charging profile for an ore block by defining its BlockCompositionComponent entries, evaluating grade-aware chemical yields in BlockCompositionCharging, modeling realistic instrument error bars across visual hand inspection, hydrometer density, and fire assaying via OreAssayMath, and auditing the resulting vessel charge inside the Assay Lab Voxel Workshop module (AssayLabModule.cs).

// Grade-aware charging from mined coordinate into a chemical vessel:
double grade = OreGradeMath.GradeAt(worldX, worldY, worldZ, settings);
BlockCompositionCharging.TryChargeIntoVessel(malachiteBlock, count: 4, grade, vesselCharge, out string error);
// Yields proportional moles of Cu2CO3(OH)2(s) and inverse moles of SiO2(s) gangue!

The ore assay and charging pipeline connects spatial world generation, authored block definitions, pure chemistry calculations, and industry vessels:

  • World Generation (OreGradeMath) computes continuous 3D noise yielding an authoritative ore grade $g \in [0, 1]$ at the mined coordinate.
  • Data Blocks (BlockDefinition.Composition) pairs each block with its authored substances, molar base quantities, and component roles (PrimaryOre, Gangue, or Fixed).
  • Charging Service (BlockCompositionCharging) converts mined blocks at grade $g$ into discrete chemical moles within a VesselCharge.
  • Measurement Engine (OreAssayMath & OreAssayNarrator) evaluates what the player’s instruments can detect: qualitative eyeball bands, density approximations, or quantitative fire assays.
  • Voxel Workshop (AssayLabModule) provides an interactive authoring scratchpad to inspect yields, slide grade values, and verify narratives.
flowchart TD
  subgraph VOXEL["World & Mining"]
    COORD["Mined Block Coordinate\n(X, Y, Z)"]
    GRADE["OreGradeMath.GradeAt\n(Continuous grade g ∈ [0, 1])"]
  end

  subgraph DEFINITION["Assets/_Game/Data/Blocks/"]
    BLOCK["BlockDefinition\n(Malachite Ore, Cassiterite, etc.)"]
    COMP_ORE["BlockCompositionComponent\n(Role = PrimaryOre, base moles)"]
    COMP_GANGUE["BlockCompositionComponent\n(Role = Gangue, base moles)"]
  end

  subgraph CHARGE_PATH["VoxelSandbox.Data.Blocks"]
    CHARGER["BlockCompositionCharging\nYieldMolesAt(grade, count)"]
    VESSEL["VesselCharge\n(Immutable moles of substance & phase)"]
  end

  subgraph ASSAY_PATH["VoxelSandbox.Chemistry"]
    INSTRUMENTS["AssayMethod\n(HandSpecimen, Density, FireAssay)"]
    ASSAY_MATH["OreAssayMath.AssaySample\n(Scattered measurement ± uncertainty)"]
    NARRATOR["OreAssayNarrator.Describe\n(Plain-language science text)"]
  end

  subgraph WORKSHOP["Voxel Workshop"]
    LAB["AssayLabModule\n(Block picker, grade slider, charge audit)"]
  end

  COORD --> GRADE
  BLOCK --> COMP_ORE
  BLOCK --> COMP_GANGUE
  COMP_ORE --> CHARGER
  COMP_GANGUE --> CHARGER
  GRADE --> CHARGER
  CHARGER --> VESSEL
  GRADE --> ASSAY_MATH
  INSTRUMENTS --> ASSAY_MATH
  ASSAY_MATH --> NARRATOR
  BLOCK --> LAB
  CHARGER --> LAB
  NARRATOR --> LAB

Read Docs/MASTERPLAN.md §30.3 (“Ore grading and assays”) and §36.4 (“Mined-block to vessel charging”), and inspect:

Review the three assay instrument tiers:

Method Instrument Output Type Error Bracket Example In-Game Readout
HandSpecimen Bare hands / eye Qualitative (Lean, Middling, Rich) Full band “By eye and weight, this looks like middling ore.”
Density Archimedes balance Quantitative percentage $\pm 15%$ “The balance reads about 48% grade, give or take 15 points.”
FireAssay Cupel furnace Quantitative percentage $\pm 3%$ “The fire assay reads about 52% grade, give or take 3 points.”

An ore block must declare its mineral constituents using BlockCompositionComponent. A copper carbonate block (Malachite) contains malachite as PrimaryOre and quartz/calcite as Gangue:

// Authoring a Malachite block composition:
block.Composition = new List<BlockCompositionComponent>
{
// Primary ore scales directly with grade g: moles = baseMoles * g * count
new BlockCompositionComponent(
substance: malachiteSolid,
molesPerBlock: 120.0,
role: BlockComponentRole.PrimaryOre),
// Gangue scales inversely with grade: moles = baseMoles * (1 - g) * count
new BlockCompositionComponent(
substance: quartzSolid,
molesPerBlock: 350.0,
role: BlockComponentRole.Gangue)
};

When grade is $1.0$ (pure vein core), gangue yields $0\text{ mol}$; when grade is $0.1$ (lean fringe), gangue dominates the charge, requiring the player to smelt mostly flux and slag.

2. All-or-Nothing Grade-Aware Vessel Charging

Section titled “2. All-or-Nothing Grade-Aware Vessel Charging”

Inspect BlockCompositionCharging.TryChargeIntoVessel:

public static bool TryChargeIntoVessel(
BlockDefinition block,
int blockCount,
double grade01,
VesselCharge charge,
out string error)
{
// 1. Preflight capacity and distinct new key additions
var newKeys = new HashSet<(int SubstanceId, ChemicalPhase Phase)>();
for (int index = 0; index < block.Composition.Count; index++)
{
BlockCompositionComponent component = block.Composition[index];
double deltaMoles = component.YieldMolesAt(grade01, blockCount);
if (deltaMoles <= 0d) continue;
if (!charge.CanAdjustMoles(component.Substance.SubstanceId, component.Substance.Phase, deltaMoles, out error))
{
return false;
}
if (!charge.Contains(component.Substance.SubstanceId, component.Substance.Phase))
{
newKeys.Add((component.Substance.SubstanceId.Value, component.Substance.Phase));
}
}
if (charge.Count + newKeys.Count > charge.Capacity)
{
error = "Vessel charge is full.";
return false;
}
// 2. Commit all molar additions atomically
for (int index = 0; index < block.Composition.Count; index++)
{
BlockCompositionComponent component = block.Composition[index];
double deltaMoles = component.YieldMolesAt(grade01, blockCount);
if (deltaMoles > 0d)
{
charge.AdjustMoles(component.Substance.SubstanceId, component.Substance.Phase, deltaMoles);
}
}
error = null;
return true;
}

If a vessel cannot hold both the ore mineral and the accompanying gangue waste rock, charging fails completely before any substance is added.

In OreAssayMath.AssaySample, evaluate the instrument reading:

public static AssayReading AssaySample(float trueGrade01, AssayMethod method, int sampleId)
{
trueGrade01 = Mathf.Clamp01(trueGrade01);
if (method == AssayMethod.HandSpecimen)
{
// Qualitative band call
AssayGradeBand band = trueGrade01 switch
{
< 0.30f => AssayGradeBand.Lean,
> 0.65f => AssayGradeBand.Rich,
_ => AssayGradeBand.Middling
};
return new AssayReading(trueGrade01, 0.5f, method, band);
}
// Quantitative measurements add bounded pseudo-random scatter
float uncertainty = method == AssayMethod.FireAssay ? 0.03f : 0.15f;
float scatter = DeterministicValueNoise.HashToUnitFloat((uint)sampleId) * 2f - 1f;
float measured = Mathf.Clamp01(trueGrade01 + scatter * uncertainty * 0.75f);
return new AssayReading(measured, uncertainty, method, AssayGradeBand.Middling);
}

The measurement is honest: the reported bracket [measured - uncertainty, measured + uncertainty] is mathematically guaranteed to enclose the true geological grade.

OreAssayNarrator.Describe turns the reading into plain scientific English at the player’s reading level:

public static string Describe(in AssayReading reading)
{
if (!reading.IsQuantitative)
{
return reading.Band switch
{
AssayGradeBand.Lean => "By eye and weight, this is lean ore — most of it is waste rock.",
AssayGradeBand.Rich => "By eye and weight, this looks like rich ore.",
_ => "By eye and weight, this looks like middling ore.",
};
}
int centre = Percent(reading.MeasuredGrade01);
int give = Percent(reading.UncertaintyHalfWidth01);
string instrument = reading.Method == AssayMethod.FireAssay ? "fire assay" : "balance";
return $"The {instrument} reads about {centre}% grade, give or take {give} points.";
}

Open AssayLabModule.cs. The module lets designers select a block, adjust a grade slider from $0.0$ to $1.0$, choose block batch counts, and instantly inspect:

  1. Moles of primary ore and gangue generated.
  2. Simulated vessel charge capacity usage.
  3. Comparative readouts across all three instrument methods.

Run the focused Chemistry and Data EditMode fixtures headlessly:

Terminal window
/opt/unity/Editor/Unity -batchmode -nographics \
-projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \
-runTests -testPlatform EditMode \
-testFilter VoxelSandbox.Tests.OreAssayMathTests

Verify that:

  • TrueGradeAlwaysFallsWithinReportedErrorBracket passes across all test grades.
  • HandSpecimenResolvesCorrectQualitativeBands verifies lean ($< 30%$) and rich ($> 65%$).
  • FireAssayProducesTighterUncertaintyThanDensityBalance holds strictly.
  • BlockCompositionChargingTests proves full-batch atomic rollback when vessel capacity is exceeded.
  1. Open Tools → Voxel Sandbox → Voxel Workshop → Assay Lab.
  2. Select Malachite Ore from the dropdown.
  3. Slide Grade to 0.20: confirm the Hand Specimen narrative reads “lean ore — most of it is waste rock”, and gangue moles dominate the charge.
  4. Slide Grade to 0.85: confirm the Hand Specimen switches to “rich ore”, and primary copper carbonate moles increase dramatically.
  5. Inspect the Fire Assay readout: verify it reports $\pm 3$ points uncertainty.

To author a brand-new mineral ore block (e.g. Galena / Lead Sulfide or Bauxite):

  1. Author SubstanceDefinitions: Ensure both the primary mineral (LeadSulfideSolid, $PbS$) and gangue (SilicaSolid, $SiO_2$) exist in SubstanceCatalog.asset.
  2. Assign composition in BlockDefinition: Add BlockCompositionComponents specifying base moles and setting roles to PrimaryOre and Gangue.
  3. Audit in Assay Lab: Open Assay Lab, select the new block, and verify that sliding grade between $0$ and $1$ smoothly shifts the molar proportion.
  4. Add EditMode tests: Add test cases in BlockCompositionChargingTests.cs validating batch charging and capacity limits.
Symptom Cause Fix
Vessel receives pure metal when smelting raw ore. The ore block was authored without a Gangue component. Real ores contain waste rock. Always author matching gangue (SilicaSolid or CalciteSolid) with BlockComponentRole.Gangue.
Moles added to vessel do not change when mining different parts of an ore body. The mining runtime called the grade-unaware overload TryChargeIntoVessel(block, count, charge) ($g=1.0$). Always pass the sampled coordinate grade: TryChargeIntoVessel(block, count, grade01, charge, out error).
Partial moles remain in a vessel when a charge fails partway through. Components were adjusted sequentially without preflighting overall charge capacity. Use BlockCompositionCharging.TryChargeIntoVessel; it verifies all new substance keys fit within charge.Capacity before adjusting values.
Hand specimen displays a false-precise percentage (e.g. “43.2% grade”). AssayReading.IsQuantitative was bypassed in UI presentation. Qualitative methods must only report qualitative bands (Lean, Middling, Rich). Never print decimal percentages from unmeasured estimates.
Moles scale non-linearly with block count. YieldMolesAt was computed with an exponent instead of linear molar scaling. Moles must scale linearly with count: $\Delta n = n_{\text{base}} \times g \times \text{count}$.

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.

  1. Loading notes…