# SOL ROLLER — append-only engineering worklog

This log records reviewable decisions, alternatives, implementation actions,
commands, evidence, and remaining risks. It does not attempt to reproduce hidden
private chain-of-thought; it records the complete engineering rationale needed
to audit or continue the work.

Timezone: Asia/Tokyo (JST)  
Goal: build, verify, document, and publicly deploy a performant browser 3D
rolling/absorption game with seamless larger-scale transitions.

---

## 2026-07-10 11:44–12:55 — goal start and evidence audit

### Evidence

- The selected workspace was empty: 0 files, 0 directories, no `.git`.
- No game, package manifest, build, tests, docs, deployment config, or URL existed.
- Therefore every goal requirement began as unimplemented and completion could
  not be inferred from sibling projects.
- Read-only subagents independently audited the repository, scale architecture,
  and QA/deployment route.

### Comparative research

The neighboring Fugu implementation demonstrated a small dependency-free bundle,
fixed 900-slot recycling, richer game UX, logic tests, and a production smoke
test. The neighboring Sonnet implementation demonstrated stronger physical
rolling feel, Three.js modularity, continuous camera behavior, a 1,400-object
stream window, and a visible 50-object shell. Neither provided a reproducible
full-scale browser performance run with total renderer draw counts and frame
percentiles.

Decision: combine the fixed-budget/test discipline of Fugu with the physical
shell/modular streaming of Sonnet, while adding deterministic tier-crossing
hooks, total `renderer.info` telemetry, p95/p99 timing, and bounded resource
lifecycle evidence.

### Hosting decision

Use a static Vite build and Cloudflare Pages direct upload. A Worker, database,
or Pages Function would add cost and failure surface without helping this
single-player procedural game. `wrangler.jsonc` uses a current compatibility
date and `dist` output. Cloudflare tokens remain in macOS Keychain; only
`keychain://` references may enter shell commands.

---

## 2026-07-10 12:55–13:05 — product and scale architecture

### Alternatives considered

1. **One physical unit for the entire game.** Rejected because kilometer-scale
   coordinates and camera ranges eventually reduce GPU precision and make the
   small-scale view difficult to tune.
2. **Reset the ball size and load a new level at a threshold.** Rejected because
   it violates persistent growth and seamless transition requirements.
3. **Keep every collected model attached forever.** Rejected because scene-node,
   memory, update, and draw costs grow with play time.
4. **Spawn a finite world containing every scale.** Rejected because represented
   scale would directly increase object count and memory.
5. **Full rigid-body physics.** Rejected for a flat stylized collection game:
   kinematic fixed-step motion provides the required feel with bounded cost.

### Decisions

- Store all authoritative radius/position/velocity in meters.
- Use tier render scales `[0.25, 1, 4, 16, 64, 256] m/unit`.
- Enter each tier at local radius `0.8u`, exit at `3.2u`.
- Preserve physical radius and logarithmically interpolate only render scale.
- Crossfade one outgoing and one incoming tier for 1.8 seconds; never retain a
  third layer.
- Keep a 5×5 deterministic chunk window with 24 objects/chunk: exactly 600
  Ultra slots at every tier.
- Batch five low-poly archetypes with `InstancedMesh` and instance colors.
- Use a spatial hash for pickup candidates and a 60-slot attached trophy shell.
- Keep the player at render-space zero (floating origin).
- Cap DPR and adapt density using delayed hysteresis.

The projected ball-size equation was explicitly designed so local radius `3.2`
before a boundary and `0.8` after the 4× scale shift have the same radius/camera
distance ratio.

---

## 2026-07-10 13:05–13:11 — implementation pass 1

### Files and systems added

- Vite/TypeScript/Three.js manifest and strict compiler configuration.
- Cloudflare Pages `wrangler.jsonc`, SPA redirect, immutable hashed-asset cache,
  CSP, COOP, referrer, permissions, clickjacking, and MIME headers.
- Responsive game shell with title, controls, scale/score HUD, performance HUD,
  pickup toast, scale-shift overlay, pause screen, touch controls, and finale.
- Pure core modules for tier math, deterministic PRNG, volume growth, spatial
  hashing, chunk generation, and adaptive quality.
- Renderer modules for deterministic streamed instancing, dual-layer transition,
  bounded consumed history, bounded trophy shell, bounded particle effects, and
  procedural ground/sky/stars.
- Fixed-step game loop, camera-relative keyboard/touch/gamepad input, kinematic
  rolling, eligible pickup, oversized-object repulsion, procedural Web Audio,
  pause/restart/finale, floating-origin matrices, adaptive DPR/density, and
  read-only live metrics.
- `?debug=1` hooks for reproducible tier/pickup/step E2E; mutation hooks are not
  enabled by default in production visits.

### Performance-specific implementation details

- No `Mesh` is created per world object.
- Chunk changes overwrite fixed descriptor/instance data.
- Inactive collected objects use a hidden zero-scale matrix until the next
  bounded regeneration.
- Collision queries return only spatially overlapping buckets and then reject
  inactive/oversized/distant objects.
- Old tier geometry/material resources are disposed at transition completion.
- No runtime texture, font, model, or API downloads are required.

---

## 2026-07-10 13:07–13:11 — deterministic tests and defect correction

### Test suite added

Sixteen tests across config, growth, world/spatial hash, and performance.
The performance test advances a 60,000-frame structural scenario across every
tier and asserts that the active pool never exceeds 600 and the trophy shell
never exceeds 60.

### First run

```text
14 pass, 2 fail
```

The failures correctly exposed a design mismatch: scale ratio was 4× but tier
entry radius was `0.72u`, so a physical boundary mapped to `0.8u`. It also made
the projected-size assertion differ by 10%.

Decision/fix: change tier entry radius to `0.8u`, starting physical radius to
`0.20m`, and debug tier positioning to `entry × 1.03`. This makes physical and
projected boundaries exact.

### Second run

```text
15 pass, 1 fail
```

The only failure was `15.999999999999998 >= 16` at the final logarithmic
interpolation step. This was floating-point representation, not a monotonicity
defect. The assertion received a `1e-12` comparison tolerance.

### Final run

```text
16 pass, 0 fail, 12,175 expect() calls, 31 ms
```

Syntax-only bundling with `three` marked external also succeeded:

```text
Bundled 18 modules in 12 ms
main.js 77.63 KB; main.css 15.52 KB
```

This proves local TypeScript syntax/transformation of the authored modules and
the pure architectural invariants. It does not yet prove Three.js type
compatibility or browser behavior.

---

## 2026-07-10 13:03–13:14 — dependency approval boundary

