| 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 |
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.
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.
- The observed-scene boundary is finite, strict, and reproducible. One
isolated compiler accepts
nomos.observed_scene@1, emits canonicalnomos.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. - 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.
- 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. - 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.
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.
R2 adds one Rust workspace member and one non-workspace browser application.
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@1atcrates/nomos-observed-scene/src/input.rs; andnomos.observed_scene_plan@1atcrates/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.
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.
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; andbrowser_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 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.
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.
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.widthandcrop.height: integers in1..=32.terrain_layers:3..=8rows, strictly ascending byid.- total layer-cell assignments across all layers:
3..=4096. actors:1..=64rows, strictly ascending byid.actions:0..=128rows, strictly ascending byid.- 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.
{
"cells": [{"x": 0, "y": 0}],
"id": "ground_layer",
"role": "calm_ground"
}role is exactly one of:
calm_ground;traversable_route; orstructure_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.
{
"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.
{
"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.
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.
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.
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.
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.
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.
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, andstack; - actor rows containing an integer ordinal handle,
cell,assembly,pose,controlled_marker,hostile_outline, andprotection_ring; and - action rows containing only
markerand 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.
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:
- the four workspace commands with
--lockedwhere Cargo accepts it; - the unchanged accepted R1 six-area artifact, wasm, native, viewer-test, browser-smoke, and native-replay proof;
- R2 schema ownership, source provenance, adopter-neutrality, maximum-fixture, and compiler tests;
- compilation, semantic-signature checks, and byte comparison of both committed R2 scenes;
- R2 viewer tests, source/public-manifest checks, distribution scan, and browser smoke;
- a fresh-target
cargo build --workspace --release --locked --offline, a clean R1 viewer build, and both clean R2 viewer builds used for byte comparison; - the budget measurements and receipt assembly; and
- 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 IDsplus 63 zeroes; - eight 64-byte layer IDs
lplus 62 zeroes plus digit0..7; - roles by layer ordinal:
calm_ground, traversable_route, structure_footprint, calm_ground, traversable_route, structure_footprint, calm_ground, traversable_route; - layer ordinal
icontains 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_stateliving whenfloor(i/8) mod 2 == 0and dead otherwise; and the three booleans from bits 0, 1, and 2 ofi; and - 128 actions with IDs
q, 60 zeroes, and three-digit ordinal; target actori mod 64; availability enabled for eveniand disabled for oddi.
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.
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.
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.
After this contract is owner-authorized, work proceeds in dependency order:
- 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.
- 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.
- 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:
- accept R2 as an admitted Nomos observed-scene presentation result;
- repair this contract explicitly and rerun affected evidence; or
- 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.
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.
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.
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.