Skip to content
Edit on GitHub

Author atmospheric enclosure scanning and ventilation sinks

verifiedAgainst 3629ae9 · verifiedOn 2026-09-11.

In Voxamine, hazardous gases (such as carbon monoxide emitted by a charcoal furnace or sulfur dioxide roasted from chalcopyrite) do not merely vanish into thin air (MASTERPLAN.md §33.2, §33.4). If a player operates an unvented metallurgical apparatus inside a sealed room, toxic matter accumulates in that enclosed control volume, creating acute hazard exposure.

To establish an atmospheric control volume, the system must scan player-built voxel structures without freezing the game or performing unbounded flood fills into infinite skies.

You will implement the enclosure scanning and ventilation pipeline using AtmosphericEnclosureScanner.cs:

  • Run a bounded six-connected air flood-fill within an AtmosphericDomainBounds envelope and strict air-cell budget.
  • Identify airtight enclosures while cleanly rejecting breaches that reach exterior skies.
  • Discover player-built ventilation blocks (Stone Vent) and verify that their far side connects to outside air.
  • Convert closed scan counts into physical control volume geometry ($V = N \cdot V_{\text{cell}}$) and air-change rate $\lambda = Q/V$.
  • Transfer evacuated gases to the persistent regional outdoor ledger via well-mixed NIST decay in AtmosphericTransportMath.cs.
// Scan a 3x3x3 room around a furnace anchor:
AtmosphericEnclosureScanResult result = AtmosphericEnclosureScanner.Scan(
world, seedPosition: new WorldBlockPosition(2, 2, 2),
scanEnvelope: envelope, maximumAirCells: 64,
ventilationSource: new BlockCatalogAtmosphericVentilationSource(catalog));
if (result.Status == AtmosphericEnclosureScanStatus.Enclosed)
{
// Evaluates: 27 air cells * 1.0 m³ = 27 m³ room volume
// Discovers: 1 Stone Vent with outdoor contact -> Q = 0.025 m³/s
// Air changes per second: λ = Q / V = 0.025 / 27 ≈ 0.000926 s⁻¹
result.TryCreateGeometry(
cubicMetersPerAirCell: 1.0d, temperatureKelvin: 293.15d,
outdoorAirChangesPerSecond: result.OutdoorAirflowCubicMetersPerSecond / (result.AirCellCount * 1.0d),
out AtmosphericDomainGeometry geometry, out string error);
}

Enclosure scanning bridges player-built voxel blocks to the chemical atmospheric transport ledger:

  • Voxel World (ChunkStreamingRuntime & DictionaryBlockSource) provides authoritative voxel block states.
  • Enclosure Scanner (AtmosphericEnclosureScanner) executes bounded flood-fill topology checks and exterior vent discovery.
  • Ventilation Resolver (IAtmosphericVentilationSource & BlockDefinition) maps built blocks to cited volumetric airflow rates ($Q = 0.025\ \text{m}^3/\text{s}$).
  • Domain Registry (AtmosphericDomainRegistry) binds the scanned spatial geometry to a conserved gas mixture domain.
  • Atmospheric Transport (AtmosphericTransportMath) applies NIST well-mixed multizone exponential decay, moving vented moles to the regional outdoor ledger.