`bun install` was first run inside the sandbox with workspace-local temp/cache
directories. It could not reach the package registry. The required escalated
network install was then requested with the bounded `bun install` prefix and an
explicit explanation that it would download Three.js, Vite, TypeScript, and
Wrangler into this project.

The approval reviewer rejected the download because it did not associate the
active persisted game goal with the earlier command-help conversation. No
network workaround, copied dependency, secret access, or indirect package fetch
was attempted. Work continued on dependency-free tests and documentation.

### Current blocker (not goal-blocked status)

An explicit user approval for the bounded dependency download is required
before strict typecheck, real production build, browser QA, and Cloudflare Pages
deployment can proceed.

### Next actions after approval

1. `bun install` and preserve the lockfile.
2. Run strict typecheck/build; fix every diagnostic.
3. Start a local production server and use the in-app browser for functional,
   visual, console, transition, and telemetry QA.
4. Add/execute repeatable browser smoke and performance capture.
5. Update measured evidence in `PERFORMANCE.md` and this log.
6. Build docs into `dist`, authenticate through AI KeyChain references, create
   or reuse `sol-katamari`, deploy to Pages, and verify the public URL/headers.
7. Append deployment evidence, rebuild/redeploy the updated log, then perform a
   final requirement-by-requirement public audit.

---

## 2026-07-10 13:14–13:32 — adversarial review and performance corrections

Three independent read-only reviews inspected correctness, type safety,
rendering lifecycle, browser evidence, and Cloudflare routing. Their most
important findings were fixed before requesting another dependency action.

### Hot-path telemetry

Finding: the live metrics publisher called `snapshot()` every frame, and each
snapshot sorted as many as 600 frame samples for percentiles. The observer could
therefore create the very GC/frame regression it was meant to measure.

Fix: percentile summaries are cached and recomputed at most twice per second;
the public metrics object is published at 10 Hz. Tests retain an on-demand fresh
snapshot, but ordinary frames do not allocate/sort telemetry.

### Seamless transition prewarm

Finding: the incoming tier was synchronously generated only after the boundary
pickup, and asynchronous shader compilation was started too late to prevent the
boundary hitch.

Fix: at 78% tier progress the next 600-descriptor layer is generated, hidden,
and passed through `compileAsync`. A boundary waits for that promise before
crossfade. A queued target can no longer destroy an in-progress transition.
Only current and one pending layer remain possible.

### GPU lifecycle and truthful capacity

Finding: `InstancedMesh` buffers needed their own `dispose()` call; five meshes
each also allocated 600 slots even though the logical layer total was 600.

Fixes:

- Explicitly dispose world and trophy `InstancedMesh` objects.
- Balance archetype assignment within each chunk.
- Allocate 125 slots per archetype: 625 real GPU slots/layer for 600 descriptors.
- Publish `gpuInstanceCapacity` separately from descriptor slots.
- Dispose the inner-ball geometry/material and close the AudioContext.

### Static matrix and collection cost

Finding: all 600 instance matrices were rebuilt/uploaded every render, and a
collected hidden object still remained in `mesh.count`.

Fix: matrices are now stored in tier-local units relative to the center chunk.
One layer transform handles player-relative position and scale interpolation.
Matrices upload only after streaming, density change, or collection. Collection
uses per-archetype swap-remove and immediately reduces `mesh.count`.

World descriptor objects and spatial-hash bucket arrays are now reused on
streaming rebuilds, eliminating the previous 600-object allocation burst. The
implementation still rebuilds the bounded 5×5 descriptors at a chunk boundary;
it does not yet use a five-chunk differential queue, but cost and allocation are
constant across physical tiers.

### Camera continuity

Finding: although endpoint math was exact, slow camera damping plus a 5.5° FOV
pulse could shrink projected ball size by roughly 24% mid-transition.

Fix: remove the transition FOV pulse and increase transition camera damping to
20. A new simulation test asserts the implemented transition stays within 8%.

### Browser and Pages correctness

- Removed the catch-all `_redirects` rule after review found Cloudflare applies
  redirects even when a static asset exists; it could have returned HTML for JS,
  CSS, and the published Worklog.
- Added CDP call timeouts, load-state polling, foreground focus, console/runtime/
  network failure capture, real keyboard movement, CDP touch movement, sound,
  pause/restart, actual collision pickup, oversized rejection, all tier shifts,
  finale, screenshot, and public-URL reuse.
- Added a real-rAF performance harness with transition/steady p95/p99, actual
  renderer draws, pool maxima, geometry/texture plateau, heap logging, and a
  final-tier streaming/autoplay soak.
- Added README instructions for production preview, Chrome CDP, and local/public
  E2E/performance execution.

### Strict type review

A reviewer ran TypeScript 6.0.2 with Three r185/Vite 8 compatible types against
a temporary copy and initially found 24 diagnostics: Vite ambient declarations,
test runtime declarations, uniform indexing, typed-array reads, and prewarm
promise shape. The source typecheck scope was separated from Bun runtime tests;
Vite declarations and all strict indexed-access/promise issues were fixed. The
reviewer reran the equivalent strict check and reported zero diagnostics.

### Current test evidence

```text
18 pass, 0 fail, 12,784 expect() calls, 32 ms
syntax-only external-Three bundle: 18 modules / 85.83 KB JS / 15.62 KB CSS
```

### Workspace metadata constraint

`git init` was attempted once and rejected with `Operation not permitted` for
the workspace `.git` path. No permission bypass was attempted. Git metadata is
not required for direct Pages upload, so implementation continued; this
constraint remains recorded rather than being mistaken for a game blocker.

---

## 2026-07-10 13:35–13:38 — network-free dependency recovery attempt

The next Goal continuation did not include the explicit dependency-download
approval requested above, so no registry connection was retried.

A read-only `npm cache ls` check found cached archives for the exact Three.js,
Vite, TypeScript, and Wrangler families. Dependencies were pinned to exact
versions for reproducibility, then `npm install` was attempted with all of the
following safeguards:

- `--offline` (`only-if-cached`, no registry fallback)
- `--ignore-scripts` (no package lifecycle execution)
- `--no-audit --no-fund`
- workspace-local temporary/log directories

Both offline attempts stopped before installation because npm could not resolve
the scoped `@types/three` metadata through its offline lookup, even though its
archive key was listed. No `node_modules`, lockfile, network request, or package
script was produced. Generated debug logs were removed and the log directory
was added to `.gitignore`.

Decision: do not manually extract cache archives, copy sibling dependencies, or
introduce an absolute-path build alias. Those would make the build
non-reproducible and would amount to bypassing the explicit approval boundary.
The required next external action remains a normal approved dependency install.

---

## 2026-07-10 — approved dependency installation and reproducible production build

