# Portal browser port — rolling changelog

## Current target

Run the original campaign in a browser using a custom Rust/WebAssembly engine and raw WebGL 2. The verified route now covers `testchmb_a_00` and its original transition into the entry sequence of `testchmb_a_01`; the latter's portal-gun gameplay remains unfinished. Preview: **http://localhost:8066**. The supplied retail files remain local; no game assets are downloaded or replaced with generated art.

**Fidelity status: early implementation; not retail-equivalent.** Loading original assets does not establish matching rendering, behavior, audio, or physics. Zero-exception parity remains the goal, not an achieved claim.

## Continued iteration

- **Development resumed at the user's request on2026-09-13.** Completed bounded objectives: **18 / 18**, active none; stopped at the renewed bound. Objective15 adds actual a00→a01 transition/entry; objective16 adds original world gun export/touch-driven pedestal opening; objective17 adds bounded source-authored holder rotation/parenting and original base collision; objective18 fixes authored portal-slot initialization/lifecycle without implementing weapon shots. Gun pickup/firing/placement and the remainder of a01 remain unimplemented. The local preview server is running.
- The first-map desktop keyboard/mouse route now passes continuously through the opening, both cube/button puzzles, cycling portals, emancipation fields and both elevators to the final in-map endpoint. The original countdown clocks and independent indicator circuits are implemented; related feature checks cover desktop/mobile. This is not an uninterrupted mobile playthrough or retail-parity certification.
- Remaining work includes cube dissolution visuals, full Source physics/material/lighting/audio equivalence, a running retail reference and gameplay beyond the entry doors of `testchmb_a_01`. The campaign port is not complete.
- Final parent review on2026-09-13 checked every workflow verdict, including repaired audit failures and independent acceptance of objectives14–18; inspected campaign-entry and desktop/mobile pedestal images; confirmed page/Wasm HTTP200 at17:27UTC. Latest evidence is under `target/objective18-20260913T160528Z/` and `target/objective18-independent-final-20260913T170843Z-6528ba4a/`. `ITERATION.md` records18/18 and no active implementation. No recurring task remains; preview health monitoring continues.

## 2026-09-15 — Preview server maintenance

- Restored the expired preview after checking port8066 was free, replaced the expired monitor, and confirmed page/Wasm HTTP200 at14:00UTC. The watcher process remains active despite the shell setup warning. Game files/build are unchanged; automatic implementation remains stopped at18/18.

## 2026-09-14 — Preview server maintenance

- Restored the expired local server after confirming port8066 was free, and replaced the expired monitor. Page and Wasm returned HTTP200 at2026-09-15 00:58UTC. No game/build changes; the bounded18/18 implementation run remains stopped.

## 2026-09-13 — Objective18: source-backed authored portal slots/lifecycle

- Completed **one prerequisite**, not gun gameplay or expanded a01 acceptance. Recovered retail `CProp_Portal::FindPortal` selects the first active or last inactive color match within a linkage group. Runtime now uses original `PortalTwo` properties rather than a00-only targetnames, preserving authored positions. Only group zero with both authored colors is supported; other groups/partial pairs fail explicitly rather than inventing or cross-linking slots.
- a01 starts with inactive blue57/orange185 candidates. Original OnMapSpawn activates orange52 at exactly+5seconds; blue remains inactive through holder cycles. Entity-scoped stale fizzle, active Kill, restart, cross-map isolation and empty-map `portals_active=false` are covered. Six no-portal volumes, four bumpers, one portal volume and six detectors were audited, **not implemented as placement rules**.
- Retail ChargePortal1 dispatches original `portal_1_charge` at `muzzle`; FirePortal1 calls `FirePortal(false,null,false)`. Both remain explicitly unsupported. Existing SetActivatedState uses authored-center approximation; retail activation trace/VerifyPortalPlacement, unlinked-portal presentation, charge particles, pickup/inventory/viewmodel and full a01 require separate work. No fabricated shots, portal destinations or inactive scene callbacks.
- Passed **136native/82compiler/3inspector/4timing**, release Wasm, all12existing browser suites,6pedestal/16package/4campaign desktop/mobile groups, lighting/clock and32original clock-frame comparisons. Unchanged a00 route:23checkpoints,6crossings,74first/73outgoing/63shutdown samples,time227.21667851693928. Ordinary campaign route:24checkpoints to the existing entry-door boundary. Actual HTTP/texture/linkage/missing-color failures, retry, restart and shared-map cleanup pass;332oldGPU allocations released in each view.
- All5078supplied files and315exported assets, including209a00 assets, remain unchanged. Raw Rust/Wasm/WebGL2 retained. Reused preview PID88734; five resources HTTP200/disk-identical. Desktop/mobile pedestal and error screenshots inspected. No VM/install/credential use/download or independent retail execution.
- Evidence: `target/objective18-20260913T160528Z/acceptance-evidence.json`, `portal-source.json`, `final-commands.json`, `fresh-result-review.json`, `retained-failures.json`, `final-protected-audit.json`, `a00-route/`, `campaign-route/`, `pedestal-recheck/`, `packages/`, `campaign/`.27recorded successful check commands and1retained corrected browser expectation failure: restart resets slots but intentionally keeps diagnostic event history. Two evidence-tool failures retained (Wasm package rewrite race; unavailable optional PIL contact-sheet dependency); no installation or runtime workaround.
- **Count17→18, ownership cleared; STOP at18/18.** On explicit resumption, prioritize one source-backed placement-validation or unlinked-portal/charge-presentation prerequisite before weapon shots. No full-a01, independent-reference, retail-parity or zero-bug claim.

## 2026-09-13 — Objective17: source-authored rotating holder and parents

- Implemented one bounded holder slice, **not gun gameplay or expanded a01 acceptance**. Supplied BSP49 and retail CRotDoor/AxisDir/SetParent instructions establish the original pivot, non-solid yaw helper,90-degree turn at30deg/s, detach on fully open, +0.1-second return at50deg/s and re-parent without snapping the gun/center world pose. The next cycle adds another90degrees. Exact completion times7/8.9/14seconds and speed capture pass native and real-time desktop/mobile tests.
- Shared raw-WebGL2 transforms apply to mesh positions/normals, transparency ordering and dynamic illumination positions; fixed lighting origins remain fixed. Source helper/center stay non-solid; the separate holder brush is preserved and the original pedestal-base PHY now exports through existing bone-collision support. Solid rotating-door pushing/crushing, arbitrary attachment collision and pickup-driven pedestal removal remain unverified.
- Preserved keyboard/touch pedestal opening, pause, restart and cross-map isolation. Browser verification asserts actual gun/center GPU matrices and visually inspected desktop/mobile captures, using explicitly labeled inspection positioning. Unsupported ChargePortal1/FirePortal1 are diagnostic warnings, never fabricated shots or placements; inactive gun-scene callbacks and the visible entry-door boundary remain intact.
- Passed **132native,82compiler,3inspector,4timing**, release Wasm, all12existing browser suites, lighting/clock desktop/mobile and32original clock-frame comparisons. Fresh unchanged a00 route:23checkpoints,6crossings,74first/73outgoing/63shutdown samples,time226.73334515839815. Ordinary campaign entry:24checkpoints. Desktop/mobile HTTP/texture failure, retry, restart and audio/GPU cleanup pass;332oldGPU allocations released in each view.
- All5078supplied files and209a00 assets unchanged. Onlya01 chamber.json changed to add original base PHY; no replacement art/audio or architecture change. Existing preview PID88734 serves five checked resources HTTP200/disk-identical. No VM, credential use, external retail acquisition or explicit tool installation was requested.
- Retained one new browser-test failure: restart correctly replays the original time-zero unsupported charge, whereas the initial assertion expected no warnings. Corrected only that expectation; full fresh serial recheck passes. **26successful recorded commands,1retained failure.** Evidence: `target/objective17-20260913T144647Z/acceptance-evidence.json`, `final-commands.json`, `holder-source.json`, `source-audit.json`, `pedestal-recheck/`, `a00-route/`, `campaign-route/`, `campaign/`, `packages/`.
- **Count16→17, ownership cleared; stop after this objective.** Next is one source-backed weapon/portal prerequisite, with original placement constraints and no guessed FirePortal1 result. Pickup/inventory/viewmodel, full a01, independent retail reference and parity remain unverified.

## 2026-09-13 — Objective16: original gun export and authored pedestal opening

- Implemented one bounded prerequisite, **not gun gameplay or expanded a01 playability**. The compiler resolves model-less `weapon_portalgun` from the supplied weapon script's `playermodel`, exporting the original `w_portalgun.mdl` at BSP origin/angles (3992triangles). Missing/unsafe script paths fail instead of substituting a model. Both pedestal models now export original local-bone clips; center touch uses existing authored `trigger_open_holder → SetAnimation(open)` and original `Buttons.snd22` audio.
- Source sampler/compiler checks cover fractional cycles for both pedestal models. Desktop/mobile keyboard/touch contact verifies the exact71-frame,30fps opening (70/30seconds), once-only sound/completion, pause, endpoint, re-entry and restart. Setup is explicitly resource inspection, not an ordinary campaign route to the pedestal. Captures show the original gun and pedestal; lighting/visual parity is not certified.
- Passed **128native,82compiler,3inspector,4timing**, release Wasm and fresh serial browser checks. Unchanged full a00 route:23checkpoints,6crossings,73first/72outgoing/63shutdown samples,time228.25001190416515. Ordinary campaign route:24checkpoints through original transition/entry-door boundary. All12existing browser suites,12package groups,4campaign groups, lighting/clock desktop/mobile and32original clock-frame comparisons passed. HTTP/texture failure, same-transfer retry, restart/shared-state and disposal passed;332oldGPU allocations released on desktop/mobile.
- All5078supplied files and every a00 asset remain unchanged. Only a01 chamber.json/chamber.bin/vertex-lighting.bin changed, with one new original gun texture export. Raw Rust/Wasm/WebGL2 retained; no generated art/audio, VM, install, credential use or external download. Preview PID88734 reused; five resources HTTP200/disk-identical.
- Preserved two new native-test failures: incorrect31-frame expectation (source has71), then scheduler-time setup before trigger contact. Corrected tests, not runtime timing. Current evidence: `target/objective16-20260913T135815Z/acceptance-evidence.json`, `source-audit.json`, `final-commands.json`, `pedestal/`, `a00-route/`, `campaign-route/`, `campaign/`, `packages/`, `artifact-hashes.json`.30successful commands and2retained failures.
- **Count15→16, ownership cleared; stop after this prerequisite.** Visible entry-door boundary remains. World gun is initial pose only; rotating holder/parent/completion-I/O, pickup/inventory/viewmodel, firing, constrained placement and animated pedestal collision remain unsupported. Inactive gun-scene callbacks are unchanged. Next priority is one source-backed rotating-holder slice, not full-a01 or retail-parity claims.

## 2026-09-13 — Objective15: authored campaign transition and bounded a01 entry

