Skip to content

Latest commit

 

History

History
870 lines (727 loc) · 44.3 KB

File metadata and controls

870 lines (727 loc) · 44.3 KB
title Nomos observed-scene presentation epoch (R2)
status Owner-authorized contract; implementation requires a separate issue
contract_revision 1
date 2026-08-27
authority docs/decisions/0023-observed-scene-presentation-epoch.md
authority_issue 191
contract_issue 193
baseline_commit 7c0d2e1e1ec2239179e23ff294d3ab29d50ba088
baseline_tree c50720762ec0221e2bed2783f1594930a56834fd
r1_contract RUNTIME.md revision 4
r1_contract_sha256 dd6f4b2ce48557f48df61d50cdc25b4ebaf0904331f4fd78d804e3af536db593
gap_fixture_sha256 9b809bee523c9be04b26c6ab08412f96f9ad4f446a50a83824961dc8be016449
authorized_candidate_commit e719bc2aeb04bb89b74a99f730b3cae9fd51ee6c
authorized_candidate_tree 06102e5cf028529b9aafb560e42110f55efb4afd
owner_authorized_date 2026-08-27

Nomos observed-scene presentation epoch (R2)

1. Contract authority and stop line

Decision 0023 opens one narrow R2 epoch and requires this separately reviewed contract before implementation. The owner authorized revision 1 exactly at the candidate commit and tree recorded above. Implementation remains ordered and must begin from the separately falsifiable issues required by section 11; this contract alone adds no crate, schema, dependency, application, artifact, or fixture to the accepted tree.

This contract answers one question: can Nomos receive a bounded already-resolved scene observation, preserve its supplied facts without becoming a second gameplay authority, compile only finite presentation consequences, and render two coherent generic scenes through an isolated offline browser consumer?

R2 is not an extension of Nomos's authoritative play runtime. It accepts an observation after another authority has resolved it. It never decides movement, traversal, collision, visibility, hostility, protection, life state, control, action legality, or action execution. A field supplied as data stays data.

R1 remains accepted and closed under RUNTIME.md revision 4. KERNEL.md, every Gate K verdict, every R1 schema and artifact, nomos-render-plan, nomos-play, and apps/nomos-viewer/ retain their exact recorded meanings. R2 evidence cannot relabel an R1 result or satisfy an adopting game's gate.

2. Acceptance criteria

R2 passes only when one combined candidate satisfies all four criteria. A red criterion makes R2 incomplete; evidence from another criterion is not partial credit.

  1. The observed-scene boundary is finite, strict, and reproducible. One isolated compiler accepts nomos.observed_scene@1, emits canonical nomos.observed_scene_plan@1, refuses every undeclared field or value, and produces byte-identical output for the same input bytes. Its accepted implementation is limited to the exact grammar, bounds, mappings, command, diagnostics, and workspace edges in this contract.
  2. Supplied facts cross without semantic loss or recomputation. Every terrain role, cell, actor fact, life state, and action availability is preserved field for field. Mutation and independence tests prove that no fact is defaulted, dropped, renamed into another meaning, inferred from another field, or recomputed. Distinct consequential inputs produce the distinct finite compiled consequences section 6 declares.
  3. An isolated offline viewer proves the consequences and generalizes once. apps/nomos-observed-viewer/ consumes only the compiled R2 plan and its own renderer catalog, fetches nothing at runtime, and proves every consequence in a scene-graph test and an actual-play-size browser frame. A second scene, independently authored from the frozen packet, compiles and renders without any compiler, decoder, catalog, renderer, UI, build, or smoke-source edit.
  4. The combined candidate is reproducible and stays inside measured ceilings. A clean network-isolated checkout passes the complete R1 and R2 proof, reproduces the canonical fixtures and built public artifact, meets every section 9 ceiling, and receives an exact-head non-author rerun. The owner then explicitly accepts or rejects R2; green proof alone does not admit it.

No criterion is satisfied by experiments/observed-scene-gap/. That experiment is the failing evidence and comparison input, not accepted source or a fixture to copy into the implementation.

3. Exact baseline and evidence boundary

The contract baseline is decision 0023's merge commit 7c0d2e1e1ec2239179e23ff294d3ab29d50ba088, tree c50720762ec0221e2bed2783f1594930a56834fd. The unchanged R1 contract is RUNTIME.md revision 4, SHA-256 dd6f4b2ce48557f48df61d50cdc25b4ebaf0904331f4fd78d804e3af536db593.

Issue #188 admitted R1 presentation commit 5e0e44cc912b57a1d29cc3e722497c16cf9a1797, tree 7e606bde9f91307483307c4af1e0764d81df5c72. Issue #189's generic failing fixture is 1,464 bytes, SHA-256 9b809bee523c9be04b26c6ab08412f96f9ad4f446a50a83824961dc8be016449. Its four exact RP0202 refusals prove carrier absence at R1; they do not specify R2 implementation.