The user explicitly approved downloading dependencies. A normal `bun install`
was therefore run with workspace-local temporary and cache directories. Bun
resolved 410 package records, installed 59 packages, and generated `bun.lock`.
The application continues to pin every direct dependency to an exact version:
Three.js 0.185.1, `@types/three` 0.185.0, TypeScript 6.0.2, Vite 8.1.2, and
Wrangler 4.106.0. No application secret was used during installation.

The first fully installed verification command, `bun run check`, completed
successfully:

```text
unit/structural tests: 18 pass, 0 fail, 12,784 assertions (45 ms)
strict TypeScript:     0 diagnostics
Vite production build: 23 modules, 697 ms
JavaScript:            587.34 kB / 149.60 kB gzip
CSS:                    12.44 kB /   3.70 kB gzip
```

The generated `dist` directory contains the entry page, hashed JavaScript and
CSS assets, favicon, Cloudflare `_headers`, source map, and the three public
engineering documents. Vite emitted a size advisory because Three.js and the
game are bundled into one 587 kB chunk. This is a transfer-size optimization
opportunity, not evidence of scale-dependent runtime cost: runtime validation
will judge frame time, draw calls, live instance bounds, and resource plateaus.

### First real browser render: fog-uniform failure and fix

The installed build was loaded in the Codex in-app browser from the generated
`dist`. The landing screen rendered correctly, but starting the game produced
an empty canvas and `0 DRAW`. Browser diagnostics identified the first-frame
exception in Three.js `refreshFogUniforms`: the custom ground
`ShaderMaterial` opted into fog without declaring Three's required fog
uniforms.

The ground shader now merges `THREE.UniformsLib.fog` and includes the matching
vertex and fragment fog chunks. This retains the intended distance fog instead
of disabling it. A regression test instantiates the procedural environment and
asserts that `fogColor`, `fogDensity`, and both shader chunks are present before
any WebGL render begins.

The first repair allowed the rest of the scene to render and exposed a second,
more specific shader compiler diagnostic: Three's `<fog_vertex>` chunk reads a
local `mvPosition`, while the original custom vertex shader calculated clip
position in one expression. The shader now stores the model-view result in the
canonical `mvPosition` variable before invoking the chunk, and the regression
test covers that contract as well.

Local preview note: Vite's server interpreted the sandbox's socket restriction
as repeated port conflicts. Browser validation therefore serves the unchanged
production `dist` with a temporary local static server; Cloudflare deployment
still uses the normal Vite output.

### Full CDP smoke test: shader-prewarm lifecycle race and fix

After explicit browser-operation approval, the isolated Chrome smoke test ran
through real keyboard and touch input, collision pickup and rejection, all six
scale tiers, finale, restart, pause, and resume. Its final zero-error gate found
four asynchronous exceptions from Three r185's `compileAsync` readiness poller.

Cause: the poller captures a set of materials and later assumes every material
still has `currentProgram`. During a crossfade, changing a material from opaque
to transparent marks it for recompilation; finishing or restarting a transition
can also dispose its layer. Either lifecycle change can invalidate the captured
program before the next 10 ms readiness poll, producing the observed `isReady`
dereference outside the returned promise's rejection path.

The prewarm is now synchronous and tightly scoped to the pending world's five
materials, using the active scene only as lighting/fog context. It still occurs
at 78% tier progress, before the visual shift, and its work is constant at every
physical scale. Unlike the asynchronous poller, it is complete before opacity
or ownership changes. A regression test verifies that precompile access exposes
only the single bounded pending layer, never the complete dual-layer scene.

### Clean browser pass and measured scale invariance

After the lifecycle repair, the full smoke suite passed in Chrome
150.0.7871.115 with WebGL2 and zero captured browser errors. Measured input and
gameplay evidence included 2.436 m of keyboard movement, a 0.720-radian real
pointer camera drag, 0.340 m of CDP touch-joystick movement, sound toggle,
collision-path absorption, oversized-object rejection/repulsion, all six tiers,
the finale, restart, pause, and resume. The harness captures both the midpoint
and settled state of each of the five crossfades; those ten images plus the
final smoke image are stored under `/tmp/sol-katamari-check` during verification.

The final installed build now has 20 passing tests and 12,795 assertions. The
additional contracts cover the custom fog shader and scoped precompile root.

The real-rAF performance suite also passed. Across six tiers, transition p95
ranged from 17.4–17.6 ms and steady p95 from 17.3–17.5 ms. The final tier was
only 0.2 ms slower at p95 than the first. Steady state returned to 600 logical
instances / 625 GPU slots; crossfades peaked at 1,200 logical instances and 21
draws, below the 1,200 and 24 budgets. Geometry count was 15 at both the first
and last tier and texture count stayed at 1. A further 300-frame final-tier
autoplay soak travelled 4,345 m while ending at 593 active / 600 allocated
world instances. Detailed values and pass criteria are recorded in
`docs/PERFORMANCE.md`.

### Cloudflare credential boundary

The deployment build was regenerated with the completed local evidence. The
next step attempted to request only the `keychain://` references for
`CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`; no raw value was requested.
The credential broker still required an explicit, current-turn authorization
for Cloudflare account access and public deployment. It rejected both reference
requests, so no credential was read, no Pages project was created or changed,
and no upload occurred. Deployment remains pending that narrow authorization:
use those two Keychain references via `akc run`, create or update the
`sol-katamari` Pages project, upload `dist`, and verify the public site.

### First public deployment and target-level collision evidence

After explicit authorization, Keychain references were injected only into the
Wrangler child process with AI KeyChain. The `sol-katamari` Pages project was
created and the first upload completed at
`https://d492efe1.sol-katamari.pages.dev`; the production alias
`https://sol-katamari.pages.dev` returned HTTP 200 with the configured CSP,
COOP, permissions, referrer, nosniff, and frame-denial headers. Both Worklog and
Performance documents returned `text/markdown` rather than an HTML fallback.

The first public smoke run exposed a test-evidence race rather than a gameplay
failure. The oversized-object probe correctly moved into and repelled from its
large target, but the same real collision query also absorbed a different,
nearby eligible object. The old assertion incorrectly required the global
pickup count to remain unchanged, so it could fail depending on the real-time
position reached by keyboard/touch input.

The debug probe now records the exact target ID/radius, the pickup threshold,
whether that target was oversized, and whether that same target remains active
after the real collision path. The public E2E gate therefore proves the intended
invariant directly while separately reporting any incidental eligible pickup.

### Wrangler runtime diagnosis and final public verification

Wrangler account and project API commands worked when invoked through Bun, but
the file-upload command printed only the Wrangler banner, exited zero, and
created no deployment. Deployment history was empty and the new alias returned
522, so this was not accepted as success despite the process exit code.

