Runtime Snapshot
Source: BuildReports/desktop-validation-report.json.
Direct Gameplay Flow
Player aims and presses E
FirstPersonChemistController raycasts from the camera and finds the focused LabInteractable.
Interactable chooses action
A bottle calls SelectChemical; a vessel calls AddSelectedToVessel; the sink calls WashVessels.
Game updates state
DesktopLabGame stores selected chemicals and vessel contents in dictionaries keyed by LabStation.
Chemistry is evaluated
ReactionSimulator checks curated, redox, then dynamic rules. ReactionConditionEngine gates the match using temperature, concentration, pH, and catalyst.
Safety is applied
LabSafetySystem computes exposure, health loss, credits lost, and evacuation state for hazardous gases.
Player receives feedback
DesktopLabHud, DesktopLabAudio, materials, and particles show the result.
OOP in the Game
Encapsulation
ChemicalDefinition hides its fields behind private setters. Once a chemical is created, other systems read its formula, phase, hazards, color, and physical properties without mutating the catalogue.
Inheritance
LabInteractable is an abstract base class. Specific objects inherit from it and implement Prompt and Interact().
Polymorphism
The player controller only knows it has a LabInteractable. It does not need to know whether the target is a bottle, vessel, sink, analysis bench, or periodic element tile.
Composition
DesktopLabGame composes HUD, audio, diagnostics, world objects, safety state, materials, and player controller. This matches Unity's component style.
Plain C# Domain Objects
Chemistry data classes such as ReactionDefinition, VesselAddition, and ReactionOutcome are not Unity components. They can be validated in editor/build code without scene dependencies.
Sealed Classes
Runtime data models are marked sealed when inheritance is not intended. This keeps behavior easier to reason about and avoids accidental subclass contracts.
OOP Example: Interactions
LabInteractable
|-- ChemicalBottleInteractable -> Game.SelectChemical(chemicalId)
|-- VesselInteractable -> Game.AddSelectedToVessel(station)
|-- SinkInteractable -> Game.WashVessels()
|-- AnalysisInteractable -> Game.ToggleInspector(true)
`-- ElementTileInteractable -> Game.InspectElement(atomicNumber)
The important point is that FirstPersonChemistController runs one generic interaction algorithm: raycast, focus, show prompt, call Interact(). The object itself decides what the action means.
Data Structures
| Structure | Where It Appears | Why It Fits |
|---|---|---|
List<ChemicalDefinition> |
DesktopChemistryDatabase |
Ordered catalogue traversal for shelves, validation, and UI listings. |
Dictionary<string, ChemicalDefinition> |
ChemicalById |
Fast lookup when gameplay only has a chemical id such as copper-sulfate. |
Dictionary<LabStation, List<VesselAddition>> |
DesktopLabGame.vesselAdditions |
Separates workbench and fume hood vessel contents while keeping each vessel's addition history. |
Dictionary<LabStation, VesselVisual> |
DesktopLabGame.vesselVisuals |
Maps simulation state to the correct 3D liquid renderer and particle system. |
Dictionary<string, Material> |
DesktopLabGame.materials |
Caches generated Unity materials so repeated colors and chemical surfaces do not create duplicate runtime assets. |
Dictionary<string, Species> |
DynamicReactionEngine.SpeciesById |
Connects catalogue chemicals to ion/species metadata for rule-based chemistry. |
Dictionary<string, ChemistryIonDefinition> |
CompoundGenerationMatrix |
Provides constant-time lookup of the 46 reusable ions loaded from JSON. |
Dictionary<string, MatrixCompoundOverrideRecord> |
CompoundGenerationMatrix |
Maps one X/Y/Z coordinate to reviewed formula, phase, solubility, colour, hazard, and evidence metadata. |
HashSet<string> |
Validation code | Detects duplicate chemical ids, duplicate reaction ids, and duplicate element symbols. |
IReadOnlyList<T> |
Public catalogues and simulator input | Lets systems read catalogue data without taking ownership or changing it. |
enum |
ChemicalPhase, ReactionEffect, HazardSeverity |
Represents small fixed state sets clearly and cheaply. |
Algorithms
Reaction Matching
ReactionSimulator.Evaluate aggregates grams by chemical id, checks every curated reaction for both reactants, and uses the curated match when available. This preserves hand-authored observations and safety notes.
Dynamic Reaction Resolution
DynamicReactionEngine.TryResolve sorts valid ids, checks every two-chemical pair, dispatches by species kind, and builds a generated ReactionDefinition when a rule family applies.
Stoichiometry
The simulator converts grams to moles, divides by reaction coefficients, chooses the smaller extent as the limiting reagent, then estimates product mass from molar mass and yield fraction.
limiting reagent mass balanceReaction Conditions and Kinetics
ReactionConditionEngine converts mass to molarity, computes net acid/base-equivalent pH, applies temperature and concentration trends, verifies catalysts, and returns a rate class, completion time, and yield multiplier. Failed hard constraints return Blocked without consuming the vessel.
Redox Electron Balance
RedoxReactionEngine stores electron loss and gain for two half-reactions. Euclid's GCD algorithm produces their least common multiple, which validates the shared electron count and supports acid/concentration product branches.
Persistent Product Mass
SynthesizedInventory serializes batches to JSON. Loading reconstructs runtime chemical definitions; reusing a batch subtracts the requested mass and removes an empty batch. Matrix-backed products register as dynamic species.
Formula Construction
CompoundGenerationMatrix combines cations and anions by charge using a greatest-common-divisor reduction. Polyatomic ions are parenthesized when count is greater than one, then reviewed overrides and exclusions are applied.
Balancing Search
Exchange, gas-release, displacement, and metal-acid reactions use bounded coefficient searches. The search space is intentionally small because high-school reactions use small integer coefficients.
bounded brute force integer coefficientsSolubility and Activity Rules
Precipitation depends on solubility rules for nitrates, alkali/ammonium salts, halides, sulfates, carbonates, phosphates, and sulfides. Metal displacement compares activity values.
solubility table activity seriesSafety Consequences
LabSafetySystem.Apply calculates exposure from fume hood capture, gas trap capture, respirator efficiency, gas severity, and released gas mass. It updates health, credits, incident count, and emergency evacuation state.
First-Person Focus
Each frame, the controller raycasts forward from the camera. If the hit object has a parent LabInteractable, the HUD prompt updates and the target receives focus highlighting.
Enriched X/Y/Z Compound Matrix
X: metal / cation + activity rank
Y: nonmetal / anion family
Z: oxygen count + explicit oxidation state
|
v
validate oxidation state
|
v
balance charge with GCD
|
v
generate formula + molar mass
|
v
estimate phase / solubility / colour / hazards
|
+-- reviewed override -> confidence Reviewed
+-- explicit exclusion -> reject candidate
`-- general rule -> confidence RuleDerived
The matrix is data-driven through Resources/Chemistry/compound-generation-matrix.json. A literal three-dimensional array would lose oxidation-state and polyatomic-ion information, so the game stores rich coordinate objects while preserving the original X/Y/Z concept.
Chemistry Engine Decision Tree
Vessel additions
|
v
Aggregate grams by chemical id
|
v
Curated reaction exists?
|-- yes -> use reviewed ReactionDefinition
|
`-- no
|
v
RedoxReactionEngine
|-- balance electron LCM
|-- select acid / concentration branch
`-- otherwise continue
|
v
DynamicReactionEngine
|-- acid + base
|-- acid + carbonate / bicarbonate
|-- acid + sulfide
|-- ammonium salt + base
|-- salt + salt precipitation
|-- metal + salt displacement
|-- metal + non-oxidising acid
`-- basic oxide + acid
|
v
CompoundGenerationMatrix
|-- charge-balanced formula
|-- phase / solubility / colour
|-- hazard flags
|-- Reviewed or RuleDerived confidence
`-- explicit rejection for unstable candidates
|
v
matched ReactionDefinition or NoMatch
|
v
ReactionConditionEngine
|-- temperature / volume / molarity / pH
|-- catalyst / rate / estimated time
|-- failed constraint -> Blocked
`-- valid -> stoichiometry + yield
|
v
SynthesizedInventory (optional collection)
|-- reusable batch + purity + hazards
|-- JSON persistence
`-- subtract mass when reused
Safety Flow
Gas product
The reaction effect is Gas, so the product formula is looked up in AirborneHazardCatalog.
Control check
Fume hood captures 90 percent. Fume hood plus gas trap captures 99.5 percent. Respirator then reduces inhalation exposure where applicable.
Consequence
Unsafe exposure reduces health and credits. At zero health, the player is evacuated and returns with 35 health.
Feedback
The HUD shows warning text, audio plays a hazard alarm, and the incident remains visible in the safety panel.
These numbers are gameplay values for learning risk-control relationships. They are not medical dose limits.
Folder Structure
chemistryLAB/
|-- Assets/
| `-- ChemistryLab/
| |-- Editor/
| |-- ExternalAssets/
| |-- Resources/
| |-- Runtime/
| `-- Scenes/
|-- BuildReports/
|-- docs/
| |-- README.md
| |-- architecture/
| |-- chemistry/
| |-- design/
| |-- gameplay/
| `-- release/
|-- Packages/
|-- ProjectSettings/
|-- SourceAssets/
`-- README.md
Key Source Files
| File | Responsibility |
|---|---|
Assets/ChemistryLab/Runtime/Bootstrap/DesktopLabGame.cs |
Game composition, procedural lab construction, vessel state, mission state, runtime smoke test. |
Assets/ChemistryLab/Runtime/Chemistry/ChemistryData.cs |
Chemical definitions, curated reactions, reaction outcomes, simulator entry point. |
Assets/ChemistryLab/Runtime/Chemistry/DynamicReactionEngine.cs |
Species model, reaction-family dispatch, stoichiometry balancing, and activity series. |
Assets/ChemistryLab/Runtime/Chemistry/ReactionConditions.cs |
Per-vessel temperature/volume, molarity and pH estimation, catalyst gates, rate class, time, and yield trends. |
Assets/ChemistryLab/Runtime/Chemistry/RedoxReactionEngine.cs |
Reviewed oxidation-reduction branches and electron GCD/LCM validation. |
Assets/ChemistryLab/Runtime/Chemistry/SynthesizedInventory.cs |
Runtime chemical registry, reusable product batches, exact mass consumption, and persistent JSON storage. |
Assets/ChemistryLab/Runtime/Chemistry/CompoundGenerationMatrix.cs |
JSON-backed element/ion graph, X/Y/Z compound generation, charge balancing, property estimation, confidence, overrides, and exclusions. |
Assets/ChemistryLab/Runtime/Safety/LabSafetySystem.cs |
Chemical hazard classification, airborne hazard profiles, respirator/gas-trap state, exposure consequences. |
Assets/ChemistryLab/Runtime/Player/LabInteractions.cs |
Abstract interactables, concrete interaction objects, first-person movement, raycast focus, hotkeys. |
Assets/ChemistryLab/Runtime/UI/DesktopLabHud.cs |
HUD, inspector, safety panel, pause menu, transient messages, UI button feedback. |
Assets/ChemistryLab/Runtime/Audio/DesktopLabAudio.cs |
Procedural audio generation and playback for UI, movement, reactions, and hazards. |
Editor/BuildPipeline/DesktopLabBuild.cs |
Editor validation, dynamic matrix validation, Windows build report generation. |