The representative adopter observation and target remain external evidence. No adopter frame, schema, identity, coordinate selection, palette, image, prose, mechanic, or governance document becomes an R2 source file. R2 fixtures are new generic documents authored against this contract.

4. Ownership and workspace boundary

R2 adds one Rust workspace member and one non-workspace browser application.

nomos-observed-scene

The new crate lives at crates/nomos-observed-scene/, with library and the nomos-observed-scene binary. It depends on nomos-core only and has no third-party dependency. It owns exactly two schema identities:

  • nomos.observed_scene@1 at crates/nomos-observed-scene/src/input.rs; and
  • nomos.observed_scene_plan@1 at crates/nomos-observed-scene/src/plan.rs.

The identities are registered in a new docs/evaluation/R2_SCHEMA_OWNERSHIP.md. It contains exactly two inventory rows, each naming the identity, owner crate, exact owner file, authoritative type set, encoder, strict reader, persisted boundary, and consumers. docs/evaluation/r2-schema-ownership.sh reads that table literally and fails unless each identity has exactly one declaration at its recorded file, neither identity occurs as a declaration elsewhere, no third R2 identity is declared, and neither identity duplicates the Gate K or R1 registers. The checker records the register and owner-file SHA-256 values. Gate K's frozen register and docs/evaluation/R1_SCHEMA_OWNERSHIP.md do not change.

Root Cargo.toml declares the member. xtask gains an explicit R2_CRATES declaration and a closed direct/dev/build/transitive dependency allowlist: nomos-observed-scene -> nomos-core, with no other edge. Planted tests prove membership, the sole permitted edge, external-dependency refusal in every dependency class, cycle refusal, kernel-to-R2 refusal, R1-to-R2 refusal, and undeclared-member refusal. No kernel crate or R1 member depends on the R2 crate. The graph remains acyclic.

apps/nomos-observed-viewer/

The browser consumer lives under apps/nomos-observed-viewer/ and is not a Cargo workspace member. It may stage the already admitted Three.js files from apps/nomos-viewer/vendor/three/ after rechecking their recorded digests and MIT license. It imports no source module from apps/nomos-viewer/, and the R1 viewer imports nothing from it.

The application's only scene-content input is nomos.observed_scene_plan@1; it additionally reads its renderer-owned catalog and non-content build integrity index. It does not read .nomos, Canonical World IR, compiler receipts, R1 rendering plans, R1 play state, the R2 input document, or an adopter payload. Its built distribution is self-contained.

apps/nomos-observed-viewer/SOURCE_MANIFEST is a sorted <sha256><two spaces><repository-relative path> allowlist for every regular application source, test, smoke, build, HTML, and CSS file beneath that app; the manifest excludes only itself and generated dist/ trees. Its test refuses a missing, extra, symlinked, non-regular, or digest-mismatched entry. The separately located Three.js files remain bound by the accepted R1 vendor manifest. No .npmrc, package.json, npm lockfile, generated bundle, or unmanifested JavaScript is admitted.

R2 source provenance

docs/evaluation/R2_SOURCE_PROVENANCE.md inventories every regular file under crates/nomos-observed-scene/, apps/nomos-observed-viewer/ outside generated dist/, fixtures/r2/, and the R2-specific evaluation and committed visual- evidence paths, plus the three referenced R1 vendor files. Each row contains exact path, SHA-256, origin class, producing receipt, and license disposition. The closed origin classes are:

  • r2_authored: source produced from a detached Nomos issue packet whose receipt binds base commit/tree, contract and issue-acceptance digests, author, commands, and the complete consulted-input list, with no adopter input;
  • r1_vendor_reuse: only the exact Three.js module, core, and MIT license bytes already recorded by R1;
  • compiler_produced: an expected plan bound to its canonical R2 input and compiler commit/tree; and
  • browser_produced: a screenshot/contact sheet bound to plan, catalog, browser, viewport, and smoke receipt.

docs/evaluation/r2-source-provenance.sh refuses a missing, extra, symlinked, non-regular, digest-mismatched, unknown-origin, unlicensed, or receipt-dangling row. The register and provenance receipts are control evidence bound by the final commit/tree and final receipt rather than recursively inventorying themselves. Every R2 implementation author receipt states whether an external input was consulted; any adopter repository, payload, target frame, palette, asset, prose, schema, coordinate set, or mechanic is a stop, not an admissible origin. The blacklist in section 9 is defense in depth, not the provenance claim.

Fact ownership

Fact class Sole owner R2 responsibility
scene, layer, actor, and action identity within one observation supplying observer validate and preserve; never correlate across observations
crop and layer cells supplying observer validate integer bounds; preserve
terrain role supplying observer preserve; select the exact presentation mapping in section 6
actor life state and controlled/hostile/protected facts supplying observer preserve independently; never infer one from another
action target and availability supplying observer preserve; never decide legality or execute
plan schema, source digest, canonical order, and finite presentation selections R2 compiler derive exactly section 6 and nothing else
camera, geometry definitions, materials, colours, lights, and UI marks R2 renderer catalog define final presentation consequences; consume no gameplay source
pixels R2 renderer draw only the render view and catalog defined in section 8