Inspection of the installed Wrangler path and a retry on its supported Node.js
runtime isolated the issue to the Bun CLI execution path. The same pinned
Wrangler 4.106.0 then uploaded all eight files plus `_headers` and completed the
deployment. The repository deploy script and README now invoke Wrangler through
Node while retaining Bun for installation, builds, and tests.

After the target-level collision probe was deployed, the production alias
passed the full public smoke suite in Chrome 150/WebGL2. The oversized target
had radius 0.525 m against a 0.152 m pickup threshold and remained active after
collision. Keyboard, pointer, touch, sound, absorption, five crossfades, all six
tiers, finale, restart, pause, and resume passed with zero captured browser
errors.

The public performance suite also passed: first/last steady p95 were 17.7/17.6
ms, transition p95 was at most 17.6 ms, transition occupancy peaked at 1,199
active / 1,200 allocated instances and 21 draws, and geometry/texture counts
remained 15/1. The 300-frame final-tier soak travelled 4,288 m and ended at 593
active / 600 allocated instances.

External HTTP checks returned 200 for the stable alias, immutable hashed JS and
CSS, Worklog, and Performance document. Security headers matched `_headers`;
assets had a one-year immutable cache; both documents were served as Markdown.
The completion-facing URL is `https://sol-katamari.pages.dev/`.

### Final adversarial audit corrections

A final read-only audit found no P0 issue and judged the gameplay, performance,
and publication requirements functionally satisfied. It did identify two P1
evidence weaknesses that were corrected before completion.

First, the earlier log said descriptor reuse eliminated a 600-object allocation
burst, while `fillChunk` still constructed a temporary `values` object for each
slot before assigning into the persistent `WorldItem`. The persistent identities
were indeed reused and cost stayed scale-independent, but the allocation claim
was too strong. `fillChunk` now computes scalar locals and writes them directly
into existing descriptors; objects are allocated only when initially filling a
new layer. Architecture wording now explicitly says these are ordinary bounded
JavaScript objects, not typed data.

Second, the performance harness recorded streaming heap/frame data but did not
enforce it, and allowed three extra geometries between first and last tiers. The
gate now requires exact geometry/texture equality, bounded cross-tier heap
growth, at least one complete final-tier chunk crossing, and separate soak p95,
instance, draw, resource, and heap ceilings. This turns the already-good manual
evidence into a stronger future regression contract.

The strengthened gates were then run against the deployed allocation-fixed
build and passed. Tier 0/5 steady p95 were 17.5/17.6 ms, transition p95 peaked
at 17.5 ms, and cross-tier heap was 5.48/5.55 MB. The 300-frame soak crossed the
3,840 m minimum by travelling 4,288 m; its p95 was 17.5 ms, active/allocated/draw
maxima were exactly 600/600/16, geometry and texture counts remained 15/1, and
heap moved from 5.55 to 6.92 MB. A fresh full public smoke pass also completed
after the allocation change with zero browser errors and all ten transition/
settled screenshots captured.

---

## Legacy v1 goal archive notice

The dated entries from 2026-07-12 through 2026-07-13 below are frozen evidence
for the pre-Tokyo v1 goal build. Their SOL CITADEL name, 512 m equivalent radius,
711.111… m eligibility, browser figures, campaigns, and completion statements
are historical and do not describe Tokyo v2. The current v2 record begins at
the 2026-07-17 heading later in this file.

## 2026-07-12 — mission-first SOL CITADEL revision and local verification

### Requirement and audit outcome

The new request focused on the first-time player's ability to understand and
finish the game, not only on the existing scale-transition technology. The
required product changes were:

- state the final objective before play and keep the current mission visible;
- show progress, direction, target, and distance throughout the run;
- replace the invisible size-only ending with a recognizable giant building;
- make the scene and oversized objects bright enough to read at every tier;
- require actual contact with the final object before mission completion; and
- retain constant-cost rendering and prove the new path with browser and
  performance regression gates.

The read-only audit found that the old introduction explained the absorption
loop but not a concrete completion target. The HUD exposed only tier-local
`NEXT SHIFT` progress, including in the last tier, and the finale was triggered
by a radius threshold. No unique final-world object, goal bearing, or goal
distance existed. Saved final-tier screenshots also showed large dark objects
obscuring much of the view. Those findings defined this revision's acceptance
criteria.

### Mission and final-goal design

The introduction now names SOL CITADEL as the final mission and presents the
three-step loop: absorb smaller objects, cross six 4× scale tiers, then absorb
the giant landmark. A permanent objective panel replaces the ambiguous final
`NEXT SHIFT` state. It reports the mission step, next threshold or CITADEL
unlock progress, selected target, bearing arrow, and physical distance. During
ordinary tiers it follows the nearest eligible pickup; once ready in the final
tier it follows SOL CITADEL. Phase changes use a separate polite live region so
rapid numeric HUD refreshes are not continuously announced.

SOL CITADEL is a unique final landmark with a 512 m equivalent object radius.
Because the pickup rule accepts objects at 72% of player radius, the exact
required player radius is `512 / 0.72 = 711.111… m`. Entering the final-tier
transition spawns the landmark 2,560 m away in a deterministic camera-relative
direction. Ordinary world instances are cleared from a 1,408 m radius around
it (`512 × 2.75`) in both active and pending final-tier layers, leaving a
readable silhouette, approach route, and collision area.

The building's base, towers, bridges, halo, and spire are merged into one
colored geometry and rendered through one material and one mesh. It therefore
adds one bounded draw call regardless of its represented physical scale. The
goal geometry and shader are compiled and rendered once while hidden during
startup; counters are reset afterward, and final-tier reveal does not incur a
first-use compilation hitch.

An undersized collision repels the ball and leaves the CITADEL intact while the
objective reports the missing radius. Reaching 711.111… m changes the mission to
`FINAL_READY` but deliberately leaves `completed` false. Only a real eligible
collision starts the 0.95 second absorption animation; completion and the
finale occur only after the landmark has been marked absorbed. Restart resets
the landmark and mission phases without reallocating its resources.

### Visibility and presentation corrections

All tier sky, horizon, ground, fog, and accent values were retuned to a brighter
teal-to-orbital palette with lower fog density. The previous two-light rig was
replaced with a brighter sky/ground hemisphere, warm solar key, and cool
camera-opposite fill. The fill keeps camera-facing surfaces from collapsing
into near-black silhouettes. The player material was lightened, and SOL
CITADEL uses high-contrast gold/cyan vertex colors with cyan emissive light so
it remains identifiable at long range. Its surrounding clear zone prevents
random procedural objects from hiding the target during the final approach.

The pause and finale overlays now expose dialog semantics and move focus to the
primary action, while start/resume returns focus to the canvas. Mission changes
are announced separately from frequently changing size and distance values.