- Real outgoing train node187 OnPass requests exactly one map change. The handoff derives **[1216,320,-1152]** from matching original landmarks, preserves the player pose and name-matched incoming elevator globals, and starts **OnMapTransition**, not OnNewGame. Parented geometry/collision retains carried offsets and animation phases; no guessed a01 spawn or reverse-node initialization. The original +1-second incoming door opening is verified.
- Map-scoped loading now replaces Engine/resources in the same document, stops old audio/captions/events, and explicitly releases renderer GPU objects. Desktop/mobile instrumentation measured **332 old allocations, zero remaining**, after the switch. Actual HTTP503/texture failures freeze the old map with an error, Try again retries the same transfer, and Restart campaign returns to a00. Pause, entry speech and restart/shared-state checks pass.
- Fresh continuous desktop keyboard/mouse route passes **24 checkpoints** from a00 opening through both puzzles, six portal crossings, outgoing OnPass, landmark arrival, original entry trigger, **21.038890838623047-second** scene completion and both entrance doors. No pose/advance/step/forced I/O/document reload in that route. Separate a00 regression retains all puzzle, train speed/delay/node/height/opening assertions with a clearly labeled verification-only handoff hold: **23 checkpoints**,6crossings,73first-ride/72outgoing/63shutdown samples, final time227.93334522098303, no errors.
- Final checks pass: **127 native,80 compiler,3 inspector,4 timing**, release Wasm, all12existing browser suites,12package groups, desktop/mobile lighting/clock and32original clock-frame comparisons. All **5078 supplied files** and **314 exported asset files** are unchanged. Reused preview PID88734; six resources HTTP200/disk-identical. No VM/install/download/reference execution.
- Retained failures: an old elevator assertion still required unsupported ChangeLevel; corrected only the superseded expectation/verification URL, and preserved structural transition events during inspection advance. An added screenshot mouse-aim controller failed after menu interaction; original failure and incidental ceiling images remain. Final door screenshots use an explicitly labeled same-position inspection camera (entry movement stays keyboard/touch), showing retracting leaves from the partly occluded trigger viewpoint.
- Evidence: `target/objective15-20260913T123802Z/acceptance-evidence.json`, `final-commands.json`, `a00-route-recheck/`, `campaign-route-recheck/`, `campaign-camera-recheck/`, `supplied-source-audit.json`, `health-final.json`.40successful recorded commands and2superseded failures preserved. **Count14→15, ownership cleared; stop after this objective.**
- **Boundary:** arrival/entry only, visibly labeled before the unsupported gun puzzle. General save/restore/transitions, reverse campaign, carried props, full a01, gun/pedestal/placement and retail equivalence are not certified. Existing light/tonemap/physics/audio/rendering fidelity gaps remain. Next bounded priority is source-supported gun/pedestal work, not a claim of completed a01.

## 2026-09-13 — Objective14 bounded source-audit repair; count14 unchanged

- Independent rejection was an incomplete **loose-file-only audit**, not a demonstrated application defect. Reproduced its exit1: `usetoilet_flush1.wav` is absent loose but present in supplied `portal1-retail/hl2/hl2_sound_misc_000.vpk` at offset109424826. Its328988bytes, CRC3398a55f and exported SHA-256 `d4cb59b9c2c105b64ae4d8384b166cc2348853d85109feb21be3825e14afd73a` match exactly. Original failed audit and earlier verifier failures remain preserved.
- Completed independent archive/BSP-aware verification for **28a00 and24a01 sound files**, both byte-identical fresh inspector sequence exports, source identities/CRCs, speech mappings and original inactive a01 callbacks. All5078supplied retail files and205existing a00 binary/texture/sound files match pre-objective hashes. All5467application/test/build/asset/source files remained unchanged during this repair; no engine, harness, Wasm, supplied asset or generated-art changes.
- Fresh unchanged full a00 route ran first and alone: **23checkpoints,6crossings**,74first-ride/73outgoing/63shutdown samples, final time227.4166785273701, no errors. Serial package12desktop/mobile groups, core7, controls4 and126native/80compiler/3inspector/4timing tests passed. Package checks exercised audio decoding, load failures/retry, pause/restart and document isolation. Core/controls ignore PORTAL_VERIFY_OUTPUT; exact tests were rechecked from fresh isolated working directories without changing assertions, and legacy/first-run artifacts were preserved separately.13commands exited0; the loose-only reproducer exited1 as expected; no unexpected check failures.
- Endpoint, mobile resource-only menu and injected HTTP503/retry screenshots inspected. Final existing preview PID88734 served nine checked resources HTTP200/disk-identical; no duplicate server or remaining workspace browser worker. Evidence: `target/objective14-audit-repair-20260913T120743Z-bd1408/acceptance-evidence.json`, `source-package-audit.json`, `commands.json`, `isolated-commands.json`, `post-isolated-source-audit.json`, `health-final.json` and `final-result-review.json`.
- Count **14→14**, ownership cleared; **stop this repair, no objective15 work**. Raw Rust/Wasm/WebGL2 preserved. No VM/install/credentials/download or retail execution; actual transition, a01 playability and retail parity remain unverified.

## 2026-09-13 — Objective14: map-scoped source packaging prerequisite

- Added explicit per-map sequence/audio resource paths and a separate `web/assets/maps/testchmb_a_01/` package without replacing a00. Compiler requires the correct non-a00 sequence, checks map/CRC/scene membership/speech and preserves original inspector decimal bytes. Browser checks package identity and required scene/speech resources before creating Engine; runtime rejects a foreign map sequence and reports missing scenes.
- Fixed local source-data resolver coverage for supplied loose Portal/HL2 scripts and sounds. `Buttons.snd22`, `Doors.FullClose8`, `Doors.FullOpen9`, and `Weapon_CombineGuard.Special1` now resolve to original sound files; both exports have zero audio warnings. No substitute audio or generated art. Every exported sound was compared byte-for-byte with its supplied source and decoded by browser Web Audio.
- Browser geometry, lightmaps, textures and audio now use the selected package root. `?verify=1&map=testchmb_a_01` is explicitly labeled **resource inspection only**, not campaign arrival/playability. Ordinary URL remains a00. Actual desktop/mobile missing-sequence HTTP503, foreign-sequence and failed-texture cases show errors and recover through Try again; pause, mute, restart and sequential a00→a01→a00 document isolation passed. This is not a seamless transition/resource-disposal claim.
- Fresh verification passed: **126 native,80 compiler,3 inspector,4 timing tests**, release exports/Wasm, all12 original serial browser suites plus package/lighting/clock checks;32 original-frame clock comparisons. The unchanged full a00 keyboard/mouse route ran first and alone: **23 checkpoints,6 crossings,73 first-ride/73 outgoing/63 shutdown samples**, final time228.00001189112663, no errors. Desktop/mobile boundary/error screenshots and a00 endpoint inspected.
- Preserved initial test-access compile failure, overbroad inspector KeyValues failure, cancellation test bypassing scheduler, inactive-actor callback expectation failures, and audit-tool failures. The gun-scene OnTrigger1/2 times exist but their authored actor is inactive: metadata and existing runtime filtering are preserved, not forced active. Packaging's JSON reserialization introduced tiny decimal changes; final packaging retains source sequence bytes exactly, with unchanged a00 scene events/sound mappings.
- All **5078 supplied retail files** and existing a00 binary/texture/sound assets are hash-identical. No physics, geometry, portal, field, indicator, clock, ambient or train implementation was changed. Evidence: `target/iteration-14-packaging-20260913/acceptance-evidence.json`, `verification-01/`, `route/`, `packages/`, `regressions/`, `source-package-audit-final.json`. Existing preview PID88734 reused; page/Wasm/packages HTTP200 and disk-identical. No VM/install/download or independent retail execution.
- Count **13→14**, ownership cleared, bounded work stopped. **Next: actual authored ChangeLevel with landmark delta[1216,320,-1152], incoming global elevator and OnMapTransition lifecycle, cleanup/error recovery, then a01 entry speech/door gate.** Gun mechanics remain later. No retail-parity or zero-bug claim.

## 2026-09-13 — Continued development: moving-prop ambient lighting

- User resumed development beyond the previous12-objective stop. The next bounded run targets objectives13–18 with fresh verification workers; exact retail parity and zero-bug status remain goals, not achieved claims.
- Fixed moving cubes retaining their original spawn-point lighting. Native export now preserves the BSP ambient node/leaf structure; runtime uses the current prop illumination position, exact same-leaf inverse-distance-squared-plus-one weights, one-hop leaf references and explicit zero fallback. This behavior was recovered from the supplied retail `Mod_LeafAmbientColorAtPos` instructions.
- Use authored MDL illumination positions or resolved nonempty entity overrides. Spawned props copy lighting metadata, renderer uses per-instance live cubes with animated normals, and removed-object cache entries are pruned. Native baked VHV static colors are preserved. Cube lighting now changes when carried through the room or portals instead of retaining dispenser colors.
- Added/tested a raw worldlight parser/evaluator and recovered ambient-inclusion flags. An experimental model-pivot direct-light fold overlit the button; it was **removed**, not hidden by brightness tuning. Proper selected per-vertex direct lights remain next work. See `docs/PROP_LIGHTING.md` and `docs/AMBIENT_LIGHTING.md` for exact evidence and limits.
- Passed **124 native tests,79 compiler tests,3 inspector tests**, release export/build, all13 serial browser suites including new desktop/mobile live-uniform/light-sampling checks, the full ordinary-input route (**23 checkpoints,6crossings,no errors**) and32clock pixel comparisons. Final empty-override/cache review fixes were rechecked with native/compiler/build and targeted browser tests. Evidence: `target/iteration-13-lighting/acceptance-evidence.json`; rejected experiments and failures are explicitly retained there.
- Parallel campaign audit compiled a01 into isolated target output and recovered the authored landmark translation and entry-scene requirements. It confirmed the supplied i386 Mac retail build cannot run natively on this ARM64 macOS host; an existing Windows VM is a possible but unverified reference route. No VM, installation or external download was started. Details: `docs/NEXT_CAMPAIGN.md`.
- Count advances12→13 for the bounded ambient correction. Next work is a00→a01 transition through original entry dialogue/door behavior; full a01/gun mechanics, direct lighting, dissolution and retail comparison remain unfinished.

## 2026-09-13 — Preview server maintenance

- Restored the local server after its ten-hour task expiry, confirmed there was no competing listener, and replaced the expired monitor. Page and Wasm returned HTTP200 at07:01UTC. The game build is unchanged and implementation remains stopped at12/12.

## 2026-09-12 — Preview server maintenance

- Restarted the local server after its ten-hour background-task expiry and replaced the expired health monitor. Page and Wasm URLs returned HTTP200 at20:59UTC. No game files or build changed; the completed12/12 implementation run remains stopped.

## 2026-09-12 — Iteration12 bounded evidence repair; count12 unchanged