“Supplying observer” is a boundary role, not a Nomos implementation. Building a live adopter adapter is outside R2.

5. nomos.observed_scene@1

The input is canonical JSON under nomos_core::CanonicalValue. It contains exactly the top-level fields below and no others. This expanded JSON is field-set notation, not accepted fixture bytes: whitespace is shown for readability and the empty collection placeholders deliberately do not satisfy the lower bounds that follow.

{
  "actions": [],
  "actors": [],
  "crop": {"height": 1, "width": 1},
  "scene": {"id": "scene_id"},
  "schema": "nomos.observed_scene@1",
  "terrain_layers": []
}

Canonical object-field ordering follows nomos-core. Arrays carry the semantic ordering below; a decoder refuses rather than silently sorting a noncanonical input. Duplicate keys are refused at every object depth before semantic validation. Each object has exactly this key set and no other:

Object Exact keys Value types
document actions, actors, crop, scene, schema, terrain_layers arrays, objects, and the exact schema string as shown above
crop height, width integers
scene id identity string
terrain layer cells, id, role cell array and strings
terrain cell x, y integers
actor cell, controlled, hostile, id, life_state, protected actor-cell object, booleans, and strings
actor cell x, y, z integers
action availability, id, target_actor strings

Missing, repeated, additional, null, or wrong-typed values are never defaulted or coerced.

Identities and bounds

Every scene, layer, actor, and action identity is ASCII matching [a-z][a-z0-9_]{0,63}. An identity is local to this one document. It conveys no persistence, network, ownership, cross-frame, or command authority.

  • crop.width and crop.height: integers in 1..=32.
  • terrain_layers: 3..=8 rows, strictly ascending by id.
  • total layer-cell assignments across all layers: 3..=4096.
  • actors: 1..=64 rows, strictly ascending by id.
  • actions: 0..=128 rows, strictly ascending by id.
  • every collection identity is unique within its collection.

No input number is fractional, exponential, negative zero, or outside nomos-core's integer profile. There is no clock, duration, random seed, transform, quaternion, scale, subcell coordinate, camera value, colour, material, assembly, shader, image, prose, or generic parameter.

Terrain layer row

{
  "cells": [{"x": 0, "y": 0}],
  "id": "ground_layer",
  "role": "calm_ground"
}

role is exactly one of:

  • calm_ground;
  • traversable_route; or
  • structure_footprint.

Each scene carries at least one layer of each role. A layer contains 1..=1024 cells. A cell has exactly integer x and y, satisfies 0 <= x < crop.width and 0 <= y < crop.height, and appears at most once in that layer. Cells are strictly row-major: ascending by y, then x. The same cell may appear in multiple layers; that overlap is load-bearing input and is never resolved into one “winning” terrain fact.

traversable_route is a supplied presentation observation. It does not assert that Nomos permits traversal. structure_footprint is not collision or a masonry mass. calm_ground is not an audio, encounter, or safety rule.

Actor row

{
  "cell": {"x": 0, "y": 0, "z": 0},
  "controlled": false,
  "hostile": false,
  "id": "actor_id",
  "life_state": "living",
  "protected": false
}

cell.x and cell.y satisfy 0 <= x < crop.width and 0 <= y < crop.height. cell.z is exactly 0; R2 admits no elevation or subcell placement. life_state is exactly living | dead. controlled, hostile, and protected are independent booleans. Each of the eight boolean combinations is legal for either life state; the decoder enforces no implication and no “one controlled actor” gameplay rule. Multiple actors may share a cell because the observation, not Nomos, owns that fact.

Action row

{
  "availability": "enabled",
  "id": "action_id",
  "target_actor": "actor_id"
}

target_actor names an actor in this document. availability is exactly enabled | disabled. The action identity is opaque: no compiler or viewer code dispatches on its spelling, recases it into prose, or treats it as a command. R2 carries no action argument, verb taxonomy, legality reason, callback, or execution endpoint.

6. nomos.observed_scene_plan@1

The compiler emits one canonical document with exactly the top-level fields below. As in section 5, this is expanded field-set notation rather than valid artifact bytes; the collection and digest placeholders are intentionally incomplete.

{
  "actions": [],
  "actors": [],
  "crop": {"height": 1, "width": 1},
  "scene": {"id": "scene_id"},
  "schema": "nomos.observed_scene_plan@1",
  "source_sha256": "64 lowercase hexadecimal characters",
  "terrain_layers": []
}