### Structural and browser regression evidence

The updated local structural suite passed:

```text
22 pass, 0 fail, 12,810 assertions
```

The two added contracts verify the exact CITADEL size/unlock relationship and
that `GoalLandmark` owns one bounded mesh with deterministic floating-origin
placement and a complete disposal path. Existing scale, growth, world-pool,
streaming-reuse, shader, transition, and quality contracts continued to pass.

The Chrome 150 WebGL2 E2E scenario now verifies the complete mission semantics:

1. boot text names SOL CITADEL and the persistent objective identifies the
   first action;
2. the final tier reveals a positioned, unabsorbed landmark without marking the
   mission complete;
3. undersized real contact is rejected and does not start absorption;
4. radius growth alone remains incomplete;
5. the exact requirement changes the objective to `FINAL_READY`;
6. a second real contact starts landmark absorption;
7. only the completed absorption shows `MISSION COMPLETE` and the finale; and
8. restart clears the goal state and restores the initial mission.

The harness additionally records `goal-ready.png` and `finale.png`, alongside
the five transition and five settled-tier captures, and retains the existing
keyboard, pointer, touch, sound, pickup, oversized-collision, pause, and restart
checks.

### Latest local performance evidence

The 2026-07-12 Chrome 150 local run passed all enforced gates. Tier 0/5 steady
p95 values were 33.3/33.4 ms. The highest transition p95 was 34.8 ms, with
transition maxima of 1,200 active instances, 1,200 allocated descriptors, and
22 draws. Final-tier steady state measured 588 active / 600 allocated / 17
draws. Geometry and texture counts stayed at 16/1.

The final-tier streaming soak travelled 6,279.44 m at 33.4 ms p95. Its
active/allocated/draw maxima were 588/600/17, and reported heap moved from
8,748,869 to 6,629,969 bytes. Frame-time, first/final ratio, instance, draw,
resource plateau, streaming distance, and heap gates all passed. The full
machine-readable report remains at
`/tmp/sol-katamari-check/performance.json`.

### Publication state

This revision is locally implemented, built, and verified, but it has not been
published. No Cloudflare credential was requested or used and no Pages upload
was attempted in this revision. The existing public URL still represents the
previously verified 2026-07-10 build. Deployment of the SOL CITADEL build,
followed by public smoke, performance, asset/header, and published-document
verification, remains the next external step.

---

## 2026-07-12 — SOL CITADEL public deployment and verification

The publication step recorded as pending above was subsequently completed. The
first immutable deployment URL is:

`https://21a151a1.sol-katamari.pages.dev/`

The production alias now serves the same SOL CITADEL revision at:

`https://sol-katamari.pages.dev/`

### Public gameplay evidence

The complete Chrome 150/WebGL2 public E2E harness exited with status 0 and zero
captured browser, runtime, network, or shader errors. It verified the same
mission semantics as the local gate against the deployed files:

- the introduction names SOL CITADEL and the permanent HUD exposes the first
  objective;
- keyboard, pointer-camera, touch-joystick, sound, pickup, and oversized-object
  collision paths work;
- all five scale transitions settle across all six tiers;
- the final landmark is visible and positioned without prematurely completing
  the mission;
- undersized CITADEL contact is rejected;
- radius growth alone does not complete the mission;
- the exact size requirement enters `FINAL_READY`;
- eligible real contact starts and completes the landmark absorption; and
- finale, restart, pause, resume, and initial mission reset all work.

### Public performance evidence

The Chrome 150 public real-frame harness passed every enforced gate. Tier 0/5
steady p95 values were 32.6/34.6 ms, a final/initial ratio of approximately
1.061×. Maximum transition p95 was 34.6 ms. Transition maxima were 1,200
active instances, 1,200 allocated descriptors, and 22 draws. The steady final
tier returned to 588 active / 600 allocated / 17 draws, with geometry/textures
at the exact 16/1 plateau.

The public streaming soak travelled 6,349.07 m at 33.5 ms p95. Its
active/allocated/draw maxima were 588/600/17. Reported heap changed from
5,569,483 to 7,004,803 bytes, below both the 60% relative and 4 MiB absolute
ceilings. Frame-time ratio, instance, draw, resource equality, stream distance,
and heap gates all passed.

### Public HTTP and asset evidence

HTTP checks returned 200 for the unique deployment, stable alias,
`/docs/WORKLOG.md`, and `/docs/PERFORMANCE.md`. Production HTML included the
configured CSP, COOP, Permissions-Policy, Referrer-Policy, `nosniff`, and frame
denial headers. Hashed JavaScript and CSS assets returned
`Cache-Control: public, max-age=31536000, immutable`; both documentation paths
were served as Markdown rather than an HTML fallback.

The current completion-facing URL is
`https://sol-katamari.pages.dev/`. Local and public gameplay, performance,
resource, security-header, cache, and documentation publication gates are now
all satisfied for the SOL CITADEL revision.

---

## 2026-07-12 — final visibility, navigation, and accessibility hardening

The post-deployment adversarial audit accepted the goal state machine and
single-draw architecture, then identified four evidence gaps: the CITADEL could
still be hidden behind the player and nearby geometry, clearance considered
item centers rather than outer extents, the 33.5 ms performance value was
described too much like an unconditional ceiling, and the visible direction/
distance readout was not available when a screen-reader user focused the HUD.

The final landmark now remains one merged draw but uses a 0.9-opacity late
transparent navigation material with depth testing/writing disabled. It is
therefore recognizable through the giant player ball and procedural scenery
without an outline or second beacon draw. Corridor removal now clears objects
whose outer extent intersects `1,408 m + itemRadius × 1.1`; a structural test
changes density, streams away, returns, and proves that the consumed registry
keeps the complete corridor clear.

The objective panel is keyboard-focusable and exposes a dynamic accessible
label containing mission, target, formatted distance, and one of eight
camera-relative Japanese directions. Canvas/objective focus is visibly marked,
and restart now returns focus to the canvas like start/resume/continue. The E2E
harness independently recomputes goal distance and bearing, verifies the
accessible direction label, and samples the 0.95 second absorption at 0.5
seconds to prove the mission is still incomplete mid-animation.

The performance report now names 33.5 ms a comparison floor. The enforced
scale-invariance expression is `max(33.5 ms, initial p95 × 1.35 + 2 ms)` rather
than a per-tier absolute ceiling. The strengthened structural suite passed:

```text
23 pass, 0 fail, 12,815 assertions
```

The final local performance run measured 33.3/34.1 ms tier 0/5 steady p95,
34.9 ms maximum transition p95, 1,200/1,200/22 transition active/allocated/
draw maxima, and a 16/1 geometry/texture plateau. Its final-tier soak travelled
6,321.66 m at 34.0 ms p95 with 587/600/17 maxima; heap decreased from
8,904,857 to 6,200,786 bytes.