- **Confirmed cause:** `target/iteration-12-clock/regressions/playthrough.json` was an unchanged historical iteration7 failure (`page.screenshot` timeout30000ms, older first-elevator objective,11 checkpoints including failure), not a current clock regression. Its SHA-256 `a1d90554fa4a34ebaf0a5981109de8ca6186a0360a1f5bc4d1a50581ddb295ac` exactly matches `target/iteration-07-timing-recovery/entry-failure/playthrough.json` and the shared `verification/playthrough.json`. The regression snapshot included stale files; `npm run verify` does not execute the continuous playthrough, and the acceptance manifest omitted this failure. No clock/runtime defect was reproduced.
- **Correction:** preserved the failed artifact byte-for-byte and archived the original acceptance manifest. Corrected `target/iteration-12-clock/acceptance-evidence.json` to account for the historical failure, distinguish the mixed-age bundle from authoritative current results, and reference fresh per-command output hashes. Reruns used empty per-suite directories rather than copying the shared verification directory. No engine, controller, assertion, timeout, physics, geometry, Wasm or asset changes.
- **Exact fresh verification:** `node --check tools/verify-playthrough.mjs`; `node --test tools/elevator-timing.test.mjs` (**4 passed**); isolated unchanged `PORTAL_VERIFY_OUTPUT=target/iteration-12-clock/evidence-repair-20260912/route node tools/verify-playthrough.mjs` (**23 checkpoints,6 crossings,74 first-ride samples,72 outgoing samples,63 shutdown samples,no errors**, final time227.3166785221547). All original five-second departures,65-unit/s motion,576/65 travel, node times and arrival+1 openings remain asserted. Only expected warning: unsupported `testchmb_a_01` ChangeLevel.
- The route completed before all12 existing browser suites ran serially: core7, interactions5, animations8, dispenser6, camera2, elevator4, upper2, cleanser7, fields4, portal-edge28, controls4, opening7 checkpoints. Then `PORTAL_VERIFY_OUTPUT=target/iteration-12-clock/evidence-repair-20260912/clock node tools/verify-clock.mjs` passed desktop/mobile real-time initialization/countdown/pause/resume/stop/restart and ordinary portal traversal; `python3 tools/verify-clock-pixels.py target/iteration-12-clock/evidence-repair-20260912/clock` passed all32 exact original-frame comparisons, minimum correlation0.8859871242291186. Digit contact sheet and route endpoint screenshot inspected. All17 commands exited0; no overlapping workspace browser suites or unaccounted fresh failures.
- **Evidence and scope:** `target/iteration-12-clock/evidence-repair-20260912/{cause.json,commands.json,completion.json,route/,regressions/,clock/,protected-source-audit.json,preview-health-after.json}`.276 protected source/test/build/exported-asset files and5078 supplied retail files remain hash-identical. Native103/compiler60/inspector3 remain prior independent results, not rerun for this evidence-only correction. Rust/Wasm and raw WebGL2 retained; no engines, ThreeJS or generated replacement art. Existing `tools/serve.py` PID43705 reused; page and Wasm HTTP200 with served bytes matching disk at16:27:37UTC. Ownership cleared, **count12→12 /12**, **STOP**; no next objective or new fidelity claim.

## 2026-09-12 — Iteration12 verified: original opening countdown clock

- Exported all original VTF digit frames. Both original clock instances now follow map-authored `SetTextureIndex`, `IncrementTextureIndex`, `SetMaterialVar` and `StartFloatLerp` outputs in shared main/portal rendering. No replacement art, supplied geometry, physics, field or retail-asset edits.
- Public Valve code and matching retail client/server disassembly establish byte texture-index increments, modulo binding, the authored wrap-key mismatch, parent/material isolation, integer endpoints, clamped f32 interpolation, first-material-bind start time and deliberately ignored float-lerp loop flag. Original initialization0.2, enables6.2/7.1, first tick7.2 and stop66.2 remain scheduler-driven, not an invented countdown timer. General proxies and retail PVS/bind-cadence parity remain unverified.
- Passed **103 native,60 compiler,3 inspector,4 timing-helper tests**, asset/Wasm release builds, all12 existing serial browser suites and the unchanged full ordinary-input route (**23 checkpoints,6 crossings,no errors**, final time227.450012). Preserved timing helper, source-derived field math/random-stream limitations and all portal/route fixes.
- New desktop1440×900/mobile390×844 real-time checks pass initialization, countdown, pause/resume, stop, restart and ordinary keyboard/mouse traversal to the second clock. **32 digit-only projected comparisons** select the exact original texture frame, minimum correlation0.8816; desktop/mobile full captures and contact sheet inspected. Compiler checks every exported clock PNG against original VTF pixels. No retail-parity claim from asset reuse.
- Preserved tooling failures: initial compiler-test frame-index type mismatch; unavailable Pillow/local Python expat installer error; test projector omitted repeating BSP UVs. Corrected tooling without changing game physics/geometry or weakening frame-identity/contrast assertions. Pixel analysis now uses only Python's standard library.
- Evidence: `target/iteration-12-clock/acceptance-evidence.json`, `source-checkpoint.json`, `authored-clock.json`, public sources/retail disassembly, test/build logs, `clock-run-01/clock.json`, `clock-run-01/pixel-checks.json`, `clock-run-01/digit-contact.png`, `run-01/playthrough.json`, `regressions/`, `protected-change-audit.json`, `preview-health.json`. Existing `tools/serve.py --port8066` PID43705 remains healthy; page/Wasm HTTP200. Count **11→12 /12**, ownership cleared; **stop the bounded run**. Remaining work includes blocked dissolution, simplified cube/portal physics, lighting/audio/general proxies, executable retail reference and unsupported ChangeLevel.

## 2026-09-12 — Indicator visual recovery

- Diagnosed the black upper sign as a pre-existing BSP normal-orientation defect, not missing light or bad second-frame texture data. The face's plane index already selects a signed normal; applying `side` again flipped negative-facing triangles and made culling discard them. Upper sign2480 and wall1553 were independently checked against original HDR/LDR planes and authored vertex normals; the lower sign's positive plane had escaped the error.
- Corrected `face_normal` to use the stored oriented normal. Existing triangle orientation and overlay bias now follow the visible side. Backface culling stays enabled; there is no two-sided rendering workaround, generated geometry, exposure adjustment, or supplied-asset edit. Both signs, the upper wall and many previously missing negative-facing surfaces now render normally.
- Kept the partial original-frame circuit implementation: lower8 and upper7 indicator targets respond independently to original outputs. Strengthened browser tests to inspect only the projected sign pixels, requiring blue/amber content in the sign itself rather than letting changing doors satisfy a whole-image comparison. Desktop/mobile blue-X/amber-check images were inspected; activation, release, cube input, pause, restart and circuit isolation passed.
- Added a fixture-wide signed-plane/original-normal regression across HDR and LDR, plus explicit upper/lower checks. Vertex count remains454536; authored UV/lightmap/normal associations stay attached to their reordered vertices. Static code review passed for the bounded indicator/culling correction. Details: `docs/INDICATOR_VISUAL_RECOVERY.md`; failure/probe/results preserved under `target/indicator-visual-recovery/`.
- Final checks passed: **101 native engine tests,60 compiler tests,3 inspector tests**, all12 serial browser suites and the complete ordinary-input route (**23 checkpoints,6 portal crossings,no errors**, final map time227.925012). Actual desktop/mobile sign crops pass blue/amber content assertions, and full-route screenshots now show the previously missing walls. Count advances10→11 for the verified indicator/culling recovery. Cube dissolution, countdown clock and campaign transitions remain unfinished.

## 2026-09-12 — Iteration 11 partial: independent indicator frames, visual acceptance blocked (superseded by recovery above)

- **Not completed; count remains10 /12.** Bounded source recovery preserved the original server cleanser call to `CBaseAnimating::Dissolve` at0x6d7f3d, decoded PCF definitions and public Valve dissolver sources. Portal-specific child/attachment/model-fade semantics were not recovered sufficiently; generic SDK timing was not substituted. Dissolution gameplay, removal/output/replacement timings and existing field mathematics remain unchanged.
- Implemented the permitted separate indicator fallback: decode the original second VTF frame for four indicator materials; preserve each authored overlay accessor when batching; apply original binary `SetTextureIndex` outputs independently to the lower8 and upper7 circuit entities; bind the selected original frame in shared main/portal rendering. Native/compiler tests cover frame offsets/truncation, asset export, scheduled circuit isolation and restart. No generated art, supplied asset edits, geometry alterations or physics changes. Clock and general material proxies remain unsupported.
- Checks passed: **101 native,59 compiler,3 inspector,4 timing-helper tests**, assets/Wasm release builds, syntax, all12 serial npm browser suites, and the unchanged keyboard/mouse-only continuous route with read-only indicator assertions. Route run-01 reached final in-map node187 at time227.150012 with23 checkpoints,74 first-ride samples,73 outgoing samples,63 shutdown samples and6 crossings; no errors. Desktop/mobile upper tests cover independent circuit states, player release, cube E/touch input, pause and restart. Geometry audit preserves454,536 vertex/light samples; existing binaries are unchanged and only overlay batch ownership is split247→257 meshes.
- **Visual acceptance failed despite passing assertions.** Lower screenshots show original blue-X/amber-check transitions. Upper screenshots show changing floor indicators but a black/unreadable sign and surrounding wall. Moving the inspection camera to the accepted corridor centerline and rerunning the upper desktop/mobile suite did not resolve that visual finding. The defect is not proven newly introduced. Whole-image differences also include doors and are insufficient proof of upper sign rendering; no full presentation or retail-parity certification is made. No test weakened or geometry/physics workaround attempted.
- Evidence: `target/iteration-11-presentation/source-checkpoint.json`, `checks/commands.json`, `browser-commands.json`, `run-01/playthrough.json`, `geometry-audit.json`, `regressions/`, `initial-visual-capture/`, `upper-capture-recheck.log`, `upper-visual-contact.png`, `blocked-checkpoint.json`, and `preview-health.json`. Source/runtime/renderer/compiler and harness changes remain as a partial checkpoint for bounded diagnosis. Original timing helper, particles/random-stream limitations, portal fixes and explicit unsupported ChangeLevel are preserved. Preview page and Wasm returnedHTTP200 from existing `tools/serve.py --port8066` PID43705. Ownership cleared; immediate priority and completed count unchanged; stop here.

## 2026-09-12 — Iteration 10 recovery: visible retail-asset fields and portal contact fix