source_sha256 matches [0-9a-f]{64} and hashes the exact canonical input bytes. Crop, scene id, all identities, cells, roles, actor fields, action targets, availability, and array orders are value-for-value copies with unchanged spelling under the successor schema. The output has exactly the same crop, layer, cell, actor, and action cardinalities and revalidates every section 5 bound, uniqueness rule, ordering rule, and cross-reference. Duplicate keys are refused at every depth. The plan adds only the finite selections below.

Terrain plan row

An input layer row gains exactly assembly, material_family, and stack, so its complete key set is assembly, cells, id, material_family, role, and stack. Its nested cell shape remains exactly x, y:

role assembly material_family stack
calm_ground terrain/calm_ground ground_muted 0
traversable_route terrain/traversable_route route_worn 10
structure_footprint terrain/structure_footprint structure_stone 20

stack is a draw-order integer, not elevation, collision, or role precedence. All overlapping rows survive. The plan retains input ID order for preservation, but the render view orders layers by stack and then canonical cell-array bytes, never by identity spelling; exact render duplicates may retain source ordinal because they are visually identical. The renderer may offset coincident surfaces by a catalog-owned fixed visual epsilon; neither input nor plan carries that float.

Actor plan row

An input actor row gains exactly assembly, pose, controlled_marker, hostile_outline, and protection_ring. Its complete key set is assembly, cell, controlled, controlled_marker, hostile, hostile_outline, id, life_state, pose, protected, and protection_ring; its nested actor cell remains exactly x, y, z. Every supplied field remains unchanged. The compiler maps each finite fact independently:

input compiled field compiled value
either life state assembly actor/observed_figure
life_state: living pose upright_living
life_state: dead pose prone_dead
controlled: true controlled_marker present
controlled: false controlled_marker absent
hostile: true hostile_outline present
hostile: false hostile_outline absent
protected: true protection_ring present
protected: false protection_ring absent

These compiled consequences compose. No flag suppresses, implies, or rewrites another. The strict plan reader refuses a row in which a copied fact and its compiled selection disagree.

Action plan row

An input action row gains exactly marker, so its complete key set is availability, id, marker, and target_actor:

availability marker
enabled action/enabled
disabled action/disabled

The plan carries no label or execution address. The marker is presentation-only.

No other plan field, variant, assembly, material family, marker, opaque tag, untyped parameter, arbitrary geometry name, pass-through payload, or extension slot exists in revision 1.

7. Compiler command, diagnostics, and immutability

The exact command is:

nomos-observed-scene compile --input <scene.json> --out <plan.json>

Flags are required once in that order. Unknown, repeated, missing, reordered, or non-UTF-8 arguments are usage errors. The binary exposes only help and compile; it does not read stdin or infer defaults.

nomos-observed-scene help exits 0, writes exactly the following two lines plus one final LF to stdout, and writes no stderr:

usage: nomos-observed-scene compile --input <scene.json> --out <plan.json>
       nomos-observed-scene help

A successful compile exits 0 and writes no stdout or stderr. A failed command publishes no output, writes no stderr, and writes exactly one canonical rejection envelope plus LF to stdout:

{"diagnostics":[{"code":"OS0001","message":"human-facing text","repairs":[]}],"status":"rejected"}

The R2 crate owns its disjoint OS#### code type and encodes this unpersisted, non-schema envelope with nomos_core::CanonicalValue; it does not mint a frozen EK#### code or declare a third R2 schema. message wording is not contractual. repairs uses only existing nomos-core repair spellings in sorted, duplicate-free order. A span, when source bytes permit one, has exactly byte_end, byte_start, column, line, and repository-relative path; no other diagnostic field exists.

The stable diagnostic set is:

Code Exit Meaning
OS0001 2 invalid command or argument grammar
OS0101 3 input unreadable, symlinked, or not one regular file
OS0102 1 malformed UTF-8 or JSON
OS0103 1 noncanonical input bytes or required array order
OS0104 1 missing or mismatched schema identity/version
OS0201 1 missing, unknown, repeated, or wrong-typed field
OS0202 1 crop, count, integer, cell, or collection bound violated
OS0203 1 invalid or duplicate scene-local identity
OS0204 1 dangling action target
OS0301 1 output exists, aliases input, or traverses a symlink
OS0302 3 environment staging, write, sync, or publication failure

Validation returns one error by this precedence: argument grammar; input path safety/type/readability; output alias/symlink/existence; UTF-8/JSON; canonical object bytes; schema; exact field sets and types; value and collection bounds; identity shape and uniqueness; required array order; action references; output publication. Within one phase it walks canonical document paths lexicographically and reports the first. Planted multi-fault tests freeze this precedence and exercise every code. Diagnostics name a repository-relative or argument-relative path, never an absolute machine path.

The compiler opens the input read-only, rejects symlinked input and output roots, refuses an existing destination, stages a new output beside its final path, reopens and verifies the staged canonical bytes, atomically publishes, and removes staging on every failure. It never edits the input or an existing artifact in place.

