{
  "endpoint": "https://otherworldlabs.com/stair-rise-run-and-stringer/api.json",
  "page": "https://otherworldlabs.com/stair-rise-run-and-stringer/",
  "what_it_answers": "For a total rise, a tread depth and a maximum riser height the reader types, how many whole risers the flight needs, what each riser's exact height is, the sixteenth below and above that height with the remainder and the deficit each carries, how many treads and how long the total run is, and the length of the stringer line those two sides make.",
  "shape": "A static document, not a service. There is no request to make and nothing is posted anywhere: this file is the engine written out, so an agent can answer the question without the page and get the same figures the page prints. The engine itself is the script with the id core in the page above, and its flat outputs function is apiOutputs(state).",
  "units": "Inches and whole counts, and nothing else. No volume, no weight, no second unit and no money. Where a sixteenth is printed as a fraction beside a decimal, that is a second notation of the same inch and not a second quantity.",
  "the_page_supplies_no_limit": "Every limit in this arithmetic is typed by the reader. The maximum riser height is the reader's own figure, read off whatever rule they are held to; this engine ships that input blank, never fills it, and holds no maximum, minimum, standard or default of its own. It cites no rule, no document and no authority, it says nothing about stringer stock, grade, species, span, fasteners, bearing, handrails, guards or landings, and no figure it returns is a judgement about any stair.",
  "inputs": [
    {
      "name": "totalRise",
      "label": "Total rise, finished floor to finished floor, in inches",
      "type": "number",
      "offered": { "min": -12, "max": 240, "step": 0.0625 },
      "blank_means": "nothing typed yet, which puts the answer in the incomplete state",
      "note": "The negative end is deliberately reachable, so a reversed or mistyped measurement lands in a named state instead of a confident stringer length. The range and the step here are the range and the step the page's own control offers."
    },
    {
      "name": "treadDepth",
      "label": "Tread depth, in inches",
      "type": "number",
      "offered": { "min": 0, "max": 24, "step": 0.0625 },
      "blank_means": "nothing typed yet, which puts the answer in the incomplete state",
      "note": "A tread depth of zero or less is incomplete rather than a run of nothing, because a flight cut from a tread depth nobody typed is the fault this state exists to stop."
    },
    {
      "name": "maxRiserHeight",
      "label": "Maximum riser height, in inches",
      "type": "number",
      "offered": { "min": 0, "max": 24, "step": 0.0001 },
      "blank_means": "nothing typed yet, which puts the answer in the incomplete state",
      "note": "The reader's own ceiling, and the engine supplies no value for it, ever. Zero or less is incomplete rather than a division. The step here is a ten-thousandth of an inch and not a sixteenth, unlike the two measured inputs, for two reasons: a rule the reader is held to may be written in millimetres or in tenths, and a ceiling forced onto the sixteenth grid makes the overTypedMaximum state below unreachable by arithmetic, because an exact height that does not pass a limit which is itself a whole number of sixteenths cannot round up past it."
    },
    {
      "name": "topTreadIsLanding",
      "label": "Is the top tread the landing?",
      "type": "choice",
      "choices": ["one fewer tread than risers", "the same number of treads as risers"],
      "blank_means": "nothing chosen yet, which puts the answer in the incomplete state",
      "note": "A fact about the job rather than something the engine can know: a flight whose top step is the floor being climbed to takes one fewer tread than risers, and one with a tread of its own up there takes the same number. The value is TRIMMED before it is compared, so padding around either of the two strings above still reads as that choice; anything else, whitespace alone included, reads as nothing chosen and puts the answer in the incomplete state. (Round 1's M2: this note used to say anything that is not one of the two strings, whitespace included, reads as nothing chosen, which the trim disproves for a padded choice. The engine is right and the note was wrong: the page's own select offers only the empty string and the two exact strings, so the trim is reachable through this endpoint's reader and not through the page.)"
    }
  ],
  "rounding": {
    "function": "rN(v, n) = Math.round(v * 10^n) / 10^n, on the double, with no epsilon and no decimal library.",
    "at_the_half": "JavaScript Math.round takes the half toward positive infinity. On the positive side that is half away from zero: 7.2985 at three decimals is 7.299 and 0.125 at two is 0.13. On the negative side it is half toward zero: -0.125 at two decimals is -0.12, not -0.13, and -0.50005 at four is -0.5 while -0.50015 at four is -0.5001. The negative half is reachable here, because totalRise is offered down to -12, and it is stated rather than left to a reader to discover. Every figure in this paragraph is inside the range the rise input offers and every one of them is executed against rN by the page's own test suite, out of the worked_halves list below: round 1's B6 found a figure here (-36.50015 at four given as -36.5002, when rN returns -36.5001) that contradicted the function stated one clause above it, so the examples are now machine-checked rather than read.",
    "worked_halves": [
      { "v": 7.2985, "decimals": 3, "is": 7.299 },
      { "v": 0.125, "decimals": 2, "is": 0.13 },
      { "v": -0.125, "decimals": 2, "is": -0.12 },
      { "v": -0.50005, "decimals": 4, "is": -0.5 },
      { "v": -0.50015, "decimals": 4, "is": -0.5001 }
    ],
    "what_is_rounded": "Only the figures returned, each to the decimals named in outputs below, with one exception named here rather than left to be discovered: the riser count is taken from the unrounded division and the exact riser height from the unrounded rise over that count, but the stringer line is taken from the unrounded rise and the total run AS RETURNED, that is after the run's rounding to 4 decimals. Inside the steps offered above the two readings cannot differ - the run is a whole tread count times a tread depth offered in sixteenths, so it is already exact at 4 decimals and the rounding changes nothing - and a reimplementation that squares the unrounded run agrees with this endpoint at every state reachable here. The sentence says which one the engine squares so that a reimplementation need not guess.",
    "sixteenths": "heightRoundedDown and heightRoundedUp are exact multiples of 0.0625, taken as floor and ceiling of (exactRiserHeight x 16) with a 1e-9 guard so a division that lands on a sixteenth is read as landing on it rather than a billionth below it."
  },
  "the_chain": "Each output is the next operand, which is why this is a chain and not a formula: the rise over the typed ceiling gives the riser count, the count gives the exact riser height, the count gives the tread count, the tread count gives the run, and the run with the rise gives the stringer line. One typed figure therefore re-foots four dependent figures.",
  "formulas": {
    "riseOverCeiling": "totalRise / maxRiserHeight, rounded to 4 decimals. Reported because it is the division the riser count is taken from, and it is not itself a count.",
    "riserCount": "ceil(totalRise / maxRiserHeight), the smallest whole number of equal risers whose height does not pass the maximum typed. Taken with a 1e-9 guard, so a rise that divides exactly does not gain a riser. Never less than 1.",
    "exactRiserHeight": "totalRise / riserCount, rounded to 3 decimals.",
    "heightRoundedDown": "floor(exactRiserHeight x 16) / 16, rounded to 4 decimals.",
    "heightRoundedUp": "ceil(exactRiserHeight x 16) / 16, rounded to 4 decimals. Equal to heightRoundedDown when the rise divides exactly at the sixteenth.",
    "lowerStack": "riserCount x heightRoundedDown, rounded to 4 decimals.",
    "upperStack": "riserCount x heightRoundedUp, rounded to 4 decimals.",
    "remainderCarried": "totalRise - (riserCount x heightRoundedDown), rounded to 4 decimals: what the flight is short of the rise if every riser is cut at the lower sixteenth. Zero or more.",
    "deficitAtRoundedUp": "(riserCount x heightRoundedUp) - totalRise, rounded to 4 decimals: what the flight overshoots the rise if every riser is cut at the upper sixteenth. Zero or more.",
    "risersAtUpperSixteenth": "How many of the flight's risers take the upper sixteenth once the marks are set at the nearest sixteenth of each cumulative height: round((riserCount - 1) x exactRiserHeight x 16) - (riserCount - 1) x floor(exactRiserHeight x 16), plus one when the top riser's own height lands on the upper sixteenth. Equal to riserCount when the rise divides exactly. A whole count, never negative.",
    "treadCount": "riserCount - 1 when the top tread is the landing, riserCount when it is not. A whole count, never negative.",
    "totalRun": "treadCount x treadDepth, rounded to 4 decimals.",
    "stringerLine": "sqrt(totalRise^2 + totalRun^2), rounded to 3 decimals. The sloped line the cuts are laid out along. It is not a stock length, a thickness, a grade or a span, and nothing here says what stock it should be cut from.",
    "clearanceAtRoundedDown": "maxRiserHeight - heightRoundedDown, rounded to 4 decimals. Zero or more, because the riser count is taken so that the exact height does not pass the maximum typed.",
    "roundedUpMinusCeiling": "heightRoundedUp - maxRiserHeight, rounded to 4 decimals. SIGNED, and the sign is the whole point: negative or zero while the upper sixteenth is inside the ceiling typed, positive when it is past it, which is the state overTypedMaximum names. The flag itself is taken with the same 1e-9 guard the riser count and the sixteenths carry - overTypedMaximum is heightRoundedUp > maxRiserHeight + 1e-9, not a plain greater-than - so a sixteenth that lands on the ceiling typed is read as landing on it rather than a billionth past it. Round 1's M1: this guard was the one the endpoint did not name, and an agent reproducing the engine from this file needs all three.",
    "the_flight_marks": "The page also prints the flight riser by riser. Mark i, for i below the riser count, is round(i x exactRiserHeight x 16) / 16; the last mark is totalRise itself, so the marks end on the floor line and no rounding error piles up on the way. Each riser's height is the difference between its own mark and the one below it, which makes the top riser the one that takes whatever is left. These rows are not in the outputs below: they are a layout of the figures that are."
  },
  "states": [
    {
      "name": "incomplete",
      "when": "any of the four inputs is blank or unreadable, or treadDepth is zero or less, or maxRiserHeight is zero or less",
      "returns": "incomplete true and every figure null",
      "note": "This is the state the page ships in, because it may not supply the reader's maximum. No headline figure is printed until this state is over, and the sentence saying so prints in the glance at the top of the page. Reachable from the page's own controls: it is what the page is on arrival. What 'unreadable' means, named rather than left to a reimplementation's choice of reader: each number is read with parseFloat, so a figure with a trailing unit or a stray character ('7.5 in', '7.5x') reads as 7.5 and does NOT land here, while a blank, a string with no leading number, and anything that reads as not finite ('Infinity', 'NaN') do. Neither is typeable through the page, whose three measured fields are type=number, nor reachable through the steps offered in inputs above.",
      "example": { "totalRise": "109.5", "treadDepth": "10", "maxRiserHeight": "", "topTreadIsLanding": "one fewer tread than risers" }
    },
    {
      "name": "riseNotPositive",
      "when": "the four inputs are in and totalRise is zero or less",
      "returns": "riseNotPositive true, every computed figure null, and the rise the engine read printed back",
      "note": "Reachable from the page's own controls: the rise input is offered down to -12. The sentence saying why nothing is computed prints in the glance ABOVE the marker rows, in the same place as the incomplete state's own sentence (round 1's M3).",
      "example": { "totalRise": "-36", "treadDepth": "10", "maxRiserHeight": "7.75", "topTreadIsLanding": "the same number of treads as risers" }
    },
    {
      "name": "dividesExactly",
      "when": "computed, and exactRiserHeight x 16 is a whole number of sixteenths",
      "returns": "dividesExactly true, with heightRoundedDown and heightRoundedUp equal and remainderCarried and deficitAtRoundedUp both zero",
      "note": "One riser height and no rounding pair. Reachable: a rise of 109.6875 against a ceiling of 7.75 is 15 risers at 7.3125 in, and a rise of 112.5 against a ceiling of 7.5 is 15 at 7.5 in. (Round 1's B5: the example here used to be a rise of 109.5 against a ceiling of 7.3125, which is 15 risers at 7.300 in and a ROUNDING PAIR of 7.2500 and 7.3125 - the one state this note exists to make reachable, named by a state that does not reach it. Every state below now carries a machine-readable `example`, and the page's own test suite runs each one through the engine and asserts the named state holds, so a worked state this file cannot reach fails S5 rather than misleading a reader.)",
      "example": { "totalRise": "109.6875", "treadDepth": "10", "maxRiserHeight": "7.75", "topTreadIsLanding": "one fewer tread than risers" }
    },
    {
      "name": "roundingPair",
      "when": "computed, and the exact height falls between two sixteenths",
      "returns": "dividesExactly false, with both directions, the remainder carried and the deficit",
      "note": "The direction is the reader's, so both are returned and neither is recommended.",
      "example": { "totalRise": "109.5", "treadDepth": "10", "maxRiserHeight": "7.75", "topTreadIsLanding": "one fewer tread than risers" }
    },
    {
      "name": "overTypedMaximum",
      "when": "computed, and heightRoundedUp is greater than maxRiserHeight by more than the 1e-9 guard",
      "returns": "overTypedMaximum true, with roundedUpMinusCeiling positive",
      "note": "Reachable only in the rounded-up direction, since the riser count is taken so the exact height does not pass the typed maximum. It is reported as the comparison it is, in the reader's own terms: the engine names no rule and cites no document, and the figure it returns is the difference between two numbers the reader typed or asked for.",
      "example": { "totalRise": "109.5", "treadDepth": "10", "maxRiserHeight": "7.3", "topTreadIsLanding": "one fewer tread than risers" }
    }
  ],
  "outputs": [
    { "name": "incomplete", "type": "boolean", "of": "the state above" },
    { "name": "riseNotPositive", "type": "boolean", "of": "the state above" },
    { "name": "dividesExactly", "type": "boolean or null", "of": "null while incomplete or while the rise is zero or less" },
    { "name": "overTypedMaximum", "type": "boolean", "of": "false unless the upper sixteenth is past the typed maximum" },
    { "name": "riserCount", "type": "whole count or null", "unit": "risers", "sign": "1 or more when computed" },
    { "name": "exactRiserHeight", "type": "number or null", "unit": "inches", "decimals": 3 },
    { "name": "heightRoundedDown", "type": "number or null", "unit": "inches", "decimals": 4 },
    { "name": "heightRoundedUp", "type": "number or null", "unit": "inches", "decimals": 4 },
    { "name": "remainderCarried", "type": "number or null", "unit": "inches", "decimals": 4, "sign": "zero or more" },
    { "name": "deficitAtRoundedUp", "type": "number or null", "unit": "inches", "decimals": 4, "sign": "zero or more" },
    { "name": "risersAtUpperSixteenth", "type": "whole count or null", "unit": "risers", "sign": "zero or more" },
    { "name": "treadCount", "type": "whole count or null", "unit": "treads", "sign": "zero or more" },
    { "name": "totalRun", "type": "number or null", "unit": "inches", "decimals": 4, "sign": "zero or more" },
    { "name": "stringerLine", "type": "number or null", "unit": "inches", "decimals": 3, "sign": "zero or more" },
    { "name": "riseOverCeiling", "type": "number or null", "unit": "risers, unrounded", "decimals": 4 },
    { "name": "lowerStack", "type": "number or null", "unit": "inches", "decimals": 4 },
    { "name": "upperStack", "type": "number or null", "unit": "inches", "decimals": 4 },
    { "name": "clearanceAtRoundedDown", "type": "number or null", "unit": "inches", "decimals": 4, "sign": "zero or more" },
    { "name": "roundedUpMinusCeiling", "type": "number or null", "unit": "inches", "decimals": 4, "sign": "signed, see the formula" }
  ],
  "worked_example": {
    "state": {
      "totalRise": "109.5",
      "treadDepth": "10",
      "maxRiserHeight": "7.75",
      "topTreadIsLanding": "one fewer tread than risers"
    },
    "outputs": {
      "incomplete": false,
      "riseNotPositive": false,
      "dividesExactly": false,
      "overTypedMaximum": false,
      "riserCount": 15,
      "exactRiserHeight": 7.3,
      "heightRoundedDown": 7.25,
      "heightRoundedUp": 7.3125,
      "remainderCarried": 0.75,
      "deficitAtRoundedUp": 0.1875,
      "risersAtUpperSixteenth": 12,
      "treadCount": 14,
      "totalRun": 140,
      "stringerLine": 177.736,
      "riseOverCeiling": 14.129,
      "lowerStack": 108.75,
      "upperStack": 109.6875,
      "clearanceAtRoundedDown": 0.5,
      "roundedUpMinusCeiling": -0.4375
    },
    "read_it_back": "109.5 over 7.75 is 14.1290, so 15 whole risers. 109.5 over 15 is 7.300 in each. The sixteenth below is 7.2500 and the sixteenth above is 7.3125. Fifteen risers at 7.2500 stack to 108.7500, which leaves 0.7500 to carry; fifteen at 7.3125 stack to 109.6875, which overshoots by 0.1875. With the top tread the landing there are 14 treads, so the run is 14 x 10 = 140.0000, and the stringer line is the diagonal of 109.5 and 140.0000, which is 177.736."
  },
  "second_worked_example": {
    "why": "the state the fence's hardest line lives in: the rounded-up direction past the reader's own ceiling",
    "state": {
      "totalRise": "109.5",
      "treadDepth": "10",
      "maxRiserHeight": "7.3",
      "topTreadIsLanding": "one fewer tread than risers"
    },
    "outputs": {
      "dividesExactly": false,
      "overTypedMaximum": true,
      "riserCount": 15,
      "exactRiserHeight": 7.3,
      "heightRoundedDown": 7.25,
      "heightRoundedUp": 7.3125,
      "remainderCarried": 0.75,
      "deficitAtRoundedUp": 0.1875,
      "clearanceAtRoundedDown": 0.05,
      "roundedUpMinusCeiling": 0.0125
    },
    "note": "A ceiling of 7.3 is not a whole number of sixteenths, so the exact height of 7.300 sits on the ceiling and the sixteenth above it, 7.3125, is 0.0125 past it. That is the whole of what overTypedMaximum says: the difference between two figures the reader supplied. It names no rule, and it is not a statement that either direction is right. With the same rise against a ceiling of 7.3125, the upper direction lands exactly on the ceiling and overTypedMaximum is false."
  },
  "limits": [
    "This is geometry against the limits the reader types. It is not a specification anyone builds to, and nothing in it says a stair is adequate for anything.",
    "The engine holds no maximum riser height, no minimum tread depth, no headroom figure and no default of any kind. If an input is blank the answer is the incomplete state, not a figure from a table.",
    "The stringer line is a line, not a board: no thickness, no grade, no species, no span, no fastener and no bearing figure is computed or implied.",
    "The flight marks assume every riser is cut from the same rise and that the rise typed is the finished floor to the finished floor. A rise measured to an unfinished slab is a different number and this engine cannot know which one it was given.",
    "Nothing a reader types is stored or sent anywhere, and the page holds nothing at all between visits: it makes no web-storage, cookie or IndexedDB call of any kind. The page does send one beacon of its own - on load, when the offer block comes into view, and when the buy link is clicked - carrying the page's address, which of those three stages it is, the host the reader came from and a random id that lasts the visit. No figure a reader types goes into it and nothing else is posted. (Round 1's B2: this line used to read `Nothing is stored and nothing is sent`, which the beacon disproves on every visit.)"
  ]
}
