Jump to content

The Principia Machina/TPC-PY/gen sr3 scope corpus.py

From The Invariance Itself
Revision as of 02:10, 30 September 2026 by BFP Rauch (talk | contribs) (TPC-PY source gen_sr3_scope_corpus.py (-), SHA-256 5e21e13fa65835fb; conformed TPS 0.4.0; PROPOSED, not declared (TPC-PY 0.2.0))
# -*- coding: utf-8 -*-
"""Generator for tpc/vectors/sr3_scope_corpus.json (tpc_strike 0.2.0,
proposed: sr3_scope, the interim SR3 and SR8 scope of D180.3; TPS 4.4.13
and 4.2.13 as staged for revision 6, provisional numbers;
tpm_d179_d180_d181_texts 0.1.0 section 2, T-C3 and T-C4).

A corpus of its own beside strike_corpus.json, as sr8_order_corpus.json
was at tpc_strike 0.1.1: strike_corpus.json is written by hand, carries no
TFK and stays byte-identical at its tpc_strike 0.1.0 label; these cases
carry whole TFKs, which the tree's TFK corpora (zone, tests, form, hold)
build in a generator with literal expectations.

THE RULE (fixed by the supervising thread's brief; not widened here):
  sr3_scope(tfk, quantity) is "MECHANICAL" when the TrM entry whose NAME
  is quantity exists and its DOF is the TARGET of a TPK CONSTRAINT whose
  ACTION is FIX; "INSPECTION" when the entry exists and no FIX CONSTRAINT
  targets its DOF (a REMOVE constraint does not count; the time DOF is an
  ordinary DOF); ["REFUSED", reason] when no TrM entry has that NAME or
  the input is not of the shape read. Matching is exact.

Every expectation is HAND-DERIVED and written literally below, from the
rule and from the tree's zone wording (tpc_zone's trm_well_formed and
t1_well_formed); the generator refuses to write the corpus if the module
disagrees with any of them. The fixtures are the tree's own kernels: the
spring with its forms (examples/spring_draft.json; tests corpus TFK_F),
the toy of the zone corpus, and the tests corpus's FIX fixture
(t3/laws-ground-fragment); under schema 0.5.0 (D176.1, D177.1) every fixture
LAW carries its ROLE, which sr3_scope does not read. Deterministic; no randomness.
0.1.3 (C-MS-5, increment 11): the decode/ cases tell a JSON {} from a JSON [];
the corpus is written with LF line ends on every platform (S65).
Run: cd tpc && python3 gen_sr3_scope_corpus.py (Windows: py ...)."""
import copy, json, os, sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import tpc_strike as S

cases = []


def case(cid, tfk, quantity, expect, **extra):
    cs = {"id": "sr3_scope/" + cid, "fn": "sr3_scope", "input": {"tfk": tfk, "quantity": quantity}}
    cs.update(extra)
    cs["expect"] = expect
    cases.append(cs)


def c(o):
    return copy.deepcopy(o)


def R(reason):
    return "[REFUSED," + reason + "]"


M, I = "MECHANICAL", "INSPECTION"