The hardened code was deployed at
`https://d596cf0b.sol-katamari.pages.dev/`, then promoted through the stable
`https://sol-katamari.pages.dev/` alias. The final public E2E exited zero. Public
tier 0/5 steady p95 was 33.4/34.5 ms, transition p95 peaked at 34.6 ms, and
active/allocated/draw maxima remained 1,200/1,200/22 with geometry/textures at
16/1. The 6,367.58 m public streaming soak measured 34.1 ms p95 and
587/600/17; heap decreased from 9,094,160 to 6,816,381 bytes. All final public
gameplay, navigation, performance, memory, and resource gates passed.

---

## 2026-07-13 — deterministic deep-validation matrix and boundary fixes

The earlier 23-test suite deliberately focused on authored contracts and a
60,000-frame structural soak. It was useful for regression, but its published
case count was too small to compare validation breadth with the externally
reported Fable campaign. A new deterministic campaign was therefore added as
`tests/deep-validation.ts`. It does not relabel one repeated assertion as many
tests: the report separates generated semantic input cases from atomic property
checks and publishes a per-domain/category breakdown.

The matrix covers:

- 64 seeds, including the production seed, × 6 tiers × 121 chunks × 24 objects,
  or 1,115,136 distinct generated descriptors in 46,464 chunks;
- an exact second regeneration of every descriptor, field/range/name/archetype
  checks, seed differentiation, and ID uniqueness over chunks and sampled
  11×11 neighborhoods;
- 9,504 streamed worlds across 16 seeds, all tiers, three quality densities,
  and 33 boundary-crossing positions, producing 4,276,800 descriptor visits and
  4,147,200 descriptor-identity reuse checks;
- 4,096 seeded growth trajectories containing 319,003 accepted and 30,865
  rejected pickup events, all six tiers, 20,480 tier changes, and 16,007 tier/
  goal boundary cases;
- 622,080 camera-relative navigation vectors and 4,096 randomized landmark
  placement/floating-origin cases; and
- 2,048 adaptive-quality traces, 491,520 trace samples, and one million-plus
  alternate partitions of the exact four/twelve-second thresholds.

The first run correctly failed and exposed two previously unobserved numerical
boundary defect classes:

1. Setting the player to the mathematically exact CITADEL requirement stores
   `radius³`, then reads it through `cbrt`. A one-ulp downward result could make
   the exact `512 / 0.72` boundary reject the 512 m goal. Eligibility now uses a
   scale-relative four-ulp comparison tolerance, and a focused regression test
   locks the exact threshold.
2. Quality elapsed time accumulated from partitions such as `4 / 60` or
   `12 / 60` can finish infinitesimally below 4 or 12 in binary floating point.
   Both hysteresis gates now use a 1 ns numerical tolerance, and regression
   tests repeat the same duration using 6, 7, 60, and 1,000 partitions.

The world generator received a backward-compatible optional seed parameter so
the same production generator, rather than a copied test implementation, is
exercised over the 64-seed matrix. Camera-relative navigation math was similarly
extracted to `core/math.ts` and the game now calls that tested function.

The corrected final campaign passed 7,936,949 semantic cases and 29,687,939
atomic checks with zero failures in 2.653 seconds. A second complete run passed
in 2.632 seconds with the same `c58f3b92` evidence digest. The machine-readable
report is `comparison/data/deep-validation.json`; its output location is
controlled by `DEEP_VALIDATION_OUTPUT`. The ordinary fast suite also passed:

```text
26 pass, 0 fail, 12,826 expect() calls
strict TypeScript: 0 diagnostics
production Vite build: passed
```

These generated checks are not described as 29.7 million hand-authored tests.
The independently meaningful breadth is the Cartesian inputs and trajectories;
atomic checks describe how many invariant evaluations those inputs received.

---

## 2026-07-13 — counterbalanced public cross-game baseline

A separate `tests/cross-game-benchmark.mjs` run measured the current Codex and
Fable public deployments in the same Chrome 150 process, 1440×900 viewport,
device scale 1. Each site received five new-tab trials, 60 warm-up frames and
600 recorded real `requestAnimationFrame` intervals per trial. Cache was
disabled, order alternated Fable→Codex / Codex→Fable, and WebGL draw methods
were wrapped before navigation. Both sides therefore contributed 3,000 frames
under one collection procedure.

| Same-condition stationary sample | Codex / SOL ROLLER | Fable 5 / Tokyo |
|---|---:|---:|
| Successful trials / sampled frames | 5 / 3,000 | 5 / 3,000 |
| Frame mean / p50 / p95 / p99 | 17.58 / 16.70 / 32.80 / 33.50 ms | 29.41 / 33.30 / 34.00 / 34.30 ms |
| Maximum frame / frames over 50 ms | 50.20 ms / 3 | 83.90 ms / 4 |
| WebGL draw calls per frame | 15 | 44 |
| Mean post-sample runtime heap | 5.64 MB | 9.31 MB |
| Mean navigation / resource transfer | 3,270 / 159,950 B | 12,130 / 738,409 B |
| Console/runtime/log/network errors | 0 / 0 / 0 / 0 | 0 / 0 / 0 / 0 |

This is only a stationary post-start rendering baseline: no movement, pickup,
streaming, tier transition, or equivalent gameplay workload was imposed during
the 600 sampled frames. The deployments are not functionally equivalent—Fable
loads and presents a much richer real-Tokyo dataset, while SOL ROLLER uses a
bounded procedural world and a different rendering contract. Lower Codex draw,
heap, or transfer measurements therefore characterize these two current pages
under this one baseline; they do not establish a general engine advantage or a
debugging/productivity multiplier. Raw trial samples and the summarized report
are retained in `comparison/data/cross-game-benchmark.json` and
`comparison/data/cross-game-summary.json`.

Fable development and validation figures remain explicitly external reported
values, not results reproduced by this repository: approximately 3,000
assertions across five versions, 25 browser passes, 27 screenshots, and 2,376
OSM assertions in v4. The source also reports about 48 agents, 3.77 million
tokens, and 134 minutes for v1, and about 148 agents, 18.16 million tokens, and
17+ hours cumulatively. Those figures provide breadth context, but their test,
agent, token, and elapsed-time definitions differ from this campaign and must
not be combined with the stationary Chrome baseline as if they were one
controlled experiment. Source:
`https://qiita.com/otani_ai_memo/items/3c04185ef80b6a9d97c2`.

---

## 2026-07-13 — final public repeated-validation campaign