- Recovered the missing field semantics from supplied retail binary instructions and public Valve headers: default lifetime1second, normalized-age radius scaling, smoothstep alpha/decay, local offset/velocity basis, velocity-facing trail geometry/UVs and width. `docs/PARTICLE_SEMANTICS.md` records the relevant symbols/addresses and mathematical equations. No leaked source or generated replacement art was used.
- Added a bounded native Rust binary-DMX/PCF reader and field definition export, plus raw Rust/WebGL2 particle drawing for both authored emitter entities using the original32×64 texture. Pause freezes the particles and restart resets sampling. Main and linked-portal views render the fields. Transparent surfaces and field trails share camera-depth ordering; a reviewer caught and verified correction of an initial fields-always-last blending bug.
- **Fidelity boundaries remain explicit:** the preview's deterministic uniform sampler is not the retail random table/seed; analytical emission differs from Source frame scheduling; SIMD reciprocal approximations, full gamma/fog and intersecting-transparent sorting remain unverified. Cube dissolve particles are still absent. This is source-parameter/operator implementation, not a 1:1 presentation claim.
- A fresh continuous-route rerun exposed a separate real Blue1 return defect at a +0.07-unit lateral offset. A backing brush started16 units behind the portal and touched the leading player hull before its center could cross; the old cutout selection missed it. Backing-brush selection now uses the leading hull reach and existing aperture footprint, with no changes to authored portal position/size or physics settings. Outside-aperture/distant-wall and bidirectional fractional-coordinate regressions were added.
- Combined checks passed: **100 native engine tests, 57 compiler tests, 3 inspector tests**, release asset/Wasm builds, all12 browser suites and the full ordinary-keyboard/mouse route (**23 checkpoints,6 portal crossings, no errors**). Final transparent-order rebuild, field checks, complete `npm run verify` and continuous route also passed. New field checks cover both emitters at1440×900 desktop/390×844 mobile, animated/paused states, restart, real keyboard/touch crossing and unchanged portal fizzle. New portal-edge suite covers28 desktop/mobile cases. Corrected compositing received a separate read-only PASS review; screenshots were inspected.
- Evidence: `target/iteration-10-cleanser-field/` including `compositing-route/playthrough.json`, `target/portal-edge/`, `target/workflow-acceptance-full-map/` (preserved failure), `verification/fields.json` and `verification/field-*.png`. Count advances9→10 for this bounded verified implementation; remaining fidelity differences are not certified away.

## 2026-09-12 — Iteration 10 blocked: cleanser-field source checkpoint (superseded by recovery above)

- Investigated only the immediate cleanser-field presentation objective. Claimed ownership before inspection artifacts; no engine, compiler, harness, web runtime, Wasm, compiled geometry, gameplay timing, or supplied retail file changed. No field implementation or visual-fidelity claim is made; count remains **9 /12**, with the existing immediate priority unchanged.
- Recovered the actual field bindings: authored `info_particle_system` entities **289 /290**, `effect_name=portal_cleanser`, `start_active=1`, angles **[0,270,0]**, at **[-1264,-864,192]** and **[-596,-256,640]**. These differ from simply drawing the cleanser trigger bounds: brush models1/27 each have six `TOOLS/TOOLSTRIGGER` faces and no rendered mesh. Do not replace the supplied geometry or infer the upper emitter origin from its trigger center.
- A reproducible inspection script consumes all **26,772 bytes /160 elements** of supplied binary-DMX `particles/cleansers.pcf`. The field definition supplies **300 particles/s**, radius5, color **[106,179,241,255]**, additive `effects/portal_cleanser.vmt` with its existing **32×64 DXT1 VTF**, position/velocity initializers, radius scaling, alpha fade/decay and sprite-trail rendering. The field has no child edge systems and no explicit lifetime initializer. The unrelated legacy `cleanser_field_edge.vmt` refers to two textures absent from loose assets, all supplied VPK directory trees, and this BSP's embedded pak; it is not a supported replacement for the authored PCF.
- **Blocker:** the decoded parameters alone do not establish the retail constant lifetime or exact fade/radius/trail operator semantics. Relevant symbols and bounded initializer disassembly from the supplied `client.dylib` are preserved, but those semantics have not been verified. Stopped rather than introducing guessed lifetimes, substitute art, or a misleading textured plane. Next work must resolve those defaults/operators before implementing this same field-only objective; cube dissolution remains separate.
- Unchanged baseline checks passed **serially**: timing helper4, core7, ordinary controls4, cleanser7 (desktop/mobile interaction and original real-time dialogue), and the complete keyboard/mouse-only route. Route `run-01` passed **23 checkpoints /73 first-ride samples /72 outgoing-ride samples /63 shutdown samples /6 portal crossings**, ending at map time **227.550012**, position **[-382.5516,-256.0170,1172.5607]**. No JavaScript/HTTP errors; endpoint WebGL0; the only train warning is the expected unsupported `testchmb_a_01` transition. Desktop/mobile cleanser before/after and desktop endpoint screenshots inspected. These tests verify the unchanged baseline, not the missing presentation. No native/compiler rebuild was required or run because their inputs were untouched.
- Evidence: `target/iteration-10-cleanser-field/inspect_cleanser.py`, `source-checkpoint.json`, `cleansers-decoded.json`, `brush-material-audit.json`, `retail-binding-audit.json`, `retail-particle-*.asm`, `verification-commands.json`, `verification-summary.json`, `run-01/playthrough.json`, archived `regressions/`, `protected-source-audit.json`, and `preview-health.json`. The source audit covers56 protected files plus the two tracking documents. Reused healthy `tools/serve.py --port 8066` **PID43705**; `/` and `/pkg/portal_browser_bg.wasm` returned **HTTP200** after checks. Active ownership cleared on exit; no retry loop or next objective started.

## 2026-09-12 — Scheduled iteration 9: continuous final in-map elevator checkpoint

- Extended `tools/verify-playthrough.mjs` from the accepted cube-held upper doors through a fresh authored Blue2 phase, blue-only exit shutdown, three full disabled-cycle phases, empty-handed upper-cleanser passage, ordinary walk-on outgoing-elevator boarding, and the final in-map endpoint. The accepted opening/first-room/plinth waypoints, first-elevator assertions, movement helpers and `tools/elevator-timing.mjs` are unchanged. All movement/look/use remains browser keyboard/mouse with read-only `state()` / `entities()` observations.
- Verified the original outgoing train `body` (201), nodes **191 → 192 → 189 → 187**, five-second departure delay, **65-unit/s** motion, exact per-node OnPass times, **576/65** travel, arrival+1 door opening and solid doors during transit. Preserved the original outgoing stop relay's `elevator_body_middle,Stop` target. The final train stops at **[-384,-256,1240]** and explicitly reports **Map transition to testchmb_a_01 is not implemented**; no next-map transition or retail parity is certified.
- Fresh Chrome desktop route passed with **23 checkpoints / 74 first-ride samples / 72 outgoing-ride samples / 63 shutdown samples / 6 portal crossings**. Final map time **227.500012**, player **[-382.5558,-256.0127,1172.5607]**, outgoing passenger height drift at most **0.00006104 units**. Both original cubes stayed undisturbed on their buttons, all four named exit panels remained open, and both portals stayed inactive after the cleanser. JavaScript/HTTP errors were empty; checked WebGL errors were0; the only train warning was the expected unsupported ChangeLevel.
- Preserved two controller failures in `run-01/` and `run-02/`: the new corridor-floor waypoint actually reached the original elevator cap at Z584.80, and the endpoint assertion counted all nine runtime doors as the four puzzle panels. Corrected the waypoint to X=-565 while retaining the Z576 floor assertion; asserted all four named puzzle panels plus the unchanged total door count. No game defect was established and no physics, geometry, engine, compiler, web runtime, Wasm or retail asset changed.
- Passed syntax checking, timing-helper tests (**4**), corrected full continuous route, and serial core (**7**), ordinary controls (**4**), elevator (**4** groups), upper (**2** desktop/mobile groups) and cleanser (**7** groups) regressions. Native/compiler tests and Wasm rebuild were not rerun for this test-only change. Source fingerprints confirm only the playthrough harness changed among53 protected files. Desktop final-endpoint and mobile elevator-arrival screenshots were inspected.
- Evidence: `target/iteration-09-outgoing-route/acceptance-evidence.json`, `run-03/playthrough.json`, route screenshots, `route-summary.json`, `regressions/commands.json` and archived results/screenshots, `timing-helper.log`, `input-source-audit.json`, `playthrough.diff`, and preserved failed runs. The continuous route remains separately invoked and desktop-only; mobile regression inspection setup is not mobile continuous-play certification. Cleanser field/dissolve presentation remains absent.
- Reused healthy `tools/serve.py --port 8066` PID43705. Preview `/` and `/pkg/portal_browser_bg.wasm` returned **HTTP200** at **2026-09-12 11:51:20 UTC** and **12:11:56 UTC**. Completed count advances **8 → 9 /12** only after the checks passed; active ownership is cleared. Immediate next objective is source-authored cleanser field presentation at both existing volumes, not another map.

## 2026-09-12 — Scheduled iteration 8: continuous upper cube/button checkpoint

- Extended `tools/verify-playthrough.mjs` from the accepted upper walkway through natural hub landing, fresh authored Blue3/Blue1/Blue2 cycle phases, upper-cube E pickup and held traversal, placement on the original upper button, and walking through both cube-held upper doors. Every movement/look/use remains browser keyboard/mouse; debug calls are read-only `state()` / `entities()`. The phase-aware elevator timing helper and safe first-room entry waypoint are unchanged.
- Fixed a proven game defect in `src/runtime.rs`: Blue2's wall-edge exit clearance searched only16 units relative to arrival, so an ordinary -0.303-unit approach offset could not reach a valid location within the original aperture. The search now accounts for the tangent offset and clamps candidates to the same aperture allowance. Supplied geometry, hull dimensions, portal size, movement physics and retail assets are unchanged. The failed browser run and failing native reproducer are preserved; native and desktop/mobile upper tests cover centered, +/-0.303 and +/-7-unit approaches.
- Preserved two separate controller failures: the drop waypoint straddled the original plinth edge (Z576 versus584), and the final outgoing-train assertion incorrectly expected no initial-node record. The controller now stops safely on the plinth with a height assertion and compares the outgoing train against its untouched pre-puzzle snapshot. These fixes did not alter game physics or weaken the checkpoint.
- Final fresh Chrome desktop route passed with **18 checkpoints / 74 ride samples / 6 portal crossings** at map time **182.766676**, position **[-771.9047,-256.0232,576.03125]**. Upper cube324 remained at **[-1375.9924,-574.6138,604.0313]**, lower cube323 remained on its original button, all four lower/upper exit panels were **1.0**, and the outgoing train remained untouched. JavaScript/HTTP errors and train warnings were empty; checked WebGL errors were0.
- Passed `cargo test --lib -- --include-ignored` (**95**), asset-compiler tests (**55**), inspector self-tests (**3**), timing-helper tests (**4**), Wasm release build, JavaScript syntax checks, the corrected continuous route and **all10 browser suites in `npm run verify`**, serially: core, interactions, animations, dispenser, camera, elevator, upper, cleanser, controls and real-time opening. Desktop final-checkpoint and mobile outgoing-exit screenshots were inspected. Source fingerprints show only runtime, upper/continuous tests and rebuilt Wasm changed among53 protected files; compiler, web runtime, compiled geometry and elevator helper stayed unchanged.
- Evidence: `target/iteration-08-upper-route/acceptance-evidence.json`, `run-04/playthrough.json` and screenshots, archived `regressions/`, native/compiler/inspector/timing/build logs and `input-source-audit.json`. Failures remain in `run-01/`, `blue2-reproducer.log`, `run-02/` and `run-03/`. The continuous route remains separately invoked, desktop-only, and stops just beyond the upper doors before the outgoing shutdown trigger. Final-elevator progression, unsupported ChangeLevel and retail parity are not certified.
- Reused healthy `tools/serve.py --port 8066` PID43705; preview `/` and `/pkg/portal_browser_bg.wasm` returned **HTTP200** at **2026-09-12 11:38:43 UTC** and again on exit. Completed count advances **7 → 8 /12** after passing verification; active ownership is cleared. Next objective is continuous outgoing-corridor/final-elevator progression, not expansion to another map.