# ---- the toy TFK of the zone corpus: a mass on a linear spring, z fixed at 0 --------
TPK = {
  "DOF": [
    {"NAME": "x", "DIMENSION": "L1"},
    {"NAME": "v", "DIMENSION": "L1 T-1"},
    {"NAME": "z", "DIMENSION": "L1"}],
  "CONSTANT": [
    {"NAME": "m", "DIMENSION": "M1"},
    {"NAME": "k_s", "DIMENSION": "M1 T-2"},
    {"NAME": "x_max", "DIMENSION": "L1", "VALUE": "0.05", "UNIT": "m", "UNCERTAINTY": "0.001"}],
  "LAW": [
    {"NAME": "N2", "STATEMENT": "m dv/dt = -k_s x", "REFS": ["m", "v", "k_s", "x"], "ROLE": "EOM(v)"},
    {"NAME": "KIN", "STATEMENT": "dx/dt = v", "REFS": ["x", "v"], "ROLE": "EOM(x)"},
    {"NAME": "FREE_Z", "STATEMENT": "d2z/dt2 = 0", "REFS": ["z"], "ROLE": "CONSTRAINT"}],
  "ASSUMPTION": [
    {"NAME": "A_lin", "PROPOSITION": "the restoring force is linear in x (Hooke régime)", "REFS": ["x"]}],
  "CONSTRAINT": [
    {"NAME": "C_NOZ", "TARGET": "z", "ACTION": "FIX", "VALUE": "0", "UNIT": "m", "STATEMENT": "motion confined to z = 0"}],
  "BOUNDARY": [
    {"NAME": "B_small", "PREDICATE": "|x| < x_max", "REFS": ["x", "x_max"]}],
}
TRM = {
  "HEADER": {
    "ENVIRONMENT_INVENTORY": [
      {"ENVIRONMENT_QUANTITY": "position", "DIMENSION": "L1"},
      {"ENVIRONMENT_QUANTITY": "velocity", "DIMENSION": "L1 T-1"},
      {"ENVIRONMENT_QUANTITY": "height", "DIMENSION": "L1"}],
    "SIGNATURE": [
      {"NAME": "time_derivative", "OVER": ["x", "v"], "IMAGE": "d/dt maps position to velocity"}],
    "TRANSFORMATIONS": [
      {"NAME": "time_translation", "STATEMENT": "t -> t + t0"}]},
  "ENTRIES": [
    {"NAME": "E_x", "DOF": "x", "DOMAIN": "|x| < x_max", "ENVIRONMENT_QUANTITY": "position",
     "IDENTIFICATION": "x is the displacement from equilibrium along the spring axis",
     "FALSIFIER": "a measured restoring force not proportional to x inside the domain",
     "FALSIFIER_CLASS": "F3", "BEARS": ["N2", "KIN"], "DIMENSION": "L1"},
    {"NAME": "E_v", "DOF": "v", "DOMAIN": "|x| < x_max", "ENVIRONMENT_QUANTITY": "velocity",
     "IDENTIFICATION": "v is the time derivative of x",
     "FALSIFIER": "a measured velocity differing from dx/dt beyond uncertainty",
     "FALSIFIER_CLASS": "F2", "BEARS": ["KIN"], "DIMENSION": "L1 T-1"},
    {"NAME": "E_z", "DOF": "z", "DOMAIN": "always", "ENVIRONMENT_QUANTITY": "height",
     "IDENTIFICATION": "z is the height above the track",
     "FALSIFIER": "any measured z different from 0",
     "FALSIFIER_CLASS": "F3", "BEARS": ["FREE_Z", "C_NOZ"], "DIMENSION": "L1"}],
}
TMS = {
  "PARAMETERS": {"STEP_CONVENTION": "one application of an inference rule or algebraic identity is one step",
                 "K": "2", "N_X": 2, "S_MAX": 10},
  "CONSTANTS": [{"NAME": "w_a", "VALUE": "1"}, {"NAME": "w_s", "VALUE": "0.5"}],
  "YARDSTICKS": [{"NAME": "combined_distance", "KEY": ["SR1", "WSUM:w_a*A;w_s*S", "S"], "LIMITING": []}],
}
TOY = {"TPK": TPK, "TrM": TRM, "TMS": TMS}

# ---- the spring with its forms: examples/spring_draft.json, the tests corpus's TFK_F --------
TPK_F = c(TPK)
TPK_F["DOF"].append({"NAME": "t", "DIMENSION": "T1"})
TPK_F["LAW"] = [
    {"NAME": "N2", "STATEMENT": "m dv/dt = -k_s x", "REFS": ["m", "v", "k_s", "x", "t"], "ROLE": "EOM(v)",
     "EQUATION": "m * D(v, t) = -k_s * x"},
    {"NAME": "KIN", "STATEMENT": "dx/dt = v", "REFS": ["x", "v", "t"], "ROLE": "EOM(x)", "EQUATION": "D(x, t) = v"},
    {"NAME": "FREE_Z", "STATEMENT": "d2z/dt2 = 0", "REFS": ["z", "t"], "ROLE": "CONSTRAINT", "EQUATION": "D(z, t, 2) = 0"}]
