The calculation algorithms, with worked examples. Identical to the white paper bundled with the application.
1. Scope of this paper
This document explains what Stack by F³ Lab computes, in the order the application computes it, with a worked number at every step. It is written for a design or quality engineer who knows GD&T and needs to defend a result to a customer — not for a statistician, and not as a substitute for the user guide, which explains how to drive the application.
Three method families run side by side on the same chain, always all three:
| Method | The question it answers | What it costs you |
|---|---|---|
| Worst case | Can any legal combination of parts fail? | Assumes extremes coincide — usually a much wider band than reality. |
| RSS | How wide is the band if the contributors vary independently? | Only legitimate when they really are independent. |
| Monte Carlo | What fraction of assemblies is inside the limits? | Only as good as the per-contributor distributions you assume. |
Every number printed in this paper is produced by the application itself and
is re-checked by the automated test suite on every build
(TSA.Tests/WhitePaperTests.cs). Where the paper states a physical
truth the application does not currently model, it says so, in
section 10, in full.
examples/tutorial.stack), Monte Carlo seed 12345, 1 000 000
samples.2. The chain
A stack-up is a closed loop of vectors. You start at one surface, travel through the assembly along a chosen axis — one dimension per hop — and come back to where you started. The dimension you care about is the vector that closes the loop.
Each hop is a contributor: one row of the stack-up grid, carrying one drawing callout. A contributor gives the chain two things and nothing else:
- a signed length — where the next hop starts;
- a half-width — how far that length may wander.
The vector you draw on the canvas is the row. Its direction sets the sign; its assigned feature supplies the numbers.
The sign convention
The convention is absolute, not relative: a vector pointing right (or up, on a vertical stack) counts as +; a vector pointing left (or down) counts as −. It does not matter which way the requirement was drawn, and it does not matter in which order the rows sit.
Closing the loop
The nominal result is simply the signed sum of the contributions:
A chain that does not close geometrically still computes — the application adds up whatever rows are there — but the number then answers a question you did not ask. The canvas shows closure directly, and the grid's Σ row is always the same number the results strip prints.
3. One contributor's numbers
Before any method runs, every row is converted to one canonical form: an equal-bilateral pair value ± half. Everything downstream — worst case, RSS, Monte Carlo, every temperature slot — reads only that pair.
Mean-centring, and the chain's two centres
What it means physically: a dimension written 10 +0.2/−0 does not vary about 10. It varies about 10.1, because 10.1 is the middle of the band the part is allowed to occupy. Adding the drawing's nominal would put the centre of the chain somewhere no part can be.
So 10 +0.2/−0 enters the chain as 10.100, not 10.000, and that is what the grid's CONTRIB. column shows.
- NOMINAL — what the drawing states. For the row above, 10.000000.
- MEAN — the middle of the population: the nominal plus every row's mean shift. Here 10.100000.
Worst case is stated about the NOMINAL, because that is how a drawing reads its own limits, so this row prints 0.200000 above and 0.000000 below 10 — the drawing's own deviations, not 10.100 ±0.100. RSS, Monte Carlo and the closed-form σ are stated about the MEAN, because that is what the parts are centred on and what the sampler draws around. The limits are the same either way: 10.000 … 10.200.
On a chain of symmetric rows — most chains — the two are the same number and the application shows one merged cell. Where they differ, the results strip splits that cell along the method rows, the grid's Σ cell names the mean in its tooltip, and the INFO column spells out the conversion on every affected row. The difference is never silent, and neither centre is ever hidden behind the other.
The half-width and ÷2
What it means physically: however lopsided the drawing's deviations, the width of the band does not change when you re-centre it. So the half width is half the total band:
A minus-sense row subtracts its mean but still adds its half-width — flipping a symmetric band leaves the band just as wide.
The ÷2 convention. A drawing states a geometric zone whole: a
position callout of ⌀0.3 means the axis may lie anywhere inside a cylinder
0.3 across. A stack-up hop, however, runs from the axis to one side. So a
geometric row contributes ±zone/2, and the application switches the
/2 divisor on by itself when a row becomes geometric.
4. Worst case
What it means physically: every part in the assembly is simultaneously at its most unhelpful legal extreme. Not likely — but legal, and therefore something a customer can hand you.
The half-widths are added linearly. Signs never enter the tolerance sum: a minus-sense row can push the result either way just as a plus-sense row can.
One exception, and it is physical: the material bonus
That ± hides an assumption — that every row can reach both ends of the band. A Ⓜ/Ⓛ bonus earned from a feature that is itself in the chain cannot. The bonus is the departure of that feature from its material condition, so at the material condition it is exactly zero. Adding the whole bonus to both ends says the hole is at maximum material and away from it in the same breath. No such part exists.
The application therefore computes the two ends separately and asks every bonus how much of it exists at that extremity — none at its own material end, all of it at the far end:
Which end a bonus falls on is derived, never declared. Maximum material is the largest size for an external feature and the smallest for a hole, so the modifier and the hole/shaft answer decide it — and a minus-sense row enters the chain negated, which turns its material end round with it. Two bonuses in one chain need not land on the same end.
Worked example — one hole pass
A ⌀10.5 ±0.2 hole located by position ⌀0.3 Ⓜ, against a ⌀10 bolt, stacked the classic way with ÷2 throughout: the size contributes 5.25 ±0.100, the zone ±0.150, and the bonus — the whole size range 0.4, halved — ±0.200.
The MAX end is unchanged and always was right: at the largest hole the bonus really is fully earned, and the chain reaches 5.700. The MIN end moves up by exactly the bonus, 4.800 → 5.000, because reaching it needs the hole at maximum material — and there the bonus does not exist. The band is asymmetric because physically it never was symmetric.
Worked example — tutorial sheet R-003
The chain is a floating-fastener assembly: two bolts of ⌀10 pass through two ⌀10.5 ±0.2 holes located by position ⌀0.3 Ⓜ, with a 60 basic hole spacing on each plate. The chain crosses four hole passes. Each pass contributes the hole radius (÷2 on the ±0.2 band → ±0.100), the position zone (÷2 on ⌀0.3 → ±0.150) and the Ⓜ bonus that hole earns as it departs from maximum material (the whole 0.4 size range, ÷2 → ±0.200).
| Term | Count | half each | Σ half |
|---|---|---|---|
| hole size, ±0.2 halved | 4 | 0.100 | 0.400 |
| position zone ⌀0.3 halved | 4 | 0.150 | 0.600 |
| Ⓜ bonus — the hole's 0.4 size range, halved | 4 | 0.200 | 0.800 |
| bolts, basic spacing (no tolerance) | 4 | 0.000 | 0.000 |
| reach → MIN — every hole at maximum material, so no bonus | 1.000 | ||
| reach → MAX — every hole at least material, so the whole bonus | 1.800 | ||
| half the span | 1.400 |
Nominal 1.000, so the worst-case band is 0.000 … 2.800 — asymmetric, because the bonus exists only at the end where the holes are away from maximum material. The requirement is "clearance ≥ 0", and worst case just reaches it.
5. RSS
What it means physically: the contributors are separate features made by separate operations, so they do not conspire. When one is at its high limit the next is somewhere in the middle. Variances — not widths — add.
In this application RSS is defined on the stated half-widths, exactly as textbooks define it. It does not read the per-row distribution setting; that is Monte Carlo's job.
Worked example — the same R-003 chain
Same inputs, same chain: half the worst-case span is 1.400 and the RSS half is 0.538516 — a factor of 2.6.
Worth a glance forward: three times the simulated σ of this very chain (§10) is 0.542627, so on this drawing RSS lands within a percent of the honest 99.73 % band. That is a coincidence of these particular tolerances, not a law — RSS is defined on the DECLARED half-widths and knows nothing about material bonuses — but it is the reason RSS is left exactly as textbooks define it: it stays comparable with what another tool would print for the same rows.
6. Monte Carlo
What it means physically: build a million virtual assemblies. For each one, draw a realised value for every contributor from an assumed distribution, add the chain up, and record the result. Then count.
Distributions and the ±3σ convention
Each row carries its own distribution:
| Setting | Sampled as | Variance from half-width h |
|---|---|---|
| Normal (default) | Box–Muller, σ = h/3, untruncated | h²/9 |
| Uniform | flat on [−h, +h] | h²/3 |
| Triangular | sum of two uniforms, symmetric on [−h, +h] | h²/6 |
Because the Normal draw is untruncated, about 0.27 % of samples fall outside the stated band of any given row. That is intentional: a real process does produce the occasional out-of-tolerance part, and truncating would quietly promise an inspection gate the drawing does not require. It does mean the simulated minimum and maximum can sit outside the worst-case band, which is not a contradiction — they are answers to different questions.
Yield, Cpk and the acceptance rule
Yield is counted, not modelled: the fraction of simulated assemblies that landed inside the stated limits. A limit that is not stated does not constrain anything, so a one-sided requirement ("clearance ≥ 0") is judged on its one side only.
Cpk asks how many 3σ steps fit between the mean and the nearest stated limit:
The acceptance rule — one rule, evaluated once:
This matters beyond pedantry. The Monte Carlo panel badge, the requirement's GO/NO GO, the Results tab and the printed report all call the same function on the same simulated run. Earlier versions let each surface ask its own question — the strip asked whether a ±Z·σ band fitted, the panel asked yield and Cpk, the report asked whether literally zero samples fell outside — and on one real field project those three read PASS, FAIL and FAIL. They cannot disagree now, by construction.
7. Temperature
What it means physically: a part at 250 °C is not a part at 20 °C with a correction bolted on. It is a different part — longer, and its tolerance band is longer too, because the band is also a length of the same material.
At a slot, every contributor is multiplied by its own factor, taken from the material of the part that owns the feature:
The strain is the integral of α(T)·dT over the range, so a material defined by a nonlinear α(T) curve is integrated properly rather than evaluated at a single α. Steel and aluminium in the same chain each move by their own amount; that is the whole point.
Worked example — R-003 across the sweep
The project is set to −50 °C / 20 °C / 250 °C. The plates are aluminium, the bolts steel, both with α(T) curves.
| Slot | nominal | worst-case min | worst-case max | half |
|---|---|---|---|---|
| T MIN (−50 °C) | 0.983768 | -0.014683 | 2.780979 | 1.397831 |
| T ROOM (20 °C) | 1.000000 | 0.000000 | 2.800000 | 1.400000 |
| T MAX (250 °C) | 1.059791 | 0.054140 | 2.869963 | 1.407911 |
Read the last column: the tolerance band itself is 0.15 % narrower cold and 0.57 % wider hot. That is small here, and it is not small on a long chain in a plastic or an alloy with a large α — which is exactly why the application refuses to scale a finished result.
Note also the verdict: the requirement is clearance ≥ 0, and the cold slot's worst case is −0.014683. The sheet passes at room and fails cold. A single-temperature analysis would have missed it.
8. GD&T in the chain
Geometric callouts enter a stack-up as three different kinds of thing, and it is worth keeping them apart.
| Callout | What it gives the chain | Contributes to the nominal? |
|---|---|---|
| Position, profile, flatness, … (the zone) | a half-width, ±zone/2 by default | no |
| Datum reference (RMB) | where the chain starts — nothing numeric | no |
| Ⓜ / Ⓛ on the tolerance (MMC/LMC) | a bonus half-width | no |
| Ⓜ / Ⓛ on a datum (MMB/LMB) | a datum shift half-width | no |
| Ⓤ unequally disposed zone | the zone as a centred band, plus an explicit shift vector | the shift vector does |
What a bonus IS
Physically: a Ⓜ modifier says the stated zone applies when the feature is at maximum material. Make the hole bigger than that, and the extra material you removed is room the axis can use — the zone grows by exactly the amount the feature departed from the material condition.
Which end of the size band is the "material condition" depends on whether the feature is internal or external:
| Ⓜ MMC is at… | Ⓛ LMC is at… | |
|---|---|---|
| External (a shaft) | the largest size | the smallest size |
| Internal (a hole) | the smallest size | the largest size |
A bonus is derived, not drawn
Here is the trap, and how the application avoids it.
A bonus is a function of a number the simulation is already drawing. If the feature of size is a member of the same chain, then sampling the bonus as if it were an independent random variable counts the same physical variation twice: once as the size row, once as the bonus row. The band comes out too wide and the yield too pessimistic.
The application stores the dependency as the bonus's zone formula — =abs(U@ID−L@ID) over the feature of size — and the simulator reads it. Per sample it computes an availability a ∈ [0, 1]: the fraction of the maximum bonus that this virtual part actually earned.
Worked example — one shaft, three rows
A ⌀20 ±0.05 shaft, a ⌀0.1 position zone, and the MMC bonus the shaft earns (±0.05 after the ÷2). Normal distributions throughout.
| Quantity | value |
|---|---|
| Σ of the declared halves | 0.150000 |
| worst case, reach → MAX (the shaft's Ⓜ bonus is spent there) | 0.100000 |
| worst case, reach → MIN (the bonus is whole there) | 0.150000 |
| RSS half (√Σ h²) | 0.086603 |
| σ if all three rows were independent | 0.028868 |
| surviving variance fraction of the bonus, E[a²] | 0.277778 |
| σ with the bonus still drawn separately, deflated by E[a²] | 0.025154 |
| σ with the bonus widening the zone it belongs to | 0.030174 |
Read the last two rows together — they are the whole story. Deflating the bonus removes the double count, and that part was never real spread. But a bonus does not sit beside the position zone as a second error: it makes the zone the same error lives in wider. Drawing them as two rows and adding the variances leaves out the cross term 2·hzone·hbonus·E[a]·E[u²], and putting it back raises σ by 20.0 % — to 0.030174, which is what the application simulates and what its closed-form σ reports. A bonus whose source is not in the chain is fully earned in every sample, the same read worst case takes.
9. The boundary method
Everything above stacks a located feature as several contributors: its size, the zone that locates it, and any bonuses. Many engineers never do it that way. They collapse the lot into the two envelopes the feature can never cross and stack a single number.
Both schools are supported. They are alternatives, never layers.
The derivation
The contract is one line:
Let the feature of size have mid-of-band mid and half-range h (so minSize = mid − h, maxSize = mid + h, and the full range is 2h), let t be the geometric tolerance zone and d the datum-shift allowance.
Without a material modifier (RFS) there is no bonus. The zone applies in full at every realised size, so both envelopes sit one zone outside the size limits and the nominal does not move:
With Ⓜ or Ⓛ the modifier pins the envelope generated on its own material side — that is ASME Y14.5's virtual condition, and the picture in §8.1 is exactly why: every unit of bonus earned by departing from the material condition is spent on displacement, so that envelope never moves. The other side opens by the whole size range 2h — the resultant condition:
The full table — ⌀10 ±0.1 located by position ⌀0.2
| Feature | Modifier | inner | outer | nominal | half |
|---|---|---|---|---|---|
| shaft (external) | Ⓜ MMC | 9.5 | 10.3 | 9.9 | 0.4 |
| shaft (external) | RFS | 9.7 | 10.3 | 10.0 | 0.3 |
| shaft (external) | Ⓛ LMC | 9.7 | 10.5 | 10.1 | 0.4 |
| hole (internal) | Ⓜ MMC | 9.7 | 10.5 | 10.1 | 0.4 |
| hole (internal) | RFS | 9.7 | 10.3 | 10.0 | 0.3 |
| hole (internal) | Ⓛ LMC | 9.5 | 10.3 | 9.9 | 0.4 |
The modifier rules
What goes into a boundary is decided by the modifiers on the drawing, and they form a sequence, not four free inputs:
- Is there a position/location tolerance at all? Without one there is no zone t and no modifier to ask about.
- Does that tolerance carry Ⓜ/Ⓛ? If so the bonus is taken automatically from the feature of size — it is the size range. It is never a separate pick and never a typed number, because a bonus that does not equal the size range is not a bonus.
- Does the datum reference carry Ⓜ/Ⓛ (MMB/LMB)? Only then is there a datum shift, and only then does the datum's own feature of size — and its own tolerance to higher-order datums — enter the arithmetic. At RMB, d ≡ 0.
10. The honest section — what the statistical path samples
The boundary method is correct as an envelope, and it is the right input for worst case. Handed to a statistical method as if it were a distribution, it misleads — so the application no longer does that. This section says exactly what was wrong, exactly how much, what the engine does instead, and where it still cannot know.
None of what follows is a rumour. It was measured against the application's own engine and cross-checked against a direct integration of the physics.
One boundary row, examined
Take the tutorial's own case: a bolt of ⌀10 passing through a hole ⌀10.5 ±0.2 located by position ⌀0.3 Ⓜ. One pass through that hole contributes to the chain the hole's radius plus wherever the axis actually landed. Written as a physical model:
Two facts follow, and they pull in opposite directions.
Fact one — the envelope is right
Work out the extremes of c for any in-tolerance part and you get exactly 5.00 … 5.70 — which is the derived boundary 10.0 / 11.4, halved. The boundary method computes the right envelope. Worst case reads that envelope and is right to.
Fact two — the distribution inside it is not right
| mean | σ | shape | |
|---|---|---|---|
| The truth (the model above) | 5.2500 | 0.09044 | right-skewed, skew +0.249 |
| The envelope read as a distribution (10.7 ± 0.7, halved, Normal) | 5.3500 | 0.11667 | symmetric |
The variance of the truth splits cleanly:
So reading the envelope as a distribution makes σ 1.29× too wide and the mean 0.10 too high. Two independent errors produce that, and it helps to name them separately:
- The midpoint of an asymmetric envelope pair is not the centre of mass. 5.35 is half of 10.7 — the least-material size. Putting the distribution's centre there asserts that the typical hole is the largest hole the drawing allows. It is not; the typical hole is 10.5.
- The half-width 0.35 is a linear worst-case sum. It is (0.4 size range + 0.3 zone) / 2 — the two added end to end, exactly as worst case adds them. Feeding a linear worst-case sum into a Gaussian as ±3σ is precisely the double count that RSS exists to avoid.
The joint space — why the corner is nearly empty
This is the picture that explains everything else. Two axes: how far the hole departed from its nominal size, and where its axis actually landed. Every real part is one dot in this field.
The permitted region is not a rectangle. The position zone grows with the hole, so the region is a wedge: narrow at maximum material, wide at least material.
Numbers for the same statement, computed exactly rather than sampled — the probability that one pass exceeds a given value:
| c exceeds | truth | the model | overstated by |
|---|---|---|---|
| 5.50 | 0.586 % | 9.93 % | 17× |
| 5.60 | 0.037 % | 1.606 % | 43× |
| 5.70 | 0.0015 % | 0.135 % | 91× |
Why worst case does not notice
This is the crux, and it is short. Worst case only ever uses the midpoint as a term in a sum and a difference:
The artificial midpoint cancels exactly. It never has to mean anything. Monte Carlo, by contrast, uses that midpoint as the centre of a distribution — and there, nothing cancels.
Two distributions, one axis
R-003 against R-004 — the same drawing, twice
The bundled tutorial contains the same floating-fastener assembly built both ways: R-003 the classic school (size + zone as separate rows) and R-004 the boundary school. Same drawing, same topology, seed 12345, 1 000 000 samples, T ROOM. The third column is a direct integration of the physical model above — what the assembly actually does.
| R-003 — classic | R-004 — boundary | physical reference | |
|---|---|---|---|
| mean | 0.999966 | 0.999955 | 1.000000 |
| σ | 0.181059 | 0.181085 | 0.180876 |
| Cpk | 1.841 | 1.841 | 1.843 |
| worst case, MIN end | 0.000 | 0.000 | 0.000 |
| worst case, MAX end | 2.800 | 2.800 | — |
The two schools now agree, and both agree with the physics. Worst case matches at both ends — the MAX ends differ by 0.000 mm, and in fact by 4 × 10⁻¹⁵ at every temperature slot, which is floating-point noise and nothing else. The simulated means differ by 1 × 10⁻⁵ and the simulated σ by 1.4 × 10⁻⁴; both sit 0.10 % above the integrated reference, which is inside the sampling error of a million draws (σ/√2n ≈ 0.07 %). The application's closed-form σ is the same double on both sheets — 0.18087578440110785 — because both sheets now describe the same physical model.
Worst case agreeing at the MIN end was never in doubt: at maximum material the bonus is zero, and maximum material is the extremity that sets the minimum clearance. Both schools describe the same virtual condition — the whole lower edge of the wedge in the picture above. Agreeing at the MAX end is the result of §4: up to 1.1.0 the classic sheet read −0.800 … 2.800, because the four bonuses were subtracted at the very extremity that earns none of them.
What each school had been getting wrong
Both errors are exact, and neither is a rounding artefact. Per hole pass, with the halves the chain actually carries (size ±0.10, zone ±0.15, Ⓜ bonus ±0.20):
| Model | variance / pass | σ of the chain |
|---|---|---|
| classic rows drawn independently, bonus deflated by E[a²] = 0.277778 | 0.004846 | 0.139222 |
| + the missing cross term 2·0.15·0.20·E[a]·E[u²] = 0.003333 | 0.008179 | 0.180876 |
| the boundary envelope read as ±3σ, (0.35/3)² | 0.013611 | 0.233333 |
The classic side was missing the cross term — 40.8 % of the honest variance of every located hole. A Ⓜ bonus is not a second position error beside the zone; it widens the zone the same error lives in, so the two share one draw and their widths add before the squaring, not after.
The boundary side was reading a linear worst-case sum as a standard deviation. Worst case adds 0.20 + 0.15 = 0.35 end to end; a simulator handed that number as ±3σ produces a distribution 1.290× too wide, and centred 0.10 per pass too high because the envelope's midpoint is the least-material size. Taking the row apart removes both at once — which is why R-004's simulated mean moved from 1.400 to 1.000 while its worst-case band did not move at all.
And the number printed between the two ends followed. R-004's sheet went on reading 1.400 beside a simulated mean of 1.000 for as long as the envelope midpoint was treated as a nominal. It is neither of the chain's centres (§3.1): the same decomposition that gives the statistics their mean gives the sheet its nominal, so R-003 and R-004 now state 1.000000 at every temperature slot — with 0.000000 … 2.800000 unchanged to the last bit.
What this means for you today
- Stack a located feature in whichever school you think in. Both give the same worst-case band and the same distribution now, so the choice is about how you read the drawing, not about which answer you get.
- Compose a boundary, do not type it in, if you want statistics from it. A composed boundary knows what it is made of and the simulation takes it apart; two numbers typed by hand carry no such information, and the application says so in the analysis warnings instead of pretending.
- Leave a boundary row's DISTRIBUTION on Normal. It now describes the size and the zone inside the boundary. The older advice in this paper — set a boundary row to Uniform — applied to sampling the envelope itself, and that is no longer what happens, except on a hand-typed boundary where it still holds.
- A repeated feature is still a repeated INSTANCE. A feature is a callout on the drawing and each chain row is one instance of it — the four passes in R-003 are four different holes in two plates, not one hole counted four times. Drawing them independently is correct; the application also binds each Ⓜ bonus to the hole it stands next to, rather than to the first hole on the sheet.
- Two things the model still does not claim. The realised position is drawn as a symmetric deviation inside the zone, not as a radial two-dimensional offset; and the ±tol = ±3σ convention is untruncated, so a few draws per thousand land outside their own tolerance. Both are stated in §6.1 and neither is hidden behind a boundary row any more.
Status. Resolved. The statistical path samples the physical model, the two schools agree with each other and with an independent integration of that model, and the envelope arithmetic — which was never in question — is unchanged. The remaining honest gap is the hand-typed boundary and the datum-shift allowance, both of which the application now reports rather than guessing at.
11. Validation & evidence
Nothing in this paper is a description of intent. Each statement is pinned by an automated test that runs on every build, and the build is red if any of them disagrees with the code.
| Evidence | What it pins |
|---|---|
| docs/validation-report.pdf | Six published worked examples from independent sources (a bearing clearance, a blocks-in-housing gap, a linear stack, an ISO 286 H8/h7 fit, a radial ⌀/2 case and a distribution check) recomputed by this engine and printed with their sources. The PDF is regenerated by the test suite and the test fails if any row prints a FAIL. |
ValidationBenchmarkTests, PublishedExampleTests |
The engine against published numbers, to the published digits; two of them load the bundled example projects end to end. |
BoundaryMethodTests |
Every branch of the boundary derivation, including the round-trip of files written by earlier versions. |
BonusCorrelationTests |
The per-sample bonus identity, the sign of the correlation, the σ reduction with real numbers, and that an unlinked bonus behaves exactly as it always did. |
PerSlotToleranceTests, ResultBandRowSetTests |
That Σ CONTRIB ± Σ TOLERANCE for a slot equals the MIN/MAX shown for that slot — the grid adds up to the results strip, digit for digit. |
WorstCaseBonusTests |
That a Ⓜ/Ⓛ bonus is absent at maximum material in worst case too, at every temperature slot — and that the classic and the boundary school then produce the SAME band, asserted against each other rather than against literals. |
StatisticalVerdictTests |
One acceptance rule across the panel, the strip, the Results tab and the reports. |
WhitePaperTests |
This document. It re-reads both language versions, recomputes every marked number from the engine, and fails if the paper and the code have drifted apart — or if the two languages state different numbers. |
The full test methodology is described in docs/TESTING.md; the
algorithms above are specified for developers in
docs/dev/domain-and-analysis.md.