## 2026-09-12 — Continued iteration and verification recovery

- Corrected test-only elevator timing and trigger-entry assumptions after independent rejection; game engine, compiler and web runtime files remain unchanged. Shared timing tests replay the actual failure and pass both active-close and idle-closed snapshots.
- Preserved all failed reruns. Further runs exposed browser/host delays rather than a proven game stall, so waypoint/ride waits now use bounded simulation progression plus explicit wall-time and attempt caps. Exact speed, arrival and arrival+1-second opening assertions were strengthened, not removed.
- Normal and delayed-boarding route variants passed. Final isolated run reached the upper walkway at **123.08334 seconds**, with **11 checkpoints / 68 ride samples**, no JavaScript/HTTP/WebGL errors; four helper tests, seven core desktop/mobile checks, four ordinary-control checks and the affected elevator regression passed. Screenshot inspected. Evidence: `target/iteration-07-timing-recovery/`.
- Restored the expired preview server. Replaced the blocked workflow with a new run that **rechecks iteration7 independently first**, then proceeds through iterations8–12 only after acceptance. Each subsequent rejection gets one bounded correction/recheck before pausing. The full map and retail fidelity remain unverified.

## 2026-09-10 — Iteration-7 verification recovery (initial findings)

- Independent verification rejected the initial elevator route: the test subtracted the close-animation duration even when the active sequence was still `close`. This inferred departure 0.633333 seconds early; the existing engine trajectory matched the original five-second delay.
- Added a shared phase-aware timing helper to continuous and isolated elevator tests, deriving clip duration and departure delay from compiled retail metadata. Exact 65-unit/s motion, arrival time, door collision and rider assertions remain enforced. Four focused tests include the captured independent failure and both close/default-pose states.
- A fresh run exposed a second controller issue: the first-room waypoint could settle short of the original release trigger. Moved the waypoint safely inside the authored volume instead of enlarging the trigger or changing physics. Each run can now use a separate output directory to prevent evidence files from being overwritten by concurrent verification.
- Engine/compiler/web source remains unchanged. Full-route and independent acceptance reruns are required before further implementation; the earlier iteration7 success entry is provisional until this recovery passes.

## 2026-09-10 — Automatic launch recovery

- The lightweight scheduled coordinator lacked a workflow-launch tool and started no worker. Cancelled that schedule instead of leaving it to fail repeatedly.
- Created and launched `portal-continuation` directly from the parent session: at most six remaining objectives, each followed by a fresh independent verification worker, with no overlapping implementation or nested launches. It stops/pauses on missing evidence, failed verification, or worker failure. Maximum12 child calls for iterations7–12.
- Workflow smoke validation passed for the canned stop path; it is not proof of live game/test success. The real workflow has started with the continuous first-elevator checkpoint as its next task. Game source was unchanged by this orchestration repair.

## 2026-09-10 — Scheduled iteration 7: continuous first-elevator / upper-walkway checkpoint

- Extended `tools/verify-playthrough.mjs` from the accepted first-puzzle exit through ordinary W-key walk-on boarding over the original static threshold, the authored elevator delay and 65-unit/s ride, arrival-gated door opening, and walking off onto the upper walkway. The opening, vault crossing, natural cube release, pickup/drop and cube-held lower doors retain their existing assertions. No engine/compiler/web source, authored geometry, player step height or retail asset changed; no game defect was established.
- Chrome **1440×900 desktop passed twice** from a fresh page using only keyboard/mouse motion, look and E use. `__portal.state()` / `entities()` are read-only observations; there are no pose, step, advance, forced-I/O, pause or restart shortcuts. Each run records **11 checkpoints and 73 ride samples**. Checks cover stationary pre-departure boarding, continuous rider support, monotonic lift motion, original nodes **174 → 175 → 173 → 171**, exact scheduled arrival, collision-gated opening and upper-walkway dismount. Maximum sampled passenger-height drift was **0.00004578 units** in both runs.
- Final checkpoint: map time **121.33334 seconds**, position **[-1153.828, -863.835, 704.031]**, one portal crossing, no held entity and no train rider after dismount. First train stopped at **[-1472, -864, 792]**; the outgoing train stayed at its initial stop. Cube 323 remained on the lower button at **[-509.247, -922.911, 156.031]**, with both lower exit-door amounts **1.0**. Every checkpoint/sample had WebGL error 0; JavaScript/HTTP errors and train warnings were empty. Boarding, arrival and upper-walkway screenshots were inspected.
- Passed commands: `node --check tools/verify-playthrough.mjs`; `node tools/verify-playthrough.mjs` **twice**; `node tools/verify.mjs` (**7** desktop/mobile core checks); `node tools/verify-controls.mjs` (**4** ordinary-route checks); unchanged `node tools/verify-elevator.mjs` (**4** result groups: desktop/mobile walk-on, timing, arrival/exit, pause/restart, real-time ride and independently triggered outgoing train); `node tools/verify-upper.mjs` (**2** desktop/mobile result groups covering cube/button, portal cycling/traversal, exit fizzle and restart). Native/compiler tests and Wasm rebuild were not rerun for this test-only extension. A **30-file** source fingerprint comparison confirms protected engine/compiler/web files, existing elevator test and package scripts are unchanged.
- Evidence: `verification/playthrough.json`, `verification/playthrough-*.png`, `verification/results.json`, `verification/controls.json`, `verification/elevator.json`, and `verification/upper.json`. Both route results/screenshots, unique command logs, accepted-route backup, scoped diff, source/input audit and `acceptance-evidence.json` are retained in `target/iteration-07-elevator-route/`. Both attempts passed; iteration-6 failure evidence remains preserved in its original archive.
- **Limits / next checkpoint:** continuous coverage ends on the first elevator's upper walkway. Upper-puzzle completion, outgoing elevator and unsupported ChangeLevel are not continuous-playthrough certified. This route is desktop keyboard/mouse, not mobile continuous-play certification; existing mobile elevator/upper regressions retain their labeled inspection setup. The route remains separately invoked, outside `npm run verify`; no retail visual/physics parity claim is made. Next extend the ordinary-input route through the upper cycling-portal cube/button puzzle to a verified cube-held upper-door checkpoint.
- Reused the healthy **`tools/serve.py --port 8066`** process (PID12977), without launching a duplicate. At **19:38:23 UTC**, **http://localhost:8066/** and **/pkg/portal_browser_bg.wasm** both returned HTTP200. Completed count advances **6 → 7 / 12** only after the above checks passed; active ownership is cleared on normal exit.

## 2026-09-10 — Rate-limit recovery

- Iteration6 stopped with HTTP429 and remains **incomplete (5/12 completed)**. It added only an unfinished ordinary-keyboard/mouse playthrough harness; comparison against the pre-iteration snapshot found no engine/compiler/web source edits.
- Preserved `tools/verify-playthrough.mjs` and its failing checkpoint without adding it to the passing test command. The initial run stopped at the vault portal with the player outside the intended approach alignment. This requires controller/game diagnosis, not a full-map success claim.
- Restored the preview after its ten-hour server-task expiry; HTTP200 plus core desktop/mobile and ordinary-route browser control suites passed.
- Replaced the recurring implementation conversation with a lightweight coordinator launching **one fresh-context workflow worker per objective**. Workers have bounded tool/read budgets and preserve progress in files rather than growing one transcript. The workflow's canned-host smoke check passed; a real fresh run was launched. This reduces accumulated context but does not guarantee external API rate limits cannot recur.

## 2026-09-10 — Scheduled iteration 6: ordinary-input first-puzzle checkpoint

- Corrected the interrupted `tools/verify-playthrough.mjs` controller without changing engine physics. Movement now releases W, waits for horizontal speed below 0.5 units/s, and rechecks each waypoint within 7 units before turning or accepting arrival; input pulses and resting positions are recorded. Portal crossing releases W even on failure and asserts exactly one crossing. Checkpoints assert WebGL error 0 and no inspection-camera override.
- Fresh exported BSP evidence identified two bad draft waypoints, not engine defects: brush 389 blocks the direct southward route at Y=-416, and entry-frame brushes 837/838 obstruct the X=-544 approach. The controller now follows the passage around the vault and centers on X=-512 through the entry. No authored geometry, player step, portal aperture, or supplied asset changed.
- Chrome 1440×900 completed the bounded real-time route **twice**: wake-up control handoff, original 66.7-second vault-portal activation, one crossing, natural first-cube release, keyboard E pickup, carry/drop on the original button, then walk through both exit panels into the corridor. The final run ended at map time **98.7667 seconds**, position **[-1105.721, -863.972, 128.031]**; cube 323 remained at **[-506.019, -922.251, 156.031]**, unheld, with both exit-door amounts **1.0**. Six checkpoints had WebGL error 0; browser/HTTP errors were empty. Portal, button, and exit screenshots were inspected.
- The final test source passed `node --check tools/verify-playthrough.mjs`, `node tools/verify-playthrough.mjs`, `node tools/verify.mjs` (**7 core checks**, desktop 1440×900 / mobile 390×844), and `node tools/verify-controls.mjs` (**4 ordinary-route checks**). Core/controls also passed before the final rerun. The playthrough invokes only read-only `__portal.state()` / `entities()`; all route motion/look/use is Chrome keyboard/mouse, with no pose, step, advance, forced I/O, restart, or pause setup. Native/compiler suites were not rerun for this test-only edit; 13 archived engine/compiler/web source files compared unchanged against the iteration-06 snapshot.
- Evidence: `verification/playthrough.json`, `verification/playthrough-*.png`, `verification/results.json`, and `verification/controls.json`. Unique command logs, the original draft, both navigation failures, and the first passing result are retained under `target/iteration-06-controller/`. The unverified later draft route remains in `verify-playthrough.before.mjs`; the executable test now explicitly ends at the first-puzzle exit instead of claiming full-map certification.
- **Limits / next checkpoint:** continuous elevator boarding/ride, upper puzzle, outgoing elevator and unsupported ChangeLevel are not covered. The route is desktop keyboard/mouse on `?verify=1` with read-only observation, not a mobile continuous-playthrough certification; ordinary non-debug controls are separately tested. The playthrough remains a separately invoked test, not part of `npm run verify`. Existing renderer/physics approximations, missing cleanser/dissolve presentation and independent retail parity remain unresolved. No new game defect was established by the controller failures.
- Existing `tools/serve.py` service was reused, with no duplicate launched. Preview **http://localhost:8066/** and actual **/pkg/portal_browser_bg.wasm** returned HTTP 200. An initial manually guessed `portal_astra_bg.wasm` probe returned 404; correcting the probe required no server/source change. Completed iteration count is now **6 / 12**; ownership is cleared after recording this checkpoint.