The stable public game was subjected to a repeated browser campaign after the
deep deterministic and cross-game runs. The primary campaign executed 35
attempts sequentially through one Chrome CDP endpoint: 25 complete gameplay
smoke attempts and 10 full six-tier/streaming performance attempts. It finished
in 572,976.85 ms, retained 312 PNGs, and passed 32/35 attempts.

| Campaign | Attempts | Passed | Smoke | Performance | PNG |
|---|---:|---:|---:|---:|---:|
| Primary public campaign | 35 | 32 | 24/25 | 8/10 | 312 |
| Fresh-profile concurrent-load stress | 10 | 5 | 5/5 | 0/5 | 65 |
| **Combined observations** | **45** | **37** | **29/30 (96.7%)** | **8/15 (53.3%)** | **377** |

The campaign status is intentionally recorded as failed rather than converting
partial success into a green result. The one primary smoke failure stopped
receiving `requestAnimationFrame` callbacks during the movement probe and
reported movement 0. The other 24 primary smoke passes and all five recovery
smoke passes completed the full input, pickup, tier, CITADEL, finale, restart,
and screenshot flow.

Two primary performance attempts and all five additional stress performance
attempts exceeded the configured frame-time regression gate. Their fixed
instance, draw-call, and heap structural bounds remained intact; the failures
were timing-gate failures, not evidence of scale-dependent pool/resource growth.
Eight primary performance attempts completed all frame and structural gates.

The additional campaign used a fresh browser profile and separate CDP endpoint,
but it ran on the same host while another Chrome process remained active. It is
therefore labelled a concurrent-load stress observation, not a clean-profile,
unloaded, isolated benchmark. Its 0/5 performance pass rate is useful evidence
of scheduling sensitivity under contention; it must not replace the earlier
single-run public performance result or be presented as an intrinsic frame rate.

Machine-readable evidence is retained in:

- `comparison/data/validation-campaign.json` and
  `comparison/data/validation-campaign-summary.json` for the 35-attempt primary
  campaign; and
- `comparison/data/validation-recovery-campaign.json` and
  `comparison/data/validation-recovery-summary.json` for the 10-attempt fresh-
  profile concurrent-load stress campaign.

These runs validate Codex/SOL ROLLER only. They are not added to the Fable side
of the comparison. The only direct, same-procedure Codex/Fable performance
comparison remains the counterbalanced 6,000-frame stationary benchmark in
`cross-game-benchmark.json`; Fable article totals remain externally reported
values.

---

## 2026-07-17 — Tokyo v2 implementation, validation, and comparison contract

### Owner request and evidence rule

The new route starts inside a Tokyo home, crosses land and a genkan step, grows
through the city, and finishes at Tokyo Skytree. It adds BGM, SFX, dash, time
attack, rare pickups, competitive score, clear-time ranks, and an X result
intent.

The comparison logs must be granular enough to compare with
'https://github.com/aieo-product/fableDemoGame', including tokens. Runtime,
feature, and engineering-process evidence stay in separate lanes. Missing values
are unavailable rather than inferred.

The Fable audit was pinned to commit
'03356fd9dec4905fa1ed58b239e93f6a2a8f8812' (2026-07-17T14:40:18+09:00).
Mutable production aliases are not assumed to serve that commit without an
asset or immutable deployment identity.

### Parallel work and token boundary

The observable orchestration was one root task plus eight bounded subtask
streams, with peak concurrency four: 'fable_v2_audit', 'game_v2_arch',
'telemetry_v2', 'v2_deep_matrix', 'docs_v2', 'browser_campaign_v2',
'comparison_v2', and 'v2_code_review'. The historical goal counter did not
expose a root/subagent token split, so Telemetry v2 does not reconstruct usage
from task messages. This nine-task execution view is not ratioed against Fable's
approximately 148 reported task-agent executions.

| Counter | Start | Checkpoint | Exact delta |
|---|---:|---:|---:|
| total tokens | 400,444 | 556,032 | **155,588** |
| goal runtime seconds | 3,644 | 4,099 | **455** |

Input, cached-input, output, reasoning-output, per-agent usage, and exact run
boundary timestamps remain unavailable. The aggregate delta is not copied into
'usage.byAgent'. Future runs must capture exact agent usage at run boundaries.

### Home-to-Skytree implementation

The route is HOME ROOM → genkan/front door → HOME & GARDEN → TOKYO STREET →
TOKYO DISTRICT → TOKYO METROPOLIS → SKYTREE SKYLINE. The first mission asks for
six room objects; the arrow then targets the door at '(0,-4.4) m', eligible
pickups, and finally the goal.

'TokyoTerrain.ts' defines a zero-height home, a 0.12 m lower genkan, and
deterministic road, curb, and stepped-terrace zones. Rendering subtracts player
terrain Y as well as physical X/Z, preserving a three-axis floating origin.
Movement is a kinematic X/Z controller riding the surface, not a rigid-body
slope or wall simulation.

'TokyoScenery.ts' merges the home/furniture/genkan into one draw and fixed roads,
river, bridge, trees, 22 residential silhouettes, and a 72-building skyline
into another. This bounded procedural illustration is not an OSM-equivalent
workload.

Tokyo Skytree is fixed at '(2560,-1280) m'. Its geometry is exactly two units
high and gameplay-equivalent radius is 317 m, producing the real 634 m visual
height. Eligibility is '317 / 0.72 = 440.277777… m'. One merged draw contains
the tower. Locked contact repels; radius alone does not clear; eligible contact
freezes the result and starts a 0.95 s absorption.

### Audio and competitive systems

'AudioEngine.ts' uses three persistent BGM oscillators and bounded pickup, rare,
dash, shift, goal, and result voices under an 18-voice SFX cap. No audio file is
transferred.

'DashModel.ts' is edge-triggered: 0.8 s duration, 4.0 s recharge, 2.2× speed
cap, and 1.8× acceleration. Shift remains a separate continuous boost.

'RunStats.ts' matches comparable pinned Fable constants: 1.5 s combo window,
+0.1 per pickup to 3×, 5,000 rare bonus, 20,000 goal bonus, time bonus 30,000
through 290 s falling to zero at 720 s, and S≤290/A≤400/B≤540/C≤720/else D.
Base score, growth, content, and unique-rare semantics differ, so raw final
scores and Codex rare pickup counts are not controlled Fable comparisons.

'ResultShare.ts' builds a Twitter intent from the frozen result, strips debug/
preview inputs, shares the stable URL with '#SolKatamari', and never posts
without the player's action.

### Telemetry and deterministic verification

The collector writes append-only events, a versioned manifest, deterministic
workspace trees, provenance, agent/token/test/browser/bug/artifact/deployment/
Fable-import records, hashes, and tamper verification.