8. Offline browser consumer and two-scene proof

The R2 viewer has one fixed renderer-owned catalog. Input and plan files select no camera, palette, material, geometry, light, animation, UI layout, or prose. Before R2-2 implementation begins, that issue's acceptance freezes the exact catalog table: every geometry dimension, material colour and parameter, light, camera constant, UI dimension, visual epsilon, and sparse-variation constant, with no catch-all key. src/catalog.mjs is the sole owner of those values. Its source SHA-256 is recorded in the frozen second-scene packet, ARTIFACTS.sha256, both build receipts, and final proof. A changed catalog is a changed candidate even when plan bytes do not move.

The mechanically fixed camera is orthographic isometric: plan (x,y,z) maps to Three.js (x,z,y); camera azimuth is 45 degrees, elevation is atan(1/sqrt(2)), up is (0,1,0), and the target is the crop midpoint. At 1280×720, crop bounds use outer tile corners with x in [-0.5,width-0.5] and y in [-0.5,height-0.5]; its frustum is their transformed bounds plus exactly one cell of margin, expanded on only the shorter projected axis to preserve viewport aspect. Catalog tests project every outer corner and prove the margin and absence of perspective. Terrain variation may use only the fixed integer predicate (17*x + 31*y + stack) mod 16 == 0; selected cells receive one unrotated accent patch, and all others receive none. There is no noise, curve, spiral, source seed, or clock. “Restrained” and “coherent” remain owner visual judgments, not substitutes for those mechanical rules.

The catalog defines one geometry/material entry for each section 6 assembly/family, the two actor poses, each present marker/outline/ring, and both action markers. There is no R2 transition or animation. Catalog values are renderer authority, not content fields and not an adopter palette.

The strict JavaScript decoder repeats the complete plan grammar. It first validates all copied facts and their compiled selections, then returns a frozen render-only view with exactly:

  • crop;
  • terrain rows containing only cells, assembly, material_family, and stack;
  • actor rows containing an integer ordinal handle, cell, assembly, pose, controlled_marker, hostile_outline, and protection_ring; and
  • action rows containing only marker and the target actor's ordinal handle.

The actor handle is exactly the zero-based position of that actor in the plan's required ID-sorted actor array, including when two actors have identical non-ID tuples. It exists only to associate an action with the already-created actor node. Renderer, UI, and catalog code may not compare, display, log, hash, style, order, or select geometry/material/pose/marker behavior from its numeric value. The decoder orders render-view actor rows by canonical bytes of (cell,assembly,pose,controlled_marker,hostile_outline,protection_ring); a handle breaks a tie only between visually identical rows. Action rows order by the target actor's complete canonical visual tuple and then marker; a handle may break a tie only when the complete action presentation is visually identical. Action markers are siblings in one action group, positioned from the target node's resolved anchor; they are never children whose graph position exposes which identical actor handle supplied that anchor. Tests cover duplicate actor tuples with different markers and prove that renaming IDs while updating their references changes no scene-graph property other than private association keys.

Scene, layer, actor, and action identity spellings, terrain role, actor life_state and booleans, action availability, schema, and source_sha256 do not cross that render-view boundary. The renderer, UI, and catalog modules receive only the render view. A throwing-proxy test and source-token sweep fail if those modules read or name any excluded raw field or use a handle outside target association; only the decoder may map actor IDs to ordinal handles and verify action references. Rendering therefore cannot recompute or override a compiler-owned selection.

Before decoding, the loader verifies fetched plan bytes against the generated integrity index. It does not claim to rederive source_sha256 after the source input has been excluded. The decoder never repairs or reinterprets a plan and returns the first error by the compiler's section 7 schema/field/value/identity/ order/reference precedence. Its non-schema error object has exactly artifact, code, message, and path; message is non-contractual, while artifact and path are distribution-relative. path is $ for a whole-artifact failure and otherwise a canonical JSON path. The complete stable codes are:

Code Meaning
OV0101 integrity index missing/malformed or fetched bytes do not match its length/digest
OV0102 plan is not UTF-8 or well-formed JSON
OV0103 plan bytes are outside the canonical JSON byte profile
OV0104 plan schema or source-digest spelling is invalid
OV0201 missing, unknown, repeated, wrong-typed, or unknown-enum field
OV0202 integer, crop, count, cell, identity, uniqueness, or order bound violated
OV0203 action target is dangling
OV0204 copied fact and compiled selection disagree

Direct decoder tests bypass integrity only to plant each semantic mutation; runtime loading never does. Multi-fault tests freeze precedence and every code. No routinely edited application file may exceed 1,000 lines.