TPK_F["BOUNDARY"] = [{"NAME": "B_small", "PREDICATE": "|x| < x_max", "REFS": ["x", "x_max"], "CONDITION": "abs(x) < x_max"}]
TRM_F = c(TRM)
TRM_F["HEADER"]["ENVIRONMENT_INVENTORY"].append({"ENVIRONMENT_QUANTITY": "time", "DIMENSION": "T1"})
TRM_F["HEADER"]["SIGNATURE"] = [{"NAME": "time_derivative", "OVER": ["x", "v", "t"], "IMAGE": "d/dt maps position to velocity",
                                 "FORM": "D(x, t) = v", "IMAGE_FORM": "D(position, time) = velocity"}]
TRM_F["ENTRIES"].append({"NAME": "E_t", "DOF": "t", "DOMAIN": "always", "ENVIRONMENT_QUANTITY": "time",
                         "IDENTIFICATION": "t is the laboratory clock reading",
                         "FALSIFIER": "two laboratory clocks that disagree beyond their stated uncertainty",
                         "FALSIFIER_CLASS": "F3", "BEARS": [], "DIMENSION": "T1"})
SPRING = {"TPK": TPK_F, "TrM": TRM_F, "TMS": TMS}


def fix(name, target, value, unit):
    return {"NAME": name, "TARGET": target, "ACTION": "FIX", "VALUE": value, "UNIT": unit,
            "STATEMENT": target + " = " + value + " " + unit}


def remove(name, target):
    return {"NAME": name, "TARGET": target, "ACTION": "REMOVE", "STATEMENT": "no " + target}


C_NOZ = TPK["CONSTRAINT"][0]


# S33 (2026-09-26): EOM(q) is the ROLE of a law for the DOF it moves; a law restating the
# motion of a DOF the kernel FIXes (or REMOVEs, the like case) takes ROLE CONSTRAINT. MOTION
# names the DOF each law of the spring family moves; s33_roles sets those ROLEs from the
# TPK's constraints wherever a fixture's constraints change. Only the spring family's
# fixtures pass through it; a vector whose subject is the ROLE sets its ROLE after it.
MOTION = {"N2": "v", "EOM_X": "v", "KIN": "x", "FREE_Z": "z", "EOM_Z": "z"}


def s33_roles(tpk):
    cons = tpk.get("CONSTRAINT", [])
    held = set(e["TARGET"] for e in (cons if isinstance(cons, list) else [])
               if isinstance(e, dict) and e.get("ACTION") in ("FIX", "REMOVE") and isinstance(e.get("TARGET"), str))
    for l in tpk["LAW"]:
        q = MOTION.get(l.get("NAME"))
        if q is not None:
            l["ROLE"] = "CONSTRAINT" if q in held else "EOM(" + q + ")"
    return tpk


def with_cons(tfk, *cons):
    t = c(tfk)
    t["TPK"]["CONSTRAINT"] = [c(x) for x in cons]
    s33_roles(t["TPK"])
    return t


def entry_index(tfk, name):
    for i, e in enumerate(tfk["TrM"]["ENTRIES"]):
        if e["NAME"] == name:
            return i
    raise KeyError(name)


# ============================================================================
# A. the rule over the tree's kernels
# ============================================================================
case("spring/E_z-fixed", SPRING, "E_z", M,
     note="C_NOZ FIXes z: a FIXed DOF is tested as TPS 4.4.4 and 4.4.8 state (T-C3)")
case("spring/E_x-free", SPRING, "E_x", I,
     note="no FIX CONSTRAINT targets x: a trajectory value, row 21 INSPECTION until C-16 (T-C3, T-C4)")
case("spring/E_v-free", SPRING, "E_v", I, note="no FIX CONSTRAINT targets v")
case("spring/E_t-time", SPRING, "E_t", I,
     note="the time DOF t, the independent variable, is an ordinary DOF here: no FIX targets it")