## 2026-09-10 — Preview maintenance and iteration-5 acceptance

- Independently reran desktop/mobile cleanser behavior and real-time speech/completion checks; they passed. Missing field/dissolve visuals remain an explicit limitation.
- Refreshed the preview server and health monitor at 08:38 UTC before their original task lifetime limit. No duplicate services were left running; page and Wasm URLs returned HTTP 200. The scheduled implementation loop remains active.

## 2026-09-10 — Scheduled iteration 5: cleanser gameplay and spawned-cube I/O

### Implemented

- Added scheduler registration for spawned `prop_physics` rows using the original lossless template metadata, a fresh clone ID and independent output fire counts. Duplicate targetnames fan out, `!activator`/`!self` resolve clone IDs, and registration does not reset the clock/queue or rerun map spawn. Invalid/duplicate rows reject atomically. The independent scheduler worker's API/tests are incorporated; other dynamic entity classes remain unsupported.
- Template sources are removed from live scheduler routing while retained as immutable spawn metadata. Replacement makers select that source rather than an arbitrary hidden/deleted clone. Actual cloned cube pickup/drop outputs now reach hint/timer targets, and pickup/drop activators use the player identity. Empty-message `env_hudhint` clears its hint instead of showing generic pickup text.
- Both original `trigger_portal_cleanser` volumes now process eligible physics props and player contacts. Props are released if held, removed from simulation/rendering once, and dispatch `OnDissolve`; only objects named `box` dispatch `OnDissolveBox`. Players fizzle active linked portals. Existing trigger flags, filters, Enable/Disable and original output wiring remain authoritative.
- First-room cube removal disables the button cube trigger and enters the map's existing dialogue/quiet relay branches. The dialogue branch releases the retained cube at actual `ai_box_fried.vcd` completion, **10.776666641235352 seconds after scene start**; the quiet branch releases it at the authored **+4 seconds**. Natural dispenser exit then closes the cover and creates one retained replacement. The upper cleanser has no invented first-room replacement output.
- Added once-per-instance `OnOutOfWorld` for Source-style ±16384 coordinate / ±2000 velocity limits, preserving the original clone-targeted Kill and drop-logic outputs. Kill clears held-object state. Added machine-readable cleanser/removal and relevant gate/scene diagnostics plus `tools/verify-cleanser.mjs` in `npm run verify`.

### Verification and explicit gaps

- **95 native engine tests**, **55 compiler tests**, **3 inspector tests**, release Wasm build and scoped formatting/syntax checks passed. The **15 scheduler tests** include six dynamic-registration cases for fresh once-counts, duplicate-name routing, clone activators/self, kill isolation, atomic failures and nonzero-clock registration. Runtime tests cover both replacement times, actual button placement/repickup, source-only template routing, clone Wake/Kill/out-of-world outputs, both cleanser volumes, disable/re-enable, noncube removal and player portal fizzle.
- Chrome **1440×900 desktop / 390×844 mobile** passed real E/touch pickup/drop and W/touch approach to the first cleanser; removal happens once, held state clears, paused state freezes, and original dialogue/quiet branches release and replace correctly. Upper-cleanser and both player-fizzle cases pass, with no fabricated replacement when the original upper entity has none. Results/screenshots: `verification/cleanser.json`, `verification/cleanser-*.png`; desktop/mobile before/after renders inspected.
- A separate desktop **continuous real-time cleanser speech/completion** run passed: the supplied crate-vaporized WAV progresses in an audio element, then source-scene completion releases the cover without inspection time advancement during that wait. Human listening/retail audio parity was not established.
- All surrounding browser regressions passed: core/first puzzle, authored animation, natural dispenser, wake-up camera, walk-on elevator and continuous ride, upper puzzle, ordinary-route controls, capture, and the full **67-second real-time opening**. Completed results report no JavaScript/WebGL/HTTP errors. A new branch-test fixture initially held the cube short of the button; it was corrected to actually drop/repick the cube, preserving the branch assertions.
- **Not a retail dissolve presentation:** gameplay removal is immediate. The original `portal_cleanser` field particles and dissolve particles/animation are **not rendered**, and the on-screen hint explicitly notes the missing dissolve particles. No invented visual art or guessed dissolve duration was added. Trigger contact remains hull/convex overlap rather than swept VPhysics; fast tunneling, additional affected entity classes, general dynamic entity spawning and full scene-actor arbitration remain incomplete.
- These route tests use inspection player poses for setup, not an uninterrupted full-map playthrough. Remaining priorities include continuous two-puzzle/elevator progression, visible cleanser/dissolve presentation, full cube PHY/angular dynamics and campaign transition. Supplied assets are unchanged; no retail-equivalence claim is made. Preview remains **http://localhost:8066** with the original server/watcher. Reversible snapshot: `target/iteration-05-before.tar.gz`.

## 2026-09-10 — Scheduled iteration 4: upper cycling-portal puzzle

### Implemented

- Fixed the upper orange portal's blocked traversal: its BSP includes a backing brush exactly **8 units** behind the portal plane, which the previous strict `<8` cutout test excluded. The existing rectangular aperture now includes that boundary with a small numerical tolerance; walls outside the aperture remain solid.
- Portal disable/fizzle inputs now affect the linked color only when they target its current entity. Fizzling an inactive predecessor no longer overwrites an unrelated live portal. The original five-second blue cycle and **0.01-second unlinked gaps** are preserved; upper exit still fizzles **blue2 only** and leaves orange active.
- Added held-prop portal-space target tracing and position/velocity mapping. The cube can cross ahead of the player, remains held through the player's subsequent traversal, and can be dropped on the far side. Loose AABB props also cross supported linked apertures. Remote holding is released in place if the portal link closes/changes, avoiding a cross-map snap to the player's new link.
- Corrected the linked-view clip-plane bias so coplanar destination wall surfaces are excluded rather than covering the upper portal view. Rendering remains raw WebGL2 and one-level, with the existing provisional rim.
- Added bounded player hull exit clearance for the authored blue2 portal at a side-wall edge. A search limited to the portal's remaining horizontal aperture width (plus contact tolerance) chooses a nonpenetrating destination; if none exists, traversal fails closed. No map geometry or authored portal position was changed.
- The original upper cube can now be retrieved through blue3, carried back through orange and into blue1, placed on the upper button, and left holding both exit panels while the player returns through the cycling portal to blue2 and walks to the outgoing elevator approach. Added `tools/verify-upper.mjs` to `npm run verify`.

### Verification and limits

- **85 native engine tests**, **55 compiler tests**, and **3 inspector tests** passed; release Wasm build and scoped formatting/syntax checks passed. New native tests cover exact cycle gaps, inactive-target fizzle isolation, held/loose cube traversal, portal-change release, upper-button door retention, and safe blue2 wall-edge exit without penetration. An inspection fixture retained walking velocity during setup; it was corrected to match browser pose setup rather than weakening its cube-location assertion.
- Chrome **1440×900 desktop / 390×844 mobile** upper-puzzle interaction checks passed: player-button press/release, original blue cycling, actual E/touch upper-cube pickup/drop, cube crossing ahead of the player, held traversal through two links, cube-held doors, five player portal crossings, walk-through exit, blue-only fizzle/cycle shutdown, outgoing elevator opening, and restart. Results: `verification/upper.json`; inspected linked-view, remote-held cube, button and exit screenshots in `verification/upper-*.png`.
- Core, first-puzzle, animation, natural dispenser, camera, walk-on elevator/continuous real-time ride, ordinary controls, and **67-second real-time opening** suites passed with no JavaScript/WebGL/HTTP errors. The new route uses **player/camera inspection poses and time advancement for setup/waiting**, while cube movement uses pickup/drop and portal simulation; it is not an uninterrupted full-map playthrough or a retail comparison.
- **Explicit approximations remain:** rectangular collision aperture versus oval visual; bounded lateral player exit adjustment; far-side held cube clamped clear of the exit plane; no split/cloned prop mesh at the portal plane; no angular dynamics or orientation transform for nonsymmetric props; no verified Source pickup distances/physics or general recursive rendering. Cube lighting still comes from its exported initial lighting and can visibly differ across rooms.
- Cleansers/dissolve/replacement dialogue, full dynamic scheduler registration, continuous two-puzzle progression, final campaign transition, and independent retail fidelity verification remain unfinished. No supplied asset files changed and no generated substitute art was introduced. Preview remains **http://localhost:8066** using the existing server/watcher. Reversible snapshot: `target/iteration-04-before.tar.gz`.

## 2026-09-10 — Elevator walk-on collision follow-up

- Fixed the jump-only boarding issue by restoring **missing original static-prop collision**, not by changing player step height, train placement, or model geometry.
- The authored `elevator_caps` PHY includes a threshold at world Z **136.745737** between corridor Z128 and elevator floor Z148.529462. The resulting **8.75-unit and 11.78-unit** rises both fit the unchanged 18-unit step. The threshold had been rendered but its collision was absent.
- Exported the retail elevator-cap and shaft-wall PHY for all eight solid6 static instances, including upper and outgoing landings: **100 convexes / 1,072 collision triangles**. Added parser shape/volume and runtime threshold/walk-on regression assertions.
- The independent investigation confirmed the train's initial +2-unit track alignment and child-parent transform were already correct. Those transforms remain unchanged.
- Desktop/mobile Chrome now boards with ordinary W/touch movement, rides to the upper walkway, exits, pauses/resumes, and restarts without jumping. The continuous real-time ride and outgoing-track checks also passed. Removed the obsolete lip `knownGaps` entry and replaced its jump workaround with required walk-on assertions.
- Final checks passed: **80 native engine tests, 55 compiler tests, 3 inspector tests**, plus the complete desktop/mobile browser suite including wake-up, dispenser, first puzzle, both elevator states/routes, ordinary controls, continuous real-time ride and 67-second opening. No JavaScript/WebGL/HTTP errors were reported.
- This correction covers the observed entry defect. Complete VPhysics, upper-puzzle and cleanser behavior, visual parity, and campaign transitions remain unfinished.

## 2026-09-10 — Scheduled iteration 3: first elevator travel

### Implemented