The scene-graph test, using an injected Three.js recording stand-in, proves:

  • one common cell covered by three distinct-role layers produces the exact three assemblies, material families, and stacks from section 6, and all coincident layers survive in deterministic order;
  • living/dead, controlled, hostile, and protected compiled selections each change only their declared node and all positive consequences coexist;
  • enabled and disabled action markers differ and remain on the supplied target;
  • actor and layer placement is the direct integer lattice mapping; and
  • no node, material, light, camera, or UI mark is created from a raw fact, identity spelling, or undeclared plan value.

The browser smoke runs at 1280×720, requires WebGL2, loads the built artifact, renders both scenes at actual play size, fails on any console error, exception, external request, digest mismatch, or missing consequence, captures one frame per scene, and closes every browser/CDP/server process within 2 seconds after the result is known. Screenshot bytes and GPU pixels are recorded, not compared for identity. For timing, the smoke records process.hrtime.bigint() immediately before Page.navigate. The app invokes a preinstalled CDP runtime binding in the first requestAnimationFrame callback after renderer.render returns; its receipt names the plan digest, viewport, and consequence counts. The matching CDP binding event ends the sample. Each launch uses a new temporary browser profile and Network.setCacheDisabled(true).

The two proof inputs live at fixtures/r2/scenes/scene_one.json and fixtures/r2/scenes/scene_two.json; compiler-produced expected plans live at the matching paths beneath fixtures/r2/plans/. Before the second scene is authored, docs/evaluation/r2-second-scene-packet/MANIFEST.sha256 freezes the exact detached commit/tree and a sorted <sha256><two spaces><packet-relative path> inventory containing this contract, author task, schema register, release compiler, decoder, catalog, renderer, UI, build, smoke harness, source/public manifests, first scene, first expected plan, and proof commands. The packet manifest excludes only itself; delivery tooling fixes its SHA-256 before the author begins, and the later receipt records that delivered digest.

A person or agent who authored none of the compiler, decoder, catalog, renderer, UI, build, smoke harness, or first scene receives only that packet. Their receipt records author identity/model, packet digest, detached commit and tree, commands, touched files, first and second semantic-signature digests, and an attestation that no adopter payload was consulted or copied. Their change may touch only the new scene, its compiler-produced expected plan and digests, contact-sheet evidence, and author receipt. Any other edit fails criterion 3.

Both committed scenes contain at least one cell shared by layers of all three distinct terrain roles, both life states, one actor for each positive boolean whose other two booleans are false, one actor on which all three positive booleans coexist, and both action availabilities. Proof-scene actors must have unique tuples of (cell,life_state,controlled,hostile,protected).

docs/evaluation/r2-scene-signature.mjs erases scene/layer/action IDs; replaces each actor ID by the ordinal of its unique actor tuple after canonical tuple sort; sorts terrain rows as (role,cells) and actions as (availability,target_actor_ordinal); retains crop and every non-identity fact; and hashes the canonical normalized result. The second scene is refused if its signature equals the first. It must differ independently in all four recorded axes: crop dimensions, normalized terrain rows, normalized actor tuples, and normalized action target/availability rows. An ID rename, array permutation, or semantic copy therefore cannot count as generalization.

The owner judges the two-frame contact sheet at its recorded 1280×720 size as accepted or rejected. Acceptance means the finite grammar produces coherent, readable members of one visual family; it is not a production-art or adopter- target verdict.

apps/nomos-observed-viewer/PUBLIC_FILES is the exact sorted subset of source-manifest paths the build may stage. The built distribution contains exactly those HTML/CSS/JavaScript files; generated ARTIFACTS.sha256; the two compiled plans; and the accepted Three.js module, core, and MIT license bytes. The UTF-8 integrity index has one final LF and exactly three tab-separated lines: catalog_sha256<TAB><catalog-digest>, then one <plan-sha256><TAB><byte-count><TAB><relative-plan-path> row per plan in path order. It is build metadata, not a schema or content input. The distribution scanner compares this closed path set, source/vendor/integrity digests, and regular-file types exactly.

The distribution contains no R2 input scene, .nomos source, World IR, compiler receipt, credential, absolute path, source map, adopter payload, external origin, unmanifested code, or symlink. The build receipt lives outside the distribution and binds every staged file. Building twice from the same checkout is byte-identical.

9. Proof and measured ceilings

Every implementation issue runs the four accepted workspace commands:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
cargo xtask boundary

Every R2 implementation issue updates the per-file provenance register and runs docs/evaluation/r2-source-provenance.sh. The carrier/compiler issue additionally runs docs/evaluation/r2-schema-ownership.sh, planted diagnostics and precedence, canonical fixtures, mutation matrix, immutability tests, exact maximum-fixture regeneration, and ten-run byte comparison. The viewer issue runs every Node test, source/public-manifest checks, adopter-neutrality audit, semantic-signature comparison, distribution scan, two-build comparison, and required-Chromium smoke.