case("toy/E_z-fixed", TOY, "E_z", M, note="the zone corpus's toy TFK, without forms and without t")
case("toy/E_x-free", TOY, "E_x", I)

# the tests corpus's FIX fixture (t3/laws-ground-fragment): x and v FIXed, the constants valued
TPK_B = c(TPK_F)
TPK_B["CONSTANT"][0]["VALUE"] = "2"; TPK_B["CONSTANT"][0]["UNIT"] = "kg"
TPK_B["CONSTANT"][1]["VALUE"] = "3"; TPK_B["CONSTANT"][1]["UNIT"] = "N m-1"
TPK_B["CONSTRAINT"] += [fix("C_X", "x", "0.1", "m"), fix("C_V", "v", "0", "m s-1")]
s33_roles(TPK_B)                           # x and v FIXed: N2 and KIN restate their motion (S33)
TESTS_FIX = {"TPK": TPK_B, "TrM": TRM_F, "TMS": TMS}
case("tests-fix/E_x-fixed", TESTS_FIX, "E_x", M,
     note="the tests corpus's FIX fixture; its TPK fails T3 over the laws at the final pass (N2 evaluates 0 = -0.3), "
          "which sr3_scope does not read")
case("tests-fix/E_v-fixed", TESTS_FIX, "E_v", M)
case("tests-fix/E_t-time", TESTS_FIX, "E_t", I)

FIX_X = with_cons(SPRING, C_NOZ, fix("C_X", "x", "0.1", "m"))
case("spring-fix-x/E_x-fixed", FIX_X, "E_x", M, note="the spring with x also FIXed; passes the final pass")
case("spring-fix-x/E_v-free", FIX_X, "E_v", I)

# ============================================================================
# B. REMOVE does not count
# ============================================================================
REM_ONLY = with_cons(SPRING, remove("C_ZR", "z"))
case("remove-only/E_z", REM_ONLY, "E_z", I,
     note="a REMOVE constraint does not count. Reachable only before the final pass: the gate refuses an entry "
          "on a REMOVEd DOF (CODOMAIN, D107.1)")
case("remove-only/E_x", REM_ONLY, "E_x", I)
REM_GONE = with_cons(SPRING, remove("C_ZR", "z"))
del REM_GONE["TrM"]["ENTRIES"][entry_index(REM_GONE, "E_z")]
REM_GONE["TrM"]["HEADER"]["ENVIRONMENT_INVENTORY"] = [
    q for q in REM_GONE["TrM"]["HEADER"]["ENVIRONMENT_INVENTORY"] if q["ENVIRONMENT_QUANTITY"] != "height"]
case("removed-no-entry/E_z", REM_GONE, "E_z", R("QUANTITY E_z names no TrM entry"),
     note="after the gate a REMOVEd DOF carries no entry (D107.1), so no QUANTITY names it; passes the final pass")
case("removed-no-entry/E_x", REM_GONE, "E_x", I)

# ============================================================================
# C. a FIX beside another constraint
# ============================================================================
case("fix-and-fix-same-value/E_z", with_cons(SPRING, C_NOZ, fix("C_Z2", "z", "0", "cm")), "E_z", M,
     note="two FIX of z at one value (tests corpus t3/same-value); passes the final pass")
case("fix-and-remove/E_z", with_cons(SPRING, C_NOZ, remove("C_ZR", "z")), "E_z", M,
     note="a FIX targets z, so MECHANICAL; the gate refuses this TFK (T3: z is both removed and fixed; CODOMAIN)")
case("remove-then-fix/E_z", with_cons(SPRING, remove("C_ZR", "z"), C_NOZ), "E_z", M,
     note="the order of the constraints does not matter")
case("fix-and-fix-two-values/E_z", with_cons(SPRING, C_NOZ, fix("C_Z2", "z", "1", "m")), "E_z", M,
     note="the gate refuses this TFK (T3: z fixed at 0 m and at 1 m)")