- Added Rust tracktrain movement for the map's unparented, fixed-orientation, instantaneous-speed `path_track` chains. Initial train origins align to p1 (`214 → 216` for the first train); `StartForward` uses authored `startspeed=65`, Stop preserves position, and every reached node emits chronological `OnPass` without replacing travel with a timer. The first route is **Z 216 → 305.390015 → 487.600006 → 792**, 576 units of travel (576/65 seconds at this implemented constant speed).
- Exported original elevator PHY collision: each body has **20 terminal convexes / 408 triangles**, each door **2 convexes / 48 triangles**. Body and door geometry/collision follow train parent translations. The retail train brush's passable spawnflag is honored; actual support comes from the authored body PHY rather than an invented platform. Door `EnableCollision`/`DisableCollision` follows original relays.
- Added ground-contact passenger displacement and vertical lift velocity inheritance on leaving/jumping, plus rising-floor contact recovery for relanding. A loose AABB cube can ride the floor. Static corridor collision remains in passenger sweeps; unsupported crush/block cases emit a diagnostic. Train-mounted children, including nested child offsets, move together. Stationary trains skip support-query work and only dynamic collision is rebuilt while this map's passable trains move.
- First-elevator entry keeps the authored **+5-second StartForward** delay. P4's actual arrival output stops the train and schedules door opening/chime at **+1 second**. Player can ride to and walk out onto the upper walkway **within testchmb_a_00**; no map transition is faked.
- The outgoing train keeps its separate name/path and original copy/paste-looking Stop target. Its isolated endpoint test emits an explicit unsupported `ChangeLevel testchmb_a_01` notice; campaign transition is not implemented. Added `verify-elevator.mjs` to `npm run verify`.

### Verification and remaining limits

- **79 native engine tests**, **54 compiler tests**, and **3 inspector tests** passed; release asset export, release Wasm build, formatting/syntax checks passed. Tests cover actual elevator PHY support/closed-door collision, source track positions, stop/start timing, four ordered node passes, parent offsets, upper exit, reset, coarse/fine scheduler times within 1e-10 seconds, jump velocity/relanding without penetration, and a loose cube's preserved relative floor height. Existing PHY volume checks now include both elevator models within the existing two-parts-per-million tolerance.
- Chrome **1440×900 desktop / 390×844 mobile** passes cover real keyboard/touch jump boarding, delayed start, constant-speed travel, frozen rider/geometry while paused, all four node passes, arrival-gated opening, independent outgoing-train state, upper-walkway exit and restart. A separate **continuous real-time desktop ride**, with mid-travel pause/resume and no inspection time advancement during the ride, passed. Results/screenshots: `verification/elevator.json` and `verification/elevator-*.png`; desktop/mobile arrival and exit screenshots inspected.
- Core, first-puzzle, authored animation, natural dispenser, wake-up camera, ordinary-route controls, capture and full **67-second real-time opening** regressions passed. These tests use inspection poses for initial setup; this is not an uninterrupted two-puzzle/full-map playthrough. Final browser results report no JavaScript/WebGL/HTTP errors.
- **Reproduced entry mismatch:** the decoded elevator floor/lip is about **20.5 units above the corridor**, exceeding the current 18-unit player step. Ordinary walking stops at the lip; a normal jump boards successfully. No retail shape was lowered and no larger step was invented to hide this. This remains in `verification/elevator.json.knownGaps` and needs further Source placement/step reference investigation before claiming walk-on equivalence.
- Track motion is a bounded subset, not full Source train simulation: reverse/alternate/looped tracks, acceleration/orientation blending, 0.1-second Source lookahead event semantics, crush/unblockable behavior, complete moving-surface impulses, rotational props and general parent transforms remain incomplete. Elevator lighting/beam/shake rendering and full sound fidelity are still incomplete. Upper portal-cycle puzzle, cleansers, final progression and campaign remain unverified/unfinished.
- Public Valve SDK `trains.cpp`, `trains.h` and `pathtrack.cpp` informed flags/speed/path semantics; no independent running-retail comparison was performed. Preview remains **http://localhost:8066** using the existing server and health watcher; supplied assets were preserved. Reversible source snapshot: `target/iteration-03-before.tar.gz`.

## 2026-09-10 — Scheduled iteration 2: authored wake-up camera

### Implemented

- Exported the original MDL attachment table with bounded reads, checksum/version validation, finite matrix checks and bone-index validation. `blackin`'s `vehicle_driver_eyes` is attached to bone 2, `Blackout.view`; the runtime composes entity × animated bone × attachment matrices, **without applying inverse bind a second time**.
- Rust/Wasm now follows the authored opening camera position, forward and up vectors, including roll. Raw WebGL2 main and linked-portal view matrices accept camera up; no JavaScript camera simulation or generated motion was added.
- `point_viewcontrol` Enable/Disable and `SetParentAttachment` handle the opening's takeover. Keyboard/mouse/touch movement, look, jump, use and noclip are blocked while this camera owns control. The player remains at the original spawn until the existing animation completion enables the original `trigger_teleport`; actual hull contact teleports to `(-544, -368.776, 160)`, yaw 190, and the authored output disables the camera. Camera/model removal still uses the authored +0.2-second outputs. Handoff is not driven by a standalone timer.
- Added the opening `env_fade` hold/decay path as a Rust-controlled raw WebGL2 fullscreen blend: commentary black at map spawn, intro fade at 0.2 s, three-second hold, four-second fade-in ending at 7.2 s. Pause and restart preserve/reset camera and fade state.
- Map-spawn I/O is drained only after scene/audio metadata is installed, fixing native scene-start regressions exposed by initializing the camera early. Restart resets the camera, animation, teleport count, and fade without starting paused audio.
- Added explicit `?verify=1` `inspectView` override for fixed-camera prop screenshots; ordinary gameplay does not use it. Prop-animation/capture harnesses opt into it; camera tests run with it off. Existing controls regressions now wait for the authored control handoff instead of expecting movement during the cutscene. Added `verify-camera.mjs` to `npm run verify`.

### Verification and limits

- **74 native engine tests**, **54 compiler tests**, **3 inspector tests**, release Wasm build, release asset export and scoped formatting checks passed. Attachment tests cover malformed offsets/bones/nonfinite matrices and compare the runtime camera against independently skinned retail bone probes at five fractional cycles within **0.0002 Source units**.
- Native tests verify original initial eye position approximately `(-486.636, -380.546, 190.299)`, blocked controls, changing authored pose, actual teleport contact, camera cleanup, delayed bed closing, and restart. Moving the player outside the trigger deliberately prevents handoff even after the animation ends, guarding against a guessed completion timer.
- Chrome **1440×900 desktop / 390×844 mobile** camera interactions passed: black/hold/fade, changing position and roll, frozen pause, blocked keyboard/touch/mouse input, animation/contact handoff, restored keyboard/touch movement, bed follow-up and restart. The pre-handoff eye differs by under two Source units from the standing eye; no extra smoothing was invented. Screenshots inspected: `verification/camera-*.png`; results: `verification/camera.json`.
- Core, first-puzzle, bed/dispenser/both-elevator animation, natural dispenser retention/release/replacement, ordinary-route pointer-lock controls and capture regressions passed. The full **67-second opening ran in real time** with additional camera pause/handoff assertions plus existing bed closing, radio/speech, portal activation and restart checks. All completed suites report no JavaScript/WebGL/HTTP errors (`verification/opening.json` and existing result files).
- Camera/fade/parent semantics were checked against public Valve Source SDK 2013 `triggers.cpp`, `baseentity.cpp`, `EnvFade.cpp` and `view_effects.cpp`; these are implementation evidence, **not** independent Portal retail execution. Full general viewcontrol paths/targets/FOV, landmark teleport transforms, modulated/per-player/multiple fades, Source message quantization and rendering parity remain unsupported. Tonemap/bloom, player body, exact lighting/glass and other visual effects remain incomplete; blackin's tiny helper mesh stays hidden.
- No elevator train travel, cleansers or full-map/campaign completion was added. Preview remains **http://localhost:8066** with the existing server/watcher. Supplied asset directories were preserved. Reversible source snapshot: `target/iteration-02-before.tar.gz`.

## 2026-09-10 — Dispenser collision follow-up

### Fixed

- Reproduced the cube passing through the closed dispenser, then decoded the **original retail PHY collision** instead of adding a release timer or guessed retaining plane.
- Added a dependency-free Rust PHY parser with checksum validation, bounded tree/ledge reads, outward convex triangles, native unit conversion, and independently checked MDL bone bindings. The cover exports **20 bone-bound convexes / 288 triangles**; the body exports **8 terminal convexes / 128 triangles**. Internal approximation hulls are excluded so the dispenser opening stays open.
- The renderer and collision system now consume the same authored bone poses. Runtime collision transforms bone-local PHY vertices directly, generates face/axis/edge separating planes for existing swept hulls, and replaces only the dynamic collision portion when poses change.
- The original cube now rests on closed panels, falls only as the real opening animation clears its shape, activates the original exit trigger, closes the cover, and creates exactly one replacement retained by the reclosed panels. Native and browser tests exercise this without moving the cube through an inspection hook.
- Replaced the previous soft `knownGaps` report in the animation test with required natural-close/replacement assertions. Added `verify-dispenser.mjs` to the normal `npm run verify` command.

### Verification

- **70 native engine tests**, including natural retail dispenser retention/release/replacement and open-gap geometry tests, passed. **52 compiler tests**, including eight PHY tests, passed. The parser validates authored volumes within two parts per million. **3 inspector tests** passed.
- Desktop **1440×900** and mobile **390×844** Chrome passes cover: naturally spawned cube retention for ten extra seconds; entry-triggered animation and release; automatic close and exactly one retained replacement; paused-state stability; E/touch pickup/drop; restart resetting cube count and retention. Results/screenshots: `verification/dispenser.json` and `verification/dispenser-*.png`; screenshots inspected.
- Existing core, first-puzzle, bed/dispenser/both-elevator animation, ordinary pointer-lock controls and real-time 67-second opening browser regressions passed with no JavaScript/WebGL/HTTP errors. The first-puzzle test still places the released cube on its button and traverses the exit.
- PHY extraction and runtime integration remain scoped to this dispenser body/cover. Other prop collisions, cube angular dynamics, moving-body impulses, continuous animation sweeps, contact/friction parity, wake-up camera and elevator travel remain unfinished. This fixes the observed failure, **not** full Source VPhysics or retail parity.

## 2026-09-09 — Scheduled iteration 1: authored skeletal playback

### Implemented

- Added validated `sample_sequence_frames` in the Rust model compiler. It exports original local bone positions/quaternions, hierarchy, inverse binds, vertex influences, source FPS/frame count and loop flags for the bed cover, dispenser cover, both elevator door instances and `blackin`. Supplied asset directories are unchanged; generated metadata is in `web/assets/chamber.json` (about 2.85 MB).
- Added Rust/Wasm local-position interpolation and quaternion slerp **before** hierarchical skinning. Raw WebGL2 updates only changed prop vertex ranges; UVs/materials/topology are retained. This is not vertex-frame interpolation or a guessed hinge transform. Pause freezes animation and restart restores the initial rendered pose.
- `SetAnimation` and `SetDefaultAnimation` now drive the supported props. Nonlooping completions fire `OnAnimationDone` once at `(frame_count - 1) / fps`, chronologically within the existing I/O scheduler; replacing/killing playback cancels the old completion. Unsupported requested sequences produce explicit diagnostics rather than invented timings.
- Preserved the map's unusual bed input: at **5.2 s** it selects the one-frame looping `open` pose, not the separate `opening` sequence. `blackin` starts at 4.2 s; its source-duration completion drives bed `closing` at **11.533333 s**, lasting 31 frames at 30 FPS (1 s). Camera/teleport behavior is not implemented by this timing support.
- Dispenser and elevator door `open`/`close` clips each use 20 authored frames at 30 FPS (19/30 s). Elevator close returns to the authored `idle_closed` default; blank default after open holds the endpoint. No train movement or map transition was added.

