LabCheck

A checker for liquid-handling protocols.

A liquid handler does what the protocol says. If the protocol asks a 20–300 µL pipette for 15 µL, or takes 60 µL from a tube with 40 in it, the robot just goes ahead. Usually you notice when the plate looks wrong.

LabCheck runs the protocol in software before the robot does. It keeps count of the liquid in every tube and well and lists the steps that can't work as written. You don't need a robot or any lab data to use it.

Figure 1
buffersampledyedashed ring: required control

Reagent left

Log

    Eight steps on a 96-well plate. The pipette takes 20–300 µL and a well holds 200 µL. Press Play, or use the buttons to put one random mistake into the protocol and see what gets reported. The rules here are written in JavaScript. The real version will be Python.

    What it checks

    1. PIPETTE_RANGE: the volume is within the pipette's range.
    2. REAGENT_EMPTY: the source still has enough left when the step runs.
    3. WELL_OVERFLOW: no well ends up over its capacity.
    4. WELL_MISSING: the well exists on that plate (there is no I1 on a 96-well plate).
    5. CONTROL_MISSING: the negative and positive control wells get filled.
    6. DEAD_VOLUME: optional. A source isn't emptied below the volume the pipette can't reach. Off in the figure above.

    Each failure is listed with its step number and a one-line reason.

    Where this comes from

    I'm building this over four weeks. It starts from the Laboratory Automation Protocol format in this ACS Synthetic Biology paper. First I reproduce the paper's format, then I add the rules, then I break protocols on purpose and count how many the checker catches. The plan is on the report page, sample protocols and reference numbers are on data, and the benchmark so far is on results.

    To make your own protocol, click wells on the Build page, or paste JSON into Try it. The glossary explains the lab words.

    Try it

    Paste a protocol as JSON and run the checks. Nothing leaves your browser.

    reagents: µL available · pipette: min, max · well_capacity · dead_volume · plate: 96 or 384 · controls · steps: from, to, ul

    Results appear here.

    Method

    Protocol in, verdict and list of failures out. See the report page for the full methodology including protocol schema, simulation algorithm, rule trigger conditions, and evaluation design.

    1. Parse. Read reagents, volumes, labware and steps from YAML or JSON, using the LAP format.
    2. Simulate. Go through each transfer and update the volume in every source and well.
    3. Check. Test every rule after each step.
    4. Report. List the step, the rule and why it failed.

    Protocol schema (JSON)

    {
      "plate": 96,
      "reagents": {"buffer": 1000, "dye": 40, "sample": 120},
      "pipette": {"min": 20, "max": 300},
      "well_capacity": 200,
      "dead_volume": 0,
      "controls": {"A1": "negative", "A12": "positive"},
      "steps": [
        {"from": "buffer", "to": "A1", "ul": 100},
        {"from": "dye", "to": "B1", "ul": 20}
      ]
    }

    Simulation algorithm

    stock = reagents.copy()
    wells = {}
    for step in steps:
      stock[step.from] -= step.ul
      wells[step.to] = (wells[step.to] || 0) + step.ul
      check_rules(step, stock, wells)
    check_controls(wells, controls)

    Rules

    CodeFails whenMessage
    PIPETTE_RANGEul < min ∨ ul > max{ul} µL outside {min}–{max} µL range
    REAGENT_EMPTYstock[from] < 0{from} short by {−stock} µL
    WELL_OVERFLOWwells[to] > capacity{to} would hold {wells[to]} µL, max {cap} µL
    WELL_MISSINGto ∉ plate grid{to} does not exist on this plate
    CONTROL_MISSINGControl well never filled{type} control {well} never dispensed into
    DEAD_VOLUME0 ≤ stock[r] < dead_volume (and stock[r] ≥ 0, i.e. not already empty){r} left with {stock} µL, below {dead} µL dead volume

    An example step

    reagents:
      buffer: {labware: reservoir, well: A1, ul: 1000}
      dye:    {labware: tubes,     well: A1, ul: 40}
    controls: {negative: A1, positive: A12}
    steps:
      - id: 4
        transfer: {from: dye, to: B1, ul: 15}
    # step 4 fails: 15 µL is below the 20 µL minimum

    Evaluation design

    Error injection: start from valid generated protocols, inject one known mistake per protocol, measure whether the corresponding rule fires. Two injection types (WRONG_REAGENT, WRONG_WELL) have no dedicated rule—any detection is incidental.

    Benchmark generation: 8–24 steps, 3 reagents (buffer 4000µL, sample 800µL, dye 400µL), random well/reagent/volume respecting all constraints.

    Metrics: detection rate per error type (Wilson 95% CI), multi-error detection (all injected codes reported), false alarm rate on clean protocols, timing.

    Real-protocol panel: four transcribed OT-2 protocols (serial-dilution subset and full run, restriction digest, PCR distribution) with the libraries' own volumes, labware and pipettes, checked as transcribed — any report counts as a false alarm on real data. Retrospective: two shipped library faults — the 649b67 example input overdraw (caught: REAGENT_EMPTY) and the 052d03 diluent-planning fix (miss: reservoir-well planning has no rule yet). Sources and transcription notes are on the data page.

    Statistical methods

    Rates are binomial proportions (caught / n). Intervals are Wilson score intervals at 95% (z = 1.96):

    (p + z²/2n ± z·√(p(1−p)/n + z²/4n²)) / (1 + z²/n)

    I use Wilson rather than the textbook Wald interval (p ± z·√(p(1−p)/n)) because Wald collapses to zero width at 0% and 100% — exactly where most of these rates sit — and under-covers near the edges. Wilson holds ~95% coverage across the range. No p-values are reported; with n = 300 the intervals speak for themselves.

    Reproducibility: base protocols come from one pseudorandom stream; each injected fault type draws from its own independent stream derived from the seed. Same seed and count replay the same tables bit-for-bit. The Atlas page re-runs five seeds to show the numbers do not depend on a lucky draw.

    Complexity

    Checking is linear: each step does constant work (update two counters, test six rules), so time is O(S) in the number of steps and memory is O(W) in filled wells plus O(R) reagent stocks. The full cost table is on the atlas page. Timing uses the median of repeated runs to resist garbage-collection pauses.

    Threats to validity

    What it will not catch

    It knows nothing about viscosity, evaporation, collisions on the deck, timing, or whether the experiment makes sense biologically. A pass only means the protocol agrees with itself. The assay could still fail.

    Planned stack: Python, pytest, YAML/JSON protocol files, and the Opentrons API where it fits.

    Results

    What the checker catches in generated protocols.

    Two evaluations. The verdicts that matter are the two real-data panels below: valid OT-2 protocols that must pass, and shipped library faults that must (or demonstrably cannot yet) be caught. The injection benchmark further down is synthetic by design — regression tests for code correctness, not field performance: random-generator protocols with script-injected errors.

    One error at a time

    Each protocol gets one mistake. "Caught" means the checker reported that rule's code. For the two rows with no rule, it means the checker reported anything.

    Injected errorTargetnCaughtRate95% interval

    Interval: Wilson score (95%). Same seed and protocol count reproduce the same table: base protocols come from one stream and each error type gets its own independent stream. Swapped reagent and moved dispense have no rule, so any catch there is by accident: the change happened to cause an overflow, an empty source or a missing control.

    Several errors at once

    Two or three different mistakes in one protocol. It only counts if every injected code is reported.

    Injected togethernAll foundRate95% interval

    What gets reported

    For each kind of injection, how many runs reported each code. A run can report more than one. Bold marks the code the injection was aimed at.

    InjectedPIPETTE_RANGEREAGENT_EMPTYWELL_OVERFLOWWELL_MISSINGCONTROL_MISSINGnothing

    The base protocols

    Real protocols

    The four transcribed real OT-2 protocols from the data page, checked as transcribed with their own pipette ranges and well limits. They are valid runs, so they should all pass — any report here is a false alarm on real data.

    ProtocolStepsResult

    Real faults from library history

    Shipped faults found by lab users (sources on the data page), not injected by me. H1 is a fault inside the checker's scope; H2 is outside it — reservoir-well planning has no rule yet — and is counted as a miss so the gap stays visible.

    CaseExpectedLabCheckVerdict

    Still to do

    Partly done: four transcribed valid OT-2 protocols, one shipped fault caught, one scoped miss. Still to do: a second in-scope historical fault, error types chosen by someone other than me, and running the checker prospectively on new protocols before they run.

    Data

    The protocols and reference numbers the checker works with.

    Sample protocols

    Nine small protocols I wrote by hand. The checker runs on each when you open this page, so the result column isn't typed in. "Open in Build" loads one into the builder.

    #ProtocolStepsµL movedWellsResult

    Real protocols

    Four subsets transcribed from published, experimentally validated OT-2 protocols in the Opentrons Protocol Library. Volumes, labware and pipettes are the libraries' own defaults — not my invention. The checker runs on each when you open this page, so the result column isn't typed in. "Open in Try it" loads the full JSON (with its real pipette range and well limit) into the validator.

    #ProtocolStepsµL movedWellsResult

    Transcription notes. LabCheck v1 models only reagent → well dispenses on one plate with one pipette range, so each entry is the reagent → well phase of its source: (1) serial dilution keeps the robot's 100 µL diluent distribution (R1: first 6 wells, R4: all 88 wells of columns 2–12); the 50 µL well-to-well serial transfers are out of scope and noted as a limitation. (2) restriction digest collapses per-sample DNA tubes and the three enzymes each into one stock but keeps every real per-well volume and the real transfer order (water → buffer → sample → enzyme); the real run splits volumes across a P20 (≤20 µL) and a P300 (>20 µL), modelled here as their combined 1–300 µL envelope. (3) PCR distribution is exact at defaults. (4) the 649b67 fault case models the 6-well plate as a 96 grid (B3 is a valid name in both) with the real 16.8 mL well limit. None of the sources declares control wells, so controls is {} and the control rule is not evaluated for them.

    Real faults from library history

    Shipped protocol versions and example inputs from the Protocol Library's own history — faults found by lab users, not injected by me. Expected verdict and live result side by side; H2 is a miss by scope and says so.

    #CaseStepsµL movedExpectedLabCheckVerdict

    Pipettes

    Single-channel ranges for the Opentrons OT-2 GEN2 pipettes, from Opentrons' pipette specifications (also in each protocol's fields.json). The checker's default 20–300 µL range comes from the P300.

    PipetteMin µLMax µL
    P20120
    P30020300
    P10001001000

    Plates

    Well maxima from labware definitions and manufacturer datasheets — not guesses. The demo default of 200 µL is the NEST 96-well 200 µL flat (Opentrons Labware Library). The 96-well 360 µL figure is the Corning 3997 flat-bottom plate (360 µL total, 75–200 µL working volume). The 384-well 112 µL figure is the Corning 384-well flat (Opentrons Labware Library). For any other plate, use its datasheet.

    FormatRowsColumnsWellsMax µL per well
    96-well (NEST 200 µL flat)A–H (8)1–1296200
    96-well (Corning 360 µL flat)A–H (8)1–1296360
    384-well (Corning 112 µL flat)A–P (16)1–24384112

    Report

    LabCheck: a static checker for liquid-handling protocols. Version 1.0 — October 2026.

    Abstract

    Liquid handlers run protocols exactly as written, so a mistake in the protocol becomes a mistake in the experiment. LabCheck simulates a structured protocol, checks it against six rules before it runs, and explains each failure. I measure how well it works by injecting known errors into valid protocols. The browser implementation validates protocols in under 2 ms for typical workflows (≤25 steps) and catches >95% of injected errors for the five rule-covered failure modes.

    Background

    The Laboratory Automation Protocol (LAP) format, described in ACS Synthetic Biology (2023), gives automated experiments a shared structure: reagents with volumes, labware definitions, pipette specifications, control wells, and an ordered list of transfer steps. LabCheck adds a validation step on top of LAP. It is meant for lab automation and assay development, where the run only works if the protocol was right to begin with.

    Methodology

    Protocol representation

    A protocol is a JSON or YAML document with these fields:

    Simulation engine

    The simulator maintains running state for every source and well:

    1. Initialize stock[r] = starting volume for each reagent
    2. Initialize wells[w] = 0 for each well on the plate
    3. For each step (from, to, ul) in order:
      1. Record pre-step state (stock, wells)
      2. stock[from] -= ul
      3. wells[to] += ul
      4. Run all six checks against the new state
      5. Record any violations with step index, rule code, and human-readable message
    4. After all steps, check that every control well in controls received ≥1 µL

    Validation rules

    CodeTrigger conditionMessage template
    PIPETTE_RANGEul < min ∨ ul > max{ul} µL is outside the {min}–{max} µL pipette range
    REAGENT_EMPTYstock[from] < 0{from} is short by {−stock} µL
    WELL_OVERFLOWwells[to] > well_capacity{to} would hold {wells[to]} µL, max {cap} µL
    WELL_MISSINGto ∉ plate grid{to} does not exist on this plate
    CONTROL_MISSINGControl well never received liquid{type} control {well} is never dispensed into
    DEAD_VOLUME0 ≤ stock[from] < dead_volume (opt-in; empty stock reports REAGENT_EMPTY instead){from} is left with {stock} µL, below its {dead} µL dead volume

    Error injection for evaluation

    To measure detection rates without real-world error data, I start from valid generated protocols and inject one known mistake per protocol:

    The last two have no dedicated rule; any detection there is incidental (the swap happens to cause overflow, empty source, or missing control).

    Benchmark protocol generation

    Valid protocols are generated by randomly sampling transfers that respect all constraints:

    Evaluation

    Error injection gives a detection rate for each error type, and clean protocols give the false alarm count. Full results with 95% Wilson intervals are on the results page. Visualizations of detection rates, timing, and limit sensitivity are on the analysis page. The real-data evaluation transcribes published OT-2 protocols and shipped library faults: four valid protocols with zero false alarms, one in-scope historical fault caught, one out-of-scope miss kept visible; sources and transcription notes are on the data page.

    Limitations

    The checker only finds what its rules describe. It doesn't model liquid properties (viscosity, evaporation, foaming), deck layout (tip rack positions, labware coordinates, collisions), timing (incubation, settling), or biology (whether the assay design makes sense). A pass only means the protocol is internally consistent with respect to the six rules—nothing more.

    Plan

    WeekGoalStatus
    1Reproduce the paper's protocol representation and one example workflowcomplete
    2Implement the validation rules and unit testscomplete
    3Add simulated execution, error injection and audit logscomplete
    4Benchmark detection, write up limitations, publish the repositorycomplete

    Log

    DateEntry
    14 Oct 2026Project page published. Browser reference checker, simulator, builder and validator working. Python implementation not started.
    15 Oct 2026Added sample protocols, reference tables, a benchmark on generated protocols (seven kinds of error), and an analysis page with charts and timing.
    16 Oct 2026Expanded report with full methodology, protocol schema, simulation algorithm, all six rules with trigger conditions and message templates, error injection procedures, benchmark generation details, and limitations.
    16 Oct 2026Real-data evaluation: 4 transcribed valid OT-2 protocols (0 false alarms), retrospective on library history — 649b67 shipped example-input overdraw caught, 052d03 diluent-planning fix missed (no reservoir rule; kept as visible miss). Synthetic injection repositioned as regression tests.

    Build

    Click wells to make a protocol. Pick a reagent, set a volume, then click a well to put it there. The checker runs after every click.

    reagent

    Reagent left

    Steps

    Glossary

    Lab words used on this site.

    Assay
    A lab test that measures something, such as how much of a molecule is in a sample.
    Dead volume
    Liquid left in a tube or reservoir that the pipette cannot pick up.
    Error injection
    Adding one known mistake to a correct protocol to see whether a checker notices it.
    False alarm
    The checker flags a protocol that was actually fine.
    LAP (Laboratory Automation Protocol)
    A structured way to describe automated experiments, from the paper this project starts with.
    Liquid handler
    A robot that moves measured volumes of liquid between containers.
    Negative control
    A well that should show no signal, so you can see the background level.
    Pipette range
    The smallest and largest volumes a pipette can measure reliably, for example 20–300 µL.
    Plate
    A tray of small wells. A 96-well plate has rows A–H and columns 1–12. A 384-well plate has rows A–P and columns 1–24.
    Positive control
    A well that should show a signal, so you know the assay can work.
    Protocol
    The ordered list of steps an experiment follows: what to move, how much, and where.
    Reservoir
    A trough holding a bulk reagent that the pipette draws from.
    Well
    One small cup in a plate, named by row and column, such as B3.
    Boundary test
    A protocol sitting exactly on a limit (20 µL, 200 µL, stock exactly spent). Must pass; one unit beyond must fail.
    Calibration
    Checking that a measurement tool reads correctly at known values. Here: the checker at known protocol limits.
    Confidence interval
    A range that plausibly contains the true rate. All intervals here are Wilson 95% intervals.
    Coverage
    How often a confidence interval contains the truth over repeats. Wilson intervals hold ~95% coverage; the simpler Wald interval does not at extreme rates.
    Incidental detection
    A fault with no dedicated rule that still gets flagged because it causes a different rule to fire.
    Jaccard index
    Overlap of two sets divided by their union. Used for protocol similarity on step tuples.
    Median of runs
    The middle timing of repeated runs, robust to one slow run from garbage collection or tab throttling.
    Must-pass case
    A protocol variant exactly on the allowed side of a limit. Any report on it is a false alarm by construction.
    Seed
    The starting number of a pseudorandom generator. Same seed replays the same protocols and faults.
    Specificity
    Share of clean or must-pass cases correctly left unflagged. The complement of the false alarm rate.
    µL
    Microlitre, a millionth of a litre.

    Analysis

    Charts drawn in your browser by the same checker.

    One protocol

    Pick a protocol to see what it does to the reagents and the plate. The example is the one on the home page and the others are on the Data page — including three transcribed real OT-2 protocols with their own pipettes, plates and well limits.

    Where the liquid goes. Each band is one reagent going into one well, and its width is the volume.
    The plate after each step. A red ring means that step has a problem.
    Reagent left after each step, as a share of what it started with. Below the dashed line means the tube ran empty.
    Total liquid moved so far, stacked by reagent.
    Liquid in each filled well at the end. Wells over the limit are red.
    Size of each transfer. The grey band is the pipette's range and dots outside it are red.
    Problems reported at each step. The last bar counts problems that only show at the end, like a control well that was never filled.

    Generated protocols

    This and the next two sections use a set of random valid protocols, the same kind the benchmark uses. Change the seed or the count and press Recompute.

    Average µL each well receives per protocol. A1 and A12 are the control wells, so they are always filled.
    Average µL per protocol going into each row.
    The same by column. Columns 1 and 12 are higher because the controls in A1 and A12 are always filled.
    How many transfers there are of each size, across all the protocols.
    Number of steps per protocol.
    Each dot is one protocol: how many steps it has and how much liquid it moves.
    How much of each stock a protocol uses. The box is the middle half, the thick line is the median, and the whiskers are the 10th and 90th percentiles.

    Detection

    These come from injecting errors into the generated protocols, as on the Results page.

    Detection rate for each kind of error, with a 95% interval. The two red rows have no rule, so any catch there is by accident.
    Share of protocols where every injected error was found, as the number of errors per protocol goes up. Dashed lines are the 95% interval.
    Total problems reported when three errors are injected at once. It is often more than three, because one mistake can cause others.
    How far into the protocol the first problem is reported, for errors that happen at a step. 0% is the first step.
    Which codes are reported together when three errors are injected. The diagonal is how often that code showed up at all.

    What if the limits were different?

    The generated protocols were made for a 200 µL well and a 20 µL pipette minimum. Here the limits change and the checker runs again on the same protocols. The dashed line or red box marks the values they were built for.

    Protocols flagged for overflow as the well limit goes down.
    Protocols flagged for pipette range as the pipette minimum goes up.
    Both limits at once. Each cell is the share of protocols flagged for overflow or pipette range.
    Share of protocols that would run out of a reagent if its tube held this much.

    Validation pipeline

    Visual summary of the checker's four stages. The parser reads the protocol, the simulator tracks volumes, the rule engine tests six conditions, and the reporter lists every violation.

    LabCheck validation pipeline: Parse → Simulate → Check → Report.

    Rule trigger conditions

    Each rule fires when its specific condition is met during or after simulation. This table shows the exact logic.

    Rule trigger conditions: PIPETTE_RANGE (step volume vs range), REAGENT_EMPTY (stock < 0), WELL_OVERFLOW (well > capacity), WELL_MISSING (well ∉ plate), CONTROL_MISSING (control never filled), DEAD_VOLUME (stock < dead, optional).

    Protocol schema

    The JSON structure a protocol must follow. Required fields are plate, reagents, and steps. Others have defaults.

    LabCheck protocol schema: plate (96/384), reagents {name: µL}, pipette {min,max}, well_capacity, dead_volume, controls {well: type}, steps [{from,to,ul}].

    Reagent usage waterfall

    How much of each stock is consumed across the protocol. The waterfall shows cumulative usage per reagent.

    Waterfall: cumulative µL drawn from each reagent stock across all steps.

    Well capacity utilization

    Heatmap showing how full each well gets as a percentage of its capacity. Red = over capacity.

    Well utilization heatmap: % of that protocol's well capacity used per well. Red = overflow.

    Step volume timeline

    Each step's volume colored by reagent, with pipette range band. Red dots exceed range.

    Step volume timeline: volume per step, color = reagent, grey band = pipette range.

    Error co-occurrence

    When multiple errors are injected, which rule codes tend to appear together. Diagonal = marginal frequency.

    Error co-occurrence matrix: how often pairs of rule codes are reported together (3 errors injected).

    Protocol similarity

    Pairwise similarity of generated protocols based on their step sequences (reagent + well + volume bucket).

    Protocol similarity matrix: Jaccard index on (reagent, well, volume bin) tuples.

    How fast is it?

    Time to check one protocol of a given length. The test protocol has no errors, so this measures the bookkeeping and not the error reporting.

    Atlas

    Every chart and table in one place.

    Same generator and checker as the Results page. Change the seed or count and press Recompute; every figure below rebuilds from the new set.

    Volumes

    What transfers look like in valid protocols: per-reagent sizes, controls, plate position, reuse, and how close wells get to the limit.

    Buffer transfer sizes (µL).
    Sample transfer sizes (µL).
    Dye transfer sizes (µL).
    Volume dispensed into control wells vs regular wells.
    How many times the average well is dosed: once, twice, three or more.
    Fullest well per protocol, as % of the 200 µL limit.

    Stability

    Do the headline numbers survive a change of seed or sample size? Five independent seeds, three sample sizes, and clean-vs-injected report counts.

    Detection rate per rule across 5 seeds (n=100 each). Thin lines are 95% intervals.
    Detection rate vs number of protocols, per rule.
    False alarms on clean protocols vs set size.
    With k errors injected: share where at least one vs every error is found.
    Problems reported per run: clean protocols vs 3 errors injected.
    Check time vs protocol length (median of 5 runs per size).

    Boundaries

    Limits are where checkers earn trust. Each boundary is tested from both sides: just inside must pass, just outside must fail.

    Flag rate as pipette minimum moves 1–60 µL (built for 20).
    Flag rate as well limit moves 60–300 µL (built for 200).
    Flag rate as dead volume moves 0–100 µL with a 50 µL tube remainder.
    Single 50 µL step tested at volumes 15–25 µL: only 19 and below fail.
    Single well filled to 190–210 µL in 5 µL steps: only above 200 fails.
    Tube of 120 µL drawn down 100–130 µL: only above 120 fails.

    Patterns

    Where errors surface and which codes travel together, beyond the Analysis page.

    Row-normalised confusion: given the injected error (row), what share of runs reported each code.
    Median step of first report, per error type (as % into the protocol).
    Reports per run with 2 errors injected.
    Pairwise protocol similarity (Jaccard) distribution, 50 protocols.
    Buffer vs sample moved per protocol (each dot one protocol).
    Steps vs wells used per protocol.

    Error taxonomy

    Fifteen fault variants, each injected into 100 valid protocols. Rows marked must-pass sit exactly on the allowed side of a boundary: any report there is a false alarm by construction.

    #VariantWhat changesExpectnFlaggedRate
    Flag rate per taxonomy variant. Amber rows are must-pass boundaries and should sit at zero.
    Boundary variants only: fail-side vs pass-side rates.
    Incidental detection: duplicated step and swapped wells have no dedicated rule.
    Which codes fire for faults with no single expected code (huge volume, duplicate, swap).

    Reagents

    Stock usage and depletion risk per reagent.

    Share of buffer stock used per protocol.
    Share of sample stock used per protocol.
    Share of dye stock used per protocol.
    Flag rate if the buffer tube started with 500–4000 µL.
    Flag rate if the sample tube started with 100–800 µL.
    Flag rate if the dye tube started with 50–400 µL.

    Wells

    Where liquid lands on the plate.

    Corner vs edge vs centre wells: average µL per protocol.
    Wells used per protocol.
    Wells receiving more than one dose per protocol.
    Wells within 10% of the limit per protocol.
    Unused wells per protocol (of 96).
    Average µL by dose count: wells dosed 1×, 2×, 3× or more.

    Methods

    How the checker is built and what each figure assumes. Seed stability across 5 seeds:

    SeednMean stepsMean µLFalse alarms

    Asymptotic cost (S steps, W filled wells, R rules):

    StageTimeSpaceNotes

    Defaults and where they come from:

    ParameterDefaultSource
    Rule evaluation order inside one step: first failure reported, all failures recorded.
    Simulator state after each step: two counters per source and well.
    Time per step stays flat as protocols grow (µs per step).
    Reports grow with injected errors; clean runs stay near zero.
    Detection by volume distance outside the pipette range.
    Overflow reports by µL over the limit.
    Empty-stock reports by µL short.
    Summary: all rule-covered rates with intervals at the current seed.