t = with_cons(SPRING, fix("C_X", "x", "0.1", "m"))
t["TrM"]["ENTRIES"][entry_index(t, "E_z")]["BEARS"] = ["FREE_Z"]
case("fix-other-dof/E_z", t, "E_z", I,
     note="a FIX of another DOF does not count: z free, x FIXed; passes the final pass")

# ============================================================================
# D. a QUANTITY that names no TrM entry
# ============================================================================
case("unknown/E_w", SPRING, "E_w", R("QUANTITY E_w names no TrM entry"))
case("unknown/constant-k_s", SPRING, "k_s", R("QUANTITY k_s names no TrM entry"),
     note="a CONSTANT is not a TrM entry; an M record's QUANTITY names a TrM entry (PMP-TPM VOCABULARY)")
case("unknown/constant-x_max", SPRING, "x_max", R("QUANTITY x_max names no TrM entry"),
     note="a CONSTANT with VALUE, UNIT and UNCERTAINTY is still not a TrM entry")
case("unknown/dof-name-z", SPRING, "z", R("QUANTITY z names no TrM entry"),
     note="the DOF's NAME is not its entry's NAME, even for the FIXed DOF")
case("unknown/law-N2", SPRING, "N2", R("QUANTITY N2 names no TrM entry"))
case("unknown/constraint-C_NOZ", SPRING, "C_NOZ", R("QUANTITY C_NOZ names no TrM entry"))
case("unknown/environment-quantity-height", SPRING, "height", R("QUANTITY height names no TrM entry"),
     note="an ENVIRONMENT_QUANTITY is not an entry's NAME")
t = c(SPRING); t["TrM"]["ENTRIES"] = []
case("empty-entries/E_z", t, "E_z", R("QUANTITY E_z names no TrM entry"))
t = c(SPRING); t["TrM"]["ENTRIES"].append(c(TRM_F["ENTRIES"][entry_index(SPRING, "E_z")]))
case("duplicate/E_z", t, "E_z", R("TrM: duplicate entry name E_z"),
     note="two entries of the NAME asked: the answer would be ambiguous (trm_well_formed's words)")
t = c(SPRING); t["TrM"]["ENTRIES"].append(c(TRM_F["ENTRIES"][entry_index(SPRING, "E_x")]))
case("duplicate-other/E_z", t, "E_z", M,
     note="a duplicate of another NAME does not change the answer and is trm_well_formed's to report")

# ============================================================================
# E. exact matching
# ============================================================================
case("case/e_z", SPRING, "e_z", R("QUANTITY e_z names no TrM entry"))
case("case/E_Z", SPRING, "E_Z", R("QUANTITY E_Z names no TrM entry"))
TWO = c(SPRING)
TWO["TrM"]["ENTRIES"].append({"NAME": "e_z", "DOF": "x", "DOMAIN": "|x| < x_max", "ENVIRONMENT_QUANTITY": "position",
                              "IDENTIFICATION": "x read on a second scale", "FALSIFIER": "the two scales disagree",
                              "FALSIFIER_CLASS": "F2", "BEARS": [], "DIMENSION": "L1"})
case("case/two-entries/e_z", TWO, "e_z", I, note="entries E_z (DOF z) and e_z (DOF x): NAMEs match exactly")
case("case/two-entries/E_z", TWO, "E_z", M)
t = c(SPRING); t["TPK"]["CONSTRAINT"][0]["TARGET"] = "Z"
case("case/target-Z", t, "E_z", I, note="TARGET Z is not the DOF z (T1 refuses it: TARGET Z is not a DOF)")
t = c(SPRING); t["TPK"]["CONSTRAINT"][0]["ACTION"] = "fix"
case("case/action-fix-lower", t, "E_z", I,
     note="only ACTION FIX counts (T1 refuses it: ACTION fix is not REMOVE or FIX)")