Before final disposition, docs/evaluation/r2-complete-proof.sh --output <empty-directory> performs from a clean standalone checkout. All required host tools are installed first; the harness then removes external network routes before step 1, permits loopback only for static smoke servers, and restores no route until every proof process has closed. It performs, in order:

  1. the four workspace commands with --locked where Cargo accepts it;
  2. the unchanged accepted R1 six-area artifact, wasm, native, viewer-test, browser-smoke, and native-replay proof;
  3. R2 schema ownership, source provenance, adopter-neutrality, maximum-fixture, and compiler tests;
  4. compilation, semantic-signature checks, and byte comparison of both committed R2 scenes;
  5. R2 viewer tests, source/public-manifest checks, distribution scan, and browser smoke;
  6. a fresh-target cargo build --workspace --release --locked --offline, a clean R1 viewer build, and both clean R2 viewer builds used for byte comparison;
  7. the budget measurements and receipt assembly; and
  8. a final clean-worktree and process-closure check.

The script runs with LC_ALL=C, refuses an output directory outside the standalone checkout, and writes only beneath that supplied empty directory and the checkout-local target/; it never edits an input or committed fixture. Its receipt binds commit, tree, toolchain, commands, environment, source/artifact digests, counts, timings, peak disk, process closure, and every result.

Compile and browser timing samples are integer monotonic-clock nanoseconds. A sorted sample set uses the arithmetic mean of its two central values for an even-count median and nearest-rank ceil(0.95 * n) for p95. Elapsed build time is /usr/bin/time -v's wall-clock value. The proof records every raw value and recomputes each summary from those values.

The maximum input is not an implementation-chosen workload. docs/evaluation/generate-r2-maximum.mjs must emit exactly 98,421 canonical bytes at fixtures/r2/maximum-observed-scene.json, SHA-256 fe332f711437dab15e4d1315cc3ca57dba6521350ff673941e77feb414585909, with no seed, clock, input, or environment branch. Its exact algorithm is:

  • crop 32×32; scene ID s plus 63 zeroes;
  • eight 64-byte layer IDs l plus 62 zeroes plus digit 0..7;
  • roles by layer ordinal: calm_ground, traversable_route, structure_footprint, calm_ground, traversable_route, structure_footprint, calm_ground, traversable_route;
  • layer ordinal i contains each row-major cell whose (x + 32*y + i) mod 2 == 0, exactly 512 cells per layer and 4,096 total;
  • 64 actors with IDs a, 61 zeroes, and two-digit ordinal; cell (i mod 32, floor(i/32), 0); life_state living when floor(i/8) mod 2 == 0 and dead otherwise; and the three booleans from bits 0, 1, and 2 of i; and
  • 128 actions with IDs q, 60 zeroes, and three-digit ordinal; target actor i mod 64; availability enabled for even i and disabled for odd i.

The generator writes keys and arrays in this contract's canonical order. Its test regenerates twice, compares both outputs and the committed fixture by bytes, and checks the stated length and digest.

Acceptance ceilings

These are ceilings, not observations or promises for every machine. They are measured on the owner reference host remi: Ubuntu x86_64, Intel i7-8700 with 12 logical CPUs, Rust 1.98.0, Node 22.22.1, and the exact headless Chromium version recorded by the final proof. A hosted rerun may supplement this measurement but does not replace or average it. The final record reports the complete environment and actual values.

Measurement Method Ceiling
clean locked release workspace build fresh target, network disabled, LC_ALL=C /usr/bin/time -v cargo build --workspace --release --locked --offline 60 s
peak standalone checkout including .git, local target, and proof outputs from proof start through process closure, sample du -sm on that checkout every 100 ms and retain the maximum; the output directory must be inside the checkout 8,192 MiB
maximum-scene compile latency the exact committed maximum fixture above; prebuilt release binary; 10 unrecorded process warmups, then 100 new processes from spawn through synced atomic publication to unique nonexistent output paths on one filesystem; warm OS file cache is retained, output is never reused, and all raw samples and outputs are retained median 50 ms, p95 100 ms
complete observed-viewer distribution sum of regular-file bytes after scan 2,000,000 bytes
browser navigation to first completed WebGL frame exact CDP interval in section 8; 10 fresh-profile, cache-disabled browser launches per committed scene p95 5,000 ms separately for each scene and for all 20 samples combined

A budget miss is red evidence. Changing a ceiling or its method requires the contract-repair process; a slow runner is recorded, not silently excluded.

The complete proof must pass with external network routes removed. Loopback is permitted only for the static smoke server. Every dependency needed after route removal is already committed, vendored, or installed and digest-recorded as a host proof tool.

docs/evaluation/r2-adopter-neutrality.sh scans the R2 crate, app, manifests, and fixtures and refuses the case-folded project spellings the-mortal-estate, mortal_estate, and cairn, as well as the external frame and projection digests recorded by decision 0023. The per-file provenance register, implementation-author receipts, second-author packet receipt, and final source review record that no adopter identity, coordinate, palette, prose, asset, schema, mechanic, or consulted payload is present. The blacklist audit is evidence of the declared boundary, not authority over an adopter repository.