### Verification and discovered limits

- **65 native engine tests** passed with retail-dependent tests enabled; **44 compiler tests** passed, including 18 model tests and runtime/compiler agreement at five fractional cycles for every exported sequence. Positions/normals agree within 0.0001 Source units/components, also under nonzero instance origin/angles. **3 inspector tests**, formatting checks, release asset export and release Wasm build passed. Existing unused-metadata warnings remain.
- Chrome animation checks passed at **1440×900 desktop / 390×844 mobile**: exact bed input times, skeletal midpoint/end geometry, screenshot changes, frozen pause, pixel-identical initial-pose restart, E/touch cube pickup/drop, real dispenser trigger close/replacement, and independent open/close/default states for both elevator door sets. Results/screenshots: `verification/animations.json` and `verification/animation-*.png`; inspected desktop/mobile bed and dispenser renders.
- Core, first-puzzle, ordinary-route controls and capture regressions passed (`results.json`, `interactions.json`, `controls.json`). The **67-second opening ran in real time**, now additionally checking pause/resume during the closing animation and completion, while retaining radio/speech/portal/restart checks (`opening.json`). Completed suites reported no JavaScript/WebGL/HTTP errors. The independent compiler worker's changes and tests were incorporated before completion.
- **Explicit pre-existing failure exposed by the new test:** no animated prop collision means the first cube falls through the closed dispenser cover before `trigger_drop_new_box` is enabled, so natural close/replacement does not occur. This was reproduced and is retained in `animations.json.knownGaps`; it was not replaced with a timer. Close/replacement is verified separately with actual E/touch pickup/drop and an **inspection pose** placing the cube within the real trigger. Natural dispensing/retention remains unfinished; the original first-puzzle regression still passes.
- Opening camera/attachment/fade/handoff, animated collision/PHY, train movement/base velocity, full-map completion, sequence transitions/blends/embedded animation events, dynamic lighting updates on skinned vertices, and general Source animation support remain incomplete. Blackin is still hidden. Glass/lighting remain visibly approximate. No independent running-retail comparison or 1:1 fidelity claim is made.
- Preview remains **http://localhost:8066**, using the existing server and health watcher without duplicate services. Reversible source snapshot: `target/iteration-01-before.tar.gz` (compiler sampler is independently scoped).

## 2026-09-09 — Playable first-puzzle baseline

### Implemented

- Custom Rust/WebAssembly engine, direct WebGL 2 draw calls and shaders, original Source-coordinate world geometry. JavaScript supplies browser input, image/network loading, and audio transport only.
- Native Rust asset compiler: BSP, embedded ZIP, shared VPK dependencies, VMT patches, VTF decoding, MDL/VVD/VTX meshes, supported initial skeletal poses, Source overlays, lightmaps, and native static-prop vertex lighting.
- Current export: **151,512 triangles, 247 batches, 194 materials, 100 full-resolution textures, 2,360 lightmap charts, 91 prop instances, 53 native-lit static props, 323 entities, 1,139 collision brushes, and 27 referenced sound files**. The full first BSP includes the vault, first puzzle, and upper portal-cycling section; rendering that geometry does not mean all sections are playable.
- Raw player hull sweeps, sliding, steps, grounded movement, jump, and crouch. Fixed 120 Hz host simulation. Source-like movement values remain unverified against retail.
- One-level linked portal views and transformed player traversal at the authored vault portal positions. Map I/O activates the pair at 66.7 seconds. Bounded rectangular collision cutouts replace an earlier unsafe whole-brush removal approach.
- Entity scheduler preserves duplicate output keys, target fanout, relay delays, timers, trigger lifecycles, and first-button player/cube filters. Doors move their parented visual meshes and brush collision together.
- Template-relative cube instances, swept AABB gravity/sliding, pickup/drop, player-versus-prop collision-mask separation, and first-button doors. The first puzzle interaction test places a dispensed cube on the button and walks through the opened exit.
- Five recovered retail scenes with 26 timed events; original entry dialogue, radio, button/bed sounds, chronological scene outputs and completion callbacks. Browser pause/resume also pauses/resumes audio. No synthesized dialogue.
- Responsive development menu, mute/restart/pause, keyboard/mouse pointer lock, touch inspection, engine diagnostics, and explicit inspection time advancement.

### Browser-driven corrections

- Fixed missing bed/button geometry: their base-texture alpha is a self-illumination mask, not transparency. Opaque materials now retain zero-alpha texels and apply the authored self-illumination mask.
- Corrected near-black dynamic props: Source ambient-cube vector samples use a different scale from lightmap texture samples. Direct lighting and better ambient lookup remain pending.
- Fixed cube sticking on dispenser slope, invalid playerclip collisions for props, and an initially overlapping upper cube hull. The replacement uses basic AABB resolution, not retail VPhysics parity.
- Fixed first-button `OnTrigger` dispatch, last-contact release, invisible button-brush travel bounds, and child mesh transforms.
- Fixed missed very short keyboard/touch presses by retaining press edges until a simulation step.
- Exported shared retail sound dependencies to remove verified button-audio 404 responses.

### Verified baseline

- **57 native engine tests**, including actual compiled-world and first-button tests, passed with asset-dependent tests explicitly enabled.
- **34 asset/compiler tests** passed; the model parser sweep covers 596 matching retail MDL/VVD/VTX triplets. **3 scene-inspector tests** passed.
- Chrome checks passed: load, enter, ordinary-route pointer lock and mouse look, keyboard movement, jump/crouch, wall collision, pause with frozen simulation, resume, mute, diagnostics, restart, portal activation/traversal, player-button press/release, dispensed-cube E pickup/drop, cube-held doors, and walking through the first exit.
- Desktop 1440×900 / 1366×768 and mobile 390×844 checked, with touch interactions. Generated screenshots were inspected, including the vault, first room, and upper room. No JavaScript/WebGL errors or missing files in the completed core/control/puzzle suites.
- The **67-second opening ran in real time** in Chrome: radio and original speech audio elements loaded and advanced, pause stopped playback, resume continued it, all seven opening speech files were requested, and the portal pair activated. That test passed with no errors; it verifies playback/timing, not human listening quality or retail DSP equivalence. Results: `verification/opening.json`.
- Automated puzzle setup uses inspection poses; this is **not** an uninterrupted full-map playthrough or a comparison to retail execution.
- Machine-readable results and screenshots are in `verification/`. This verifies implemented browser behavior, not zero-exception retail equivalence.

### Remaining fidelity and campaign gaps

- Opening animated camera/attachment, bed/dispenser/elevator animation playback, trains/base velocity, cleansers, final transitions, save/load, and full first-map completion are still incomplete.
- Prop motion uses simple AABBs, contact sliding and spawn-overlap recovery; no angular VPhysics, decoded PHY shapes, prop-prop/player collision, or complete dynamic-entity I/O registration. Held-object portal behavior remains incomplete.
- Portal renderer is one level deep with a provisional rim; recursion, full clipping fidelity, player avatar and collision equivalence remain unfinished.
- Material/refraction/reflection/bump/Phong effects, direct/dynamic prop lighting/shadows, lightstyle changes, particles, color correction/bloom, clock and indicator proxies are incomplete. Current glass and prop brightness differ visibly from retail.
- Audio is not yet spatialized and has no full soundscape, surface footsteps, pitch/DSP, busy-actor arbitration, or extracted retail captions. `Doors.FullClose8` is an explicit unresolved sound-script dependency.
- Later campaign maps have not been implemented or verified. The native compiler is reusable, but loading a map is not campaign support.
- Retail comparison is blocked by lack of a verified running compatible reference; supplied executable is 32-bit macOS. Source asset evidence and browser tests cannot establish exact fidelity alone.

## 2026-09-09 — Initial implementation and first render

- Confirmed original BSP campaign maps, unpacked Portal materials/models/sounds, and shared Half-Life 2 VPK archives are present.
- Confirmed Rust, the `wasm32-unknown-unknown` target, Node, and Chrome are installed.
- Started concurrent work on the Rust BSP/material/lightmap compiler, MDL/VVD/VTX prop loader, convex-brush player collision, and first-chamber entity scripting.
- Engine rendering and simulation are authored directly in Rust; JavaScript is limited to browser startup, input, asset transport, and browser audio access. No Three.js or game engine is used.
- The supplied retail executable is Mach-O i386 (32-bit). Running the original as a reference on this Mac has not been verified.

### Verification record

- First Rust/Wasm build compiled successfully and is served at **http://localhost:8066**.
- First BSP-only export: 2,541 rendered map faces, 8,842 triangles, 154 material records, 61 decoded retail textures, 2,360 native lightmap charts, 1,139 collision brushes, 323 entities, and 71 overlays.
- Chrome end-to-end pass: enter chamber, keyboard movement, pause with frozen state, resume, mute, diagnostics, restart, ground collision, jump, crouch, wall containment, map-timed portal activation, and orange-to-blue traversal. Portal timing/traversal assertions use explicitly enabled inspection hooks; ordinary keyboard movement is tested separately.
- Desktop 1440×900 and mobile 390×844 layouts inspected; touch entry, jump, pause, and diagnostics exercised. No JavaScript exceptions or HTTP errors in that pass.
- Screenshots and machine-readable results are in `verification/`. This is browser behavior verification, **not** side-by-side retail parity verification.
- Native tests initially exposed an 18-inch step/contact-margin assertion; this was subsequently resolved and the full test suite passed.

### Gaps at the first-render checkpoint (superseded by the entry above)

- The first verified export contained BSP/overlay geometry only; authored props were integrated in the later baseline.
- Opening camera animation is not implemented: the initial view uses `info_player_start`, not the animated retail wake-up attachment.
- Portal views currently render one linked view, not recursive views; the rim is a provisional shader rather than the full retail portal particle/material effect.
- Glass/material effects, prop lighting, countdown animation, cube/button sequencing, and complete audio behavior remain incomplete. The first puzzle is not yet certified completable.

### Known outstanding requirements

- World mesh/material/lightmap rendering and authored props.
- Correct spawn, camera, player movement, collision, crouch, jumping, interactions, cube dynamics.
- Recursive portal views and matching traversal/momentum; first chamber scripted portals.
- Entity input/output timing, triggers, buttons, doors, elevators, transitions, save state.
- Retail voiceover, positional audio, ambient sounds, footsteps, sound processing, subtitles.
- Source material shader behavior, animated materials, decals, glass, particles, dynamic shadows, postprocessing.
- Animation, skinning, physics meshes, prop collision, player representation and reflection.
- Browser end-to-end and regression verification; desktop/mobile display checks.
- Side-by-side retail reference verification and subsequent campaign maps.