# ============================================================================
# F. the QUANTITY's shape: an identifier, never echoed otherwise
# ============================================================================
QI = R("QUANTITY is not an identifier")
case("quantity/integer", SPRING, 5, QI)
case("quantity/null", SPRING, None, QI)
case("quantity/empty", SPRING, "", QI)
case("quantity/space", SPRING, "E z", QI)
case("quantity/zero-width", SPRING, "E_z​", QI, note="a hidden character: refused, not echoed")
case("quantity/trailing-newline", SPRING, "E_z\n", QI)
case("quantity/leading-digit", SPRING, "1E", QI)
case("quantity/non-ascii", SPRING, "É_z", QI, note="NAMEs are ASCII identifiers")
case("quantity/list", SPRING, ["E_z"], QI)
case("quantity/first", "x", 5, QI, note="the QUANTITY is read before the TFK")

# ============================================================================
# G. the TFK's shape: what sr3_scope reads is the TrM ENTRIES (each entry's NAME
#    and DOF) and the TPK CONSTRAINT kind (each constraint's TARGET and ACTION)
# ============================================================================
case("tfk/string", "x", "E_z", R("TFK is not an object"))
case("tfk/null", None, "E_z", R("TFK is not an object"))
case("tfk/list", [1], "E_z", R("TFK is not an object"))
case("tfk/integer", 7, "E_z", R("TFK is not an object"))
case("tfk/empty-object", {}, "E_z", R("TrM: zone is not an object"))
t = c(SPRING); del t["TrM"]
case("trm/missing", t, "E_z", R("TrM: zone is not an object"))
t = c(SPRING); t["TrM"] = "E_z"
case("trm/string", t, "E_z", R("TrM: zone is not an object"))
t = c(SPRING); del t["TrM"]["ENTRIES"]
case("entries/missing", t, "E_z", R("TrM: missing part ENTRIES"))
t = c(SPRING); t["TrM"]["ENTRIES"] = "E_z"
case("entries/string", t, "E_z", R("TrM ENTRIES is not a list"))
t = c(SPRING); t["TrM"]["ENTRIES"] = {"E_z": c(TRM_F["ENTRIES"][entry_index(SPRING, "E_z")])}
case("entries/object", t, "E_z", R("TrM ENTRIES is not a list"))
t = c(SPRING); t["TrM"]["ENTRIES"] = None
case("entries/null", t, "E_z", R("TrM ENTRIES is not a list"))
t = c(SPRING); t["TrM"]["ENTRIES"].append("E_w")
case("entry/string", t, "E_z", R("TrM ENTRIES: entry is not an object"))
t = c(SPRING); t["TrM"]["ENTRIES"].append(["E_w"])
case("entry/list", t, "E_z", R("TrM ENTRIES: entry is not an object"))
t = c(SPRING); del t["TrM"]["ENTRIES"][0]["NAME"]
case("entry/name-missing", t, "E_z", R("ENTRY:?: missing field NAME"))
t = c(SPRING); t["TrM"]["ENTRIES"][0]["NAME"] = 5
case("entry/name-integer", t, "E_z", R("ENTRY:?: field NAME has the wrong type"))
t = c(SPRING); t["TrM"]["ENTRIES"][0]["NAME"] = None
case("entry/name-null", t, "E_z", R("ENTRY:?: field NAME has the wrong type"))
t = c(SPRING); del t["TrM"]["ENTRIES"][entry_index(SPRING, "E_z")]["DOF"]
case("entry/dof-missing", t, "E_z", R("ENTRY:E_z: missing field DOF"))
t = c(SPRING); t["TrM"]["ENTRIES"][entry_index(SPRING, "E_z")]["DOF"] = ["z"]
case("entry/dof-list", t, "E_z", R("ENTRY:E_z: field DOF has the wrong type"))
t = c(SPRING); del t["TrM"]["ENTRIES"][entry_index(SPRING, "E_x")]["DOF"]
case("entry/other-dof-missing", t, "E_z", R("ENTRY:E_x: missing field DOF"),
     note="every entry's NAME and DOF is read, not only the matched entry's")
t = c(SPRING); t["TrM"]["ENTRIES"].append({"NAME": "E_w", "DOMAIN": "always"})
case("entry/after-match", t, "E_z", R("ENTRY:E_w: missing field DOF"),
     note="every entry is read before the answer, those after the matched one included")
t = c(SPRING); t["TrM"]["ENTRIES"].append({"NAME": "E‮w"})
case("entry/hidden-character-escaped", t, "E_z", R("ENTRY:E<U+202E>w: missing field DOF"),
     note="a reason that quotes input escapes disallowed code points (zone schemas 6.3)")
t = c(SPRING); del t["TPK"]
case("tpk/missing", t, "E_z", R("TPK: zone is not an object"))
t = c(SPRING); t["TPK"] = [1]
case("tpk/list", t, "E_z", R("TPK: zone is not an object"))
t = c(SPRING); del t["TPK"]["CONSTRAINT"]; s33_roles(t["TPK"])
case("constraint/kind-absent", t, "E_z", I,
     note="an absent kind holds no entry, as T1 reads a TPK: no FIX, so INSPECTION")
case("constraint/kind-empty", with_cons(SPRING), "E_z", I)
t = c(SPRING); t["TPK"]["CONSTRAINT"] = "C_NOZ"
case("constraint/kind-string", t, "E_z", R("TPK kind CONSTRAINT is not a list"))
t = c(SPRING); t["TPK"]["CONSTRAINT"] = None
case("constraint/kind-null", t, "E_z", R("TPK kind CONSTRAINT is not a list"))
t = c(SPRING); t["TPK"]["CONSTRAINT"] = ["C_NOZ"]
case("constraint/entry-string", t, "E_z", R("TPK kind CONSTRAINT: entry is not an object"))
t = c(SPRING); del t["TPK"]["CONSTRAINT"][0]["TARGET"]
case("constraint/target-missing", t, "E_z", R("CONSTRAINT:C_NOZ: missing field TARGET"))
t = c(SPRING); t["TPK"]["CONSTRAINT"][0]["ACTION"] = 1
case("constraint/action-integer", t, "E_z", R("CONSTRAINT:C_NOZ: field ACTION has the wrong type"))
t = c(SPRING); del t["TPK"]["CONSTRAINT"][0]["NAME"]; del t["TPK"]["CONSTRAINT"][0]["TARGET"]
case("constraint/unnamed-target-missing", t, "E_z", R("CONSTRAINT:?: missing field TARGET"))
t = with_cons(SPRING, C_NOZ, remove("C_R", "x")); t["TPK"]["CONSTRAINT"][1]["TARGET"] = None
case("constraint/other-target-null", t, "E_z", R("CONSTRAINT:C_R: field TARGET has the wrong type"),
     note="every constraint's TARGET and ACTION is read, a REMOVE of another DOF included")
t = c(SPRING); del t["TMS"]
case("tms/not-read", t, "E_z", M, note="the TMS is not read")
t = c(SPRING); t["TPK"] = "x"
case("order/trm-before-tpk", t, "E_w", R("QUANTITY E_w names no TrM entry"),
     note="the TrM is read, and the QUANTITY resolved, before the TPK")


# ---- 0.1.3 (C-MS-5; increment 11): a JSON {} and a JSON [] are told apart -------------
# Expectations by hand from sr3_scope's reading order (the QUANTITY, the TFK, the TrM and
# its ENTRIES, each entry; then the TPK and its CONSTRAINT kind, each constraint).
E1 = {"NAME": "E_z", "DOF": "z"}
T_OK = {"TrM": {"ENTRIES": [E1]}, "TPK": {"CONSTRAINT": [{"NAME": "C", "TARGET": "z", "ACTION": "FIX"}]}}
case("decode/control-mechanical", T_OK, "E_z", M, note="the minimal shape sr3_scope reads")
case("decode/tfk-list", [], "E_z", R("TFK is not an object"))
case("decode/trm-list", dict(T_OK, TrM=[]), "E_z", R("TrM: zone is not an object"))
case("decode/entries-object", dict(T_OK, TrM={"ENTRIES": {}}), "E_z", R("TrM ENTRIES is not a list"))
case("decode/entries-zero-keyed", dict(T_OK, TrM={"ENTRIES": {"0": E1}}), "E_z", R("TrM ENTRIES is not a list"),
     note="an object keyed 0 is not a list (it read as [E_z] and answered MECHANICAL)")