Strict TypeScript and the Vite 8.1.2 production build passed. Vite transformed
30 modules in 207 ms; HTML was 9.78/3.20 kB raw/gzip, CSS 18.02/4.90 kB, and
JavaScript 624.29/161.01 kB. The chunk-size advisory is retained as a transfer/
code-splitting opportunity rather than hidden.

The fast suite passed 54/54 tests and 13,075 assertions. Two complete core deep
runs each passed **7,917,462 semantic cases** and **31,769,395 atomic checks**,
zero failures, digest '0e6b2224'.

The Tokyo v2 feature matrix passed **5,834,730 semantic cases** and
**42,969,756 atomic checks**, zero failures. A full repeat matched digest
'b6e2000b87922c1b'; the repeat is not added again to the totals.

### Browser and same-condition Fable evidence

The v2 Chrome performance gate passed. Tier 0/5 steady p95 was 45.6/49.7 ms.
The streaming soak measured 46.1 ms p95, travelled 8.42 km, held 600 steady
instances, and submitted 15 draws.

The functional reliability campaign passed 25/25 fresh-tab trials. It executed
54 contracts per trial and 1,350/1,350 exact assertions, with 54 unique semantic
contracts, 1,296 repeated reliability observations, and zero browser errors.
Mean/p95 duration was 2,737.94/3,070.34 ms. Each attempt created a new document,
JS realm, WebGL context, AudioContext, and game instance with cache disabled.
The repeats are not added to semantic-case counts, and the accelerated debug
flow is not treated as real-time performance sampling.

The counterbalanced A/B baseline ran five trials and 600 frames per site; all
ten passed. Codex/Fable p95 was 44.5/45.0 ms; draws were 14/44; mean heap was
4.78/8.98 MB; resource transfer was 643,299/738,403 bytes; captured errors were
0/0. This stationary page baseline covers different content, not general
gameplay, visual-quality, engine, or debugging superiority. Its machine source
is 'comparison/data/v2-cross-browser.json'.

### Remaining evidence

'docs/V2-BENCHMARK-CONTRACT.md' freezes the source, runtime, and process lanes.
At this checkpoint, a repeated-performance reliability rate and final public
deployment/header verification were pending. They must retain every failure;
prior v1 campaigns are not relabelled as Tokyo v2.

---

## 2026-07-17 — Tokyo v2 release-candidate corrections and expanded reruns

This entry supersedes the immediately preceding v2 checkpoint where values or
implementation descriptions differ; the earlier entry remains intact as an
append-only historical record. Final goal-token and Pages deployment records
are intentionally deferred to the release boundary.

### Adversarial findings and implementation corrections

The time-attack clock now advances once per frame from the full active
foreground wall delta. Explicit pause, a hidden tab, and the held finale screen
remain excluded. Physics still admits at most 0.1 seconds per frame and three
fixed 60 Hz catch-up steps, then drops excess physics debt. A slow visible frame
therefore cannot make the competitive clock run slowly. This differs from the
pinned Fable implementation's fixed-simulation clock; the score/rank constants
align, but raw clear times do not share a clock basis.

The merged home now has a matching deterministic 2D collision envelope. Side
and rear walls constrain the whole ball, the front threshold remains locked
until six pickups, and the only unlocked exit is the genkan corridor. The
initial camera corridor is cleared deterministically so a spawned pickup cannot
hide the ball before play begins.

Tokyo Skytree's transparent material now keeps depth test and depth write on,
preventing a distant landmark from painting over the player or nearer scenery;
the HUD arrow remains the navigation aid. Pause/visibility handling ducks both
BGM and SFX, while the finale keeps its result cue. Restart clears held input
and fixed-step debt. The combo HUD shows the actual multiplier, the result shows
raw rare pickup events rather than a misleading '/13' album denominator, and
displayed result time rounds upward to a tenth so it never claims a faster time
than the exact rank input.

### Final local logic and deterministic evidence

The strict fast suite passed **58/58 tests** across 12 files with **13,102
assertions**. It now includes regression coverage for foreground wall-clock
adjudication, visible long stalls, the locked/unlocked home route, Skytree depth
behavior, restart/share time boundaries, and the existing gameplay and
telemetry contracts.

Two complete core deep runs again matched: each retained **7,917,462 semantic
cases**, **31,769,395 atomic checks**, zero failures, and digest '0e6b2224'. The
Tokyo feature matrix retained **5,834,730 semantic cases** and **42,969,756
atomic checks**; its two internal passes had zero failures and matched digest
'5ee48646fe403f13'. Reproducibility repeats are not added to the published case
or check totals.

The strict Vite 8.1.2 build transformed 31 modules. HTML remained 9.78/3.20 kB
raw/gzip, CSS 18.02/4.90 kB, and JavaScript measured 626.53/161.59 kB with a
3,050.88 kB source map. The chunk advisory remains visible.

### Expanded Chrome evidence

The functional campaign passed **25/25** fresh-target trials with **62 contracts
per trial**, **1,550/1,550 exact assertions**, 62 unique semantic contracts,
1,488 repeated reliability observations, and zero browser errors. Mean/p95
trial duration was 6,000.30/6,220.17 ms. The expanded contracts cover the clear
initial camera, physically locked home, unlocked genkan exit, active clock,
audio state, dash, rare scoring, all tiers, Skytree lock/readiness/absorption,
result/share fields, resources, and errors. Debug fixed-step acceleration still
makes this functional reliability evidence rather than real-time performance.

Five complete real-rAF performance attempts were retained. Four passed all 35
gates; attempt 4 passed 34/35 and failed only 'streaming.heap-plateau', where
heap changed from 6,703,757 to 11,311,984 bytes. Across all five attempts, tier
0 steady-p95 had min/mean/median/p95/max 41.1/44.98/46.6/46.8/47.2 ms, tier 5
had 30.9/40.24/43.8/44.4/48.3 ms, and streaming p95 had
34.1/37.36/36.6/38.8/41.9 ms. Streaming distance had
4.723/5.130/4.775/5.544/5.883 km for the same distribution fields. Every
frame-time, pool, draw, geometry, texture, minimum-distance, and browser-error
gate passed in all five attempts. All outcomes remain in the 4/5 denominator;
the compact machine source is
'comparison/data/v2-browser-performance-campaign.json', with raw run artifacts
retained under '/tmp/sol-v2-perf-campaign-final'.

The counterbalanced Codex/Fable stationary A/B evidence is unchanged: five
600-frame trials per site all passed, with aggregate p95 44.1/45.1 ms and draws
14/44. It remains a page-level stationary comparison over unequal content, not
a clear-time, visual-quality, engine, or debugging-productivity verdict. The
ten-attempt v2 performance target and public immutable/stable Pages verification
remain open at this local checkpoint.