10. Dependency policy

The six kernel crates stay dependency-free. nomos-observed-scene depends only on nomos-core; this contract admits no Rust, npm, or browser dependency. cargo xtask boundary inspects normal, target-specific, optional, dev, build, and transitive Cargo metadata and the manifest text; an R2 edge outside the sole allowlist fails even if unused or absent from the lockfile. Node and Chromium are recorded host proof tools, not shipped dependencies.

R2 reuses Three.js 0.185.1 exactly as recorded by RUNTIME.md revision 4: vendored module bytes, manifest digests, and MIT license remain at their accepted paths. The R2 build re-verifies and stages those bytes; it does not run npm install, follow a CDN, or create a second unrecorded copy in source. The built distribution includes the accepted MIT license beside the two module files. SOURCE_MANIFEST and PUBLIC_FILES fail closed on every other app source or staged code path.

Any later dependency requires a contract repair or later epoch decision naming its exact version, provenance, content digest, license path, reason a local implementation is worse, authoritative-determinism effect, and offline proof.

11. First-target order and final disposition

After this contract is owner-authorized, work proceeds in dependency order:

  1. R2-1 strict carrier and compiler. New issue; crate, schemas, ownership register, boundary enforcement, canonical fixtures, mutation matrix, diagnostics, immutability, determinism, and measured compile latency. No browser application in this slice.
  2. R2-2 offline consumer and second scene. New issue after R2-1 lands; isolated application, strict decoder, catalog, renderer, scene-graph tests, first actual browser frame, frozen packet, independent second scene, contact sheet, offline build, and viewer budgets. No adopter integration.
  3. R2 final evidence and disposition. New issue after R2-2 lands; one bound combined candidate, complete network-isolated proof, refreshed ceilings, exact-head non-author rerun, owner visual verdict, and owner R2 verdict.

Each target has falsifiable issue acceptance and a non-author exact-head rerun. Nothing is called green from its author's proof alone.

The final owner dispositions are:

  1. accept R2 as an admitted Nomos observed-scene presentation result;
  2. repair this contract explicitly and rerun affected evidence; or
  3. stop R2.

Even disposition 1 is not game adoption. An adopter may consume only the exact admitted commit, tree, schemas, and artifacts through its own reviewed mapping and decision.

12. Stop conditions and non-goals

Stop for owner disposition on any:

  • presenter derivation or override of a supplied gameplay fact;
  • field dropped, defaulted, coupled to another field, or renamed into a different meaning;
  • adopter identity, schema, mechanic, coordinate selection, palette, prose, image, or governance entering accepted Nomos source;
  • arbitrary payload, unbounded vocabulary, opaque tag, generic parameter, source-authored transform, shader, or final pixel;
  • R1 schema, artifact, command behavior, contract, or viewer change presented as compatible evidence;
  • new dependency or workspace edge not admitted here;
  • second scene requiring compiler, decoder, catalog, renderer, UI, build, or smoke-source edits;
  • renderer/UI access to copied raw facts or identity spellings instead of the render-only compiled view;
  • a second scene that is ID/permutation-equivalent, misses a distinct-role three-layer overlap, or fails any required semantic-difference axis;
  • an unmanifested application/public file, catalog digest drift, or omitted dependency license;
  • red required proof, non-reproducible canonical/public artifact, leaked process, external runtime request, or budget miss; or
  • evidence that the boundary is adopter-specific or requires authoritative gameplay runtime, live networking, persistence, or a platform decision.

R2 does not provide or authorize movement, collision, navigation, visibility, combat, damage, inventory, dialogue resolution, audio, networking, replication, save migration, editor tooling, plugins, a general scene format, a live adapter, Godot integration, production deployment, or public-player admission.

The fixed browser consumer is evidence, not a production-platform verdict. Godot comparison remains separately conditional under decision 0022.

13. Contract repair

An ambiguity, contradiction, impossible requirement, or falsified assumption is recorded rather than reinterpreted. Repair requires an owner-authorized decision record with the prior wording, replacement wording, reason, effect on existing evidence, owner disposition, and new contract revision. A criterion or ceiling is never weakened merely because implementation failed it.

14. Owner disposition

Authorized. On 2026-08-27, the owner replied Authorized to the explicit request to authorize this contract at commit e719bc2aeb04bb89b74a99f730b3cae9fd51ee6c, tree 06102e5cf028529b9aafb560e42110f55efb4afd. That disposition authorizes revision 1 exactly as reviewed. This paragraph and the matching front matter record the disposition; they do not alter its requirements.

R2-1 may now begin only from its own falsifiable issue. No implementation is admitted until that slice passes its contract proof, receives its exact-head non-author rerun, and receives the owner's merge disposition.