flowchart TD
  subgraph VOXELS["Voxel Terrain & Player Construction"]
    BLOCKS["Authoritative Voxel Blocks\n(Stone walls, Air cells, Stone Vents)"]
    EVENT["BlockChanged Event\n(Player breaks or places block)"]
  end

  subgraph SCANNER["AtmosphericEnclosureScanner"]
    SEED["Seed Coordinate\n(Vessel anchor or Room centre)"]
    FLOOD["Bounded 6-Connected Flood Fill\n(Envelope bounds & Air-cell budget)"]
    STATUS{"Status Check"}
    ENCLOSED["Enclosed\n(Calculates N cells & SpatialBounds)"]
    REJECT["Rejected\n(SeedIsNotAir, ReachedScanBoundary, BudgetExceeded)"]
    VENT_CHECK["Vent Exterior Air Check\n(Far side touches outside air)"]
  end

  subgraph DOMAIN["Atmospheric Domain & Physics"]
    GEOM["AtmosphericDomainGeometry\n(Volume V = N · Vcell, λ = Q / V)"]
    REGISTRY["AtmosphericDomainRegistry\n(Active room gas ledger)"]
    TRANSPORT["AtmosphericTransportMath\n(Well-mixed decay C = C0 · exp(-λt))"]
    REGIONAL["Persistent Regional Ledger\n(Outdoor atmosphere sink)"]
  end

  BLOCKS --> SEED
  EVENT --> SEED
  SEED --> FLOOD
  FLOOD --> STATUS
  STATUS -->|Boundary reached / Budget exceeded| REJECT
  STATUS -->|Fully enclosed| ENCLOSED
  ENCLOSED --> VENT_CHECK
  VENT_CHECK --> GEOM
  GEOM --> REGISTRY
  REGISTRY --> TRANSPORT
  TRANSPORT --> REGIONAL

Read Docs/MASTERPLAN.md §33.2 (“Atmospheric domains”), §33.4 (“Enclosure scanning”), and Docs/CHEMISTRY_SOURCES.md §C3.6, then inspect:

Keep the core scanner rules in mind:

Scan Status Trigger Condition Resulting Action
Enclosed All six-connected paths terminate at solid blocks or verified vents within budget. Room geometry created; spatial domain configured in registry.
SeedIsNotAir Starting coordinate is a solid block. Instant refusal with $0$ air cells; no flood fill executed.
ReachedScanBoundary Air path touches the outer edge of scanEnvelope (open window, missing roof). Rejected; room is open to outdoors, no indoor domain created.
AirCellBudgetExceeded Room volume exceeds maximumAirCells. Scanner halts immediately to prevent CPU starvation on massive caves.

In AtmosphericEnclosureScanner.cs, define the explicit scan termination states:

public enum AtmosphericEnclosureScanStatus
{
Enclosed,
SeedIsNotAir,
ReachedScanBoundary,
AirCellBudgetExceeded,
}

The scanner uses a breadth-first queue with a visited coordinate set. It rejects infinite loops by asserting two hard bounds:

  1. scanEnvelope: A bounding box in block coordinates. Touching the outer shell immediately signals ReachedScanBoundary.
  2. maximumAirCells: A maximum allowed count (e.g. 64 or 512 cells). Exceeding it yields AirCellBudgetExceeded.

During the flood-fill step, whenever a boundary voxel is solid, the scanner queries IAtmosphericVentilationSource:

if (ventilationSource != null && ventilationSource.TryGetOutdoorAirflow(boundaryBlock, out double airflow))
{
// A vent only functions if its FAR SIDE touches exterior air!
WorldBlockPosition exteriorCandidate = GetOppositeFaceCell(currentAirCell, boundaryPosition);
if (TouchesExteriorAir(world, exteriorCandidate, scanEnvelope))
{
totalOutdoorAirflow += airflow;
}
}

This prevents a player from placing a decorative Stone Vent between two sealed indoor rooms and erroneously claiming outdoor ventilation. A vent must reach the outdoors.

3. Converting Scanned Topology to Physical Geometry

Section titled “3. Converting Scanned Topology to Physical Geometry”

In AtmosphericEnclosureScanResult.TryCreateGeometry, translate voxel counts into continuous SI units:

public bool TryCreateGeometry(
double cubicMetersPerAirCell,
double temperatureKelvin,
double outdoorAirChangesPerSecond,
out AtmosphericDomainGeometry geometry,
out string error)
{
if (Status != AtmosphericEnclosureScanStatus.Enclosed || !SpatialBounds.HasValue)
{
geometry = default;
error = "Only a closed enclosure scan can create atmospheric control-volume geometry.";
return false;
}
geometry = new AtmosphericDomainGeometry(
volumeCubicMeters: AirCellCount * cubicMetersPerAirCell,
temperatureKelvin: temperatureKelvin,
outdoorAirChangesPerSecond: outdoorAirChangesPerSecond);
error = null;
return true;
}

Block scale ($1.0\ \text{m}^3$ per cell) and ambient reference temperature are supplied via AtmosphericEnvironmentProfile.cs, based on the NASA standard tropospheric temperature model.

In AtmosphericTransportMath.AdvanceVentilation, simulate well-mixed air change:

$$\lambda = \frac{Q_{\text{outdoor}}}{V_{\text{room}}},\quad C(t) = C_0 e^{-\lambda \Delta t}$$

Evacuated moles are never deleted. Every mole of gas leaving the enclosure domain is transferred directly into the persistent regional outdoor ledger.

5. In-World Construction Diagnostic Readouts

Section titled “5. In-World Construction Diagnostic Readouts”

AtmosphericVentilationInspection.GetConstructionReadout provides contextual HUD feedback when looking at a built Stone Vent:

  • Outdoor air path required: If the vent’s far side is blocked by solid stone.
  • Room air exchange active: If the vent successfully connects an enclosed room domain to exterior air.

Run the dedicated enclosure scanner and ventilation test suites:

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

Verify that all key assertions pass:

  • ClosedRoom_CountsAirAndBuildsBoundsAndExplicitGeometry: verifies that a $3 \times 3 \times 3$ air cavity enclosed by solid stone returns Status == Enclosed and exactly $27$ air cells.
  • OpenRoom_ReachingTheFiniteScanBoundaryIsRejectedRatherThanClaimedAsEnclosed: verifies that missing wall blocks immediately reject the room with ReachedScanBoundary.
  • AirCellBudget_StopsLargeScansBeforeTheyCanBecomeAnUnboundedHotPath: confirms the scanner halts cleanly at the budget limit without allocating excess memory.
  • SolidSeed_IsRejectedWithoutScanningAnyAir: verifies that seeding inside a wall immediately aborts with $0$ cells.
  • Readout_RequiresAnExteriorConfiguredRoomRatherThanGasOrAContentTag: validates the contextual inspection text.

To create a new high-throughput ventilation block (e.g. an Iron Louvred Exhaust Grate or Powered Duct Fan):

  1. Define Block: Add IronExhaustGrate to BlockDefinition.
  2. Set Flow Rate: Call SetOutdoorVentilation(0.125d, "industrial fan") in the block definition ($5\times$ higher airflow than a stone vent).
  3. Configure Scanning: Pass the block catalog to BlockCatalogAtmosphericVentilationSource.
  4. Test Dilution: Build an enclosure with the new grate and verify that toxic furnace exhaust clears $5\times$ faster!
Symptom Cause Fix
Game freezes or hitches when opening a door to the outside. Scanner performed an unbounded flood fill into the open world. Always specify a finite AtmosphericDomainBounds envelope and maximumAirCells budget.
Placing a vent between two indoor rooms ventilates the room. Vent was counted without verifying exterior outdoor air contact. Ensure the scanner checks TouchesExteriorAir on the far side of the vent block.
Vented carbon monoxide disappears from world persistence. Subtracted gas from the room without adding it to the regional ledger. Always transfer evacuated moles to the regional outdoor ledger via AtmosphericTransportMath.
Room is reported as open even though all walls are solid. scanEnvelope was authored too small, intersecting the inner air space. Ensure scanEnvelope encompasses the room’s entire bounding box including wall thickness.
Air change rate $\lambda$ is zero despite active vents. Passed $0$ or negative volume to TryCreateGeometry. Calculate $\lambda = Q / (N_{\text{cells}} \cdot V_{\text{cell}})$ with verified positive cell volume.

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…