case("decode/entry-list", dict(T_OK, TrM={"ENTRIES": [[]]}), "E_z", R("TrM ENTRIES: entry is not an object"))
case("decode/tpk-list", dict(T_OK, TPK=[]), "E_z", R("TPK: zone is not an object"))
case("decode/tpk-empty-object", dict(T_OK, TPK={}), "E_z", I, note="no CONSTRAINT kind: none fixes z")
case("decode/constraints-object", dict(T_OK, TPK={"CONSTRAINT": {}}), "E_z", R("TPK kind CONSTRAINT is not a list"),
     note="it read as no constraint and answered INSPECTION")
case("decode/constraints-zero-keyed", dict(T_OK, TPK={"CONSTRAINT": {"0": T_OK["TPK"]["CONSTRAINT"][0]}}), "E_z",
     R("TPK kind CONSTRAINT is not a list"))
case("decode/constraint-list", dict(T_OK, TPK={"CONSTRAINT": [[]]}), "E_z",
     R("TPK kind CONSTRAINT: entry is not an object"))


def dispatch(fn, i):
    if fn == "sr3_scope":
        return S.sr3_scope(i["tfk"], i["quantity"])
    raise KeyError(fn)


if __name__ == "__main__":
    bad = 0
    for cs in cases:
        try:
            got = S.canon(dispatch(cs["fn"], cs["input"]))
        except Exception as e:
            got = "ERROR: %s" % e
        if got != cs["expect"]:
            bad += 1
            print("MISMATCH", cs["id"], "\n  expect", cs["expect"], "\n  got   ", got)
    if bad:
        sys.exit("refusing to write: %d mismatches" % bad)
    corpus = {
        "meta": {
            "name": "tpc_sr3_scope_corpus", "version": "0.1.3",
            "status": "PROPOSED 2026-09-26 (D180.3, taken 2026-09-26; the interim of TPS 4.4.13 and 4.2.13 as staged "
                      "for revision 6, provisional numbers); awaiting the supervisor's verification",
            "conforms_tps": "0.2.0 (v15), with D180.3's texts T-C3 and T-C4 as staged (tpm_d179_d180_d181_texts 0.1.0)",
            "module": "tpc_strike 0.2.1 (proposed with TPC-PY 0.2.0; reads with tpc_zone 0.5.1's helpers, schema 0.5.0)",
            "note": ("Every expectation is hand-derived from the rule and the zone wording. The fixtures are the spring "
                     "with its forms (examples/spring_draft.json), the zone corpus's toy and the tests corpus's FIX "
                     "fixture, and one-change variants of them; test fixtures, not physics under research. Where a case "
                     "is reachable only before the final pass its note says so. Both residences MUST reproduce every "
                     "expect. 0.1.1 (schema 0.5.0; D176.1, D177.1): every fixture LAW carries a ROLE chosen by its "
                     "physics (N2 EOM(v), KIN EOM(x), FREE_Z EOM(z)); sr3_scope reads no ROLE, so no expectation "
                     "changed, and every final-pass status a note states holds under schema 0.5.0. 0.1.2 (S33, "
                     "2026-09-26): a LAW restating the motion of a FIXed or REMOVEd DOF carries ROLE CONSTRAINT "
                     "(FREE_Z under C_NOZ; N2 and KIN where v or x is FIXed); a variant whose constraints leave z "
                     "free gives FREE_Z EOM(z). No expectation moves. 0.1.3 (C-MS-5, increment 11, 2026-09-28): "
                     "11 decode/ cases, a JSON {} and [] and an object keyed 0 read as written in both residences; "
                     "no earlier expectation moves.")
        },
        "cases": cases,
    }
    open(os.path.join(HERE, "vectors", "sr3_scope_corpus.json"), "w", encoding="utf-8", newline="\n").write(
        json.dumps(corpus, indent=1, ensure_ascii=True) + "\n")
    print("wrote", len(cases), "sr3_scope cases; all match hand-derived expectations")