From e1e227acaf417e053ba1222ce6c8a0a5d7c08a46 Mon Sep 17 00:00:00 2001 From: Daniel Meyer Date: Thu, 5 Nov 2026 14:18:27 -0800 Subject: [PATCH 1/3] Record the evidence names, the gates, and the phone sample. Device-qa notes for the evidence filenames, the success streak, the playtest load, the catalog and ambient gates, frame backing, gate C, the expansion decision, the world call order, and the phone sample. No number was changed. --- docs/review/device-qa/README.md | 10 ++++++++++ docs/review/device-qa/ambient-gate-2026-11-05.md | 13 +++++++++++++ docs/review/device-qa/catalog-gate-2026-11-05.md | 15 +++++++++++++++ docs/review/device-qa/device-sample-2026-11-05.md | 11 +++++++++++ docs/review/device-qa/draw-pass-2026-11-05.md | 11 +++++++++++ .../review/device-qa/evidence-names-2026-11-05.md | 13 +++++++++++++ .../review/device-qa/expansion-gate-2026-11-05.md | 13 +++++++++++++ docs/review/device-qa/frame-backing-2026-11-05.md | 13 +++++++++++++ docs/review/device-qa/frame-times-2026-11-05.md | 15 +++++++++++++++ docs/review/device-qa/playtest-load-2026-11-05.md | 11 +++++++++++ docs/review/device-qa/run-streak-2026-11-05.md | 9 +++++++++ 11 files changed, 134 insertions(+) create mode 100644 docs/review/device-qa/ambient-gate-2026-11-05.md create mode 100644 docs/review/device-qa/catalog-gate-2026-11-05.md create mode 100644 docs/review/device-qa/device-sample-2026-11-05.md create mode 100644 docs/review/device-qa/draw-pass-2026-11-05.md create mode 100644 docs/review/device-qa/evidence-names-2026-11-05.md create mode 100644 docs/review/device-qa/expansion-gate-2026-11-05.md create mode 100644 docs/review/device-qa/frame-backing-2026-11-05.md create mode 100644 docs/review/device-qa/frame-times-2026-11-05.md create mode 100644 docs/review/device-qa/playtest-load-2026-11-05.md create mode 100644 docs/review/device-qa/run-streak-2026-11-05.md diff --git a/docs/review/device-qa/README.md b/docs/review/device-qa/README.md index c256751..fd1fcbf 100644 --- a/docs/review/device-qa/README.md +++ b/docs/review/device-qa/README.md @@ -276,5 +276,15 @@ Each note records one fact about bytes or draw calls already on main. None of th | [court-capture-2026-11-05.md](court-capture-2026-11-05.md) | The court, stored on the first boss tick | | [damage-log-2026-11-05.md](damage-log-2026-11-05.md) | The damage log, last 512 | | [field-cache-2026-11-05.md](field-cache-2026-11-05.md) | The field polygon, cached | +| [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md) | The evidence file names | +| [run-streak-2026-11-05.md](run-streak-2026-11-05.md) | A success adds one | +| [playtest-load-2026-11-05.md](playtest-load-2026-11-05.md) | The playtest file, loaded | +| [catalog-gate-2026-11-05.md](catalog-gate-2026-11-05.md) | The asset catalog, refused | +| [ambient-gate-2026-11-05.md](ambient-gate-2026-11-05.md) | The ambient contract, refused | +| [frame-backing-2026-11-05.md](frame-backing-2026-11-05.md) | A direction, fully backed | +| [frame-times-2026-11-05.md](frame-times-2026-11-05.md) | The frame sample, and gate C | +| [expansion-gate-2026-11-05.md](expansion-gate-2026-11-05.md) | The expansion gate, computed | +| [draw-pass-2026-11-05.md](draw-pass-2026-11-05.md) | The world pass, in call order | +| [device-sample-2026-11-05.md](device-sample-2026-11-05.md) | The phone sample, beside the frames | Next session: follow [retest-checklist.md](retest-checklist.md) (D-105 allows device checks on any build). diff --git a/docs/review/device-qa/ambient-gate-2026-11-05.md b/docs/review/device-qa/ambient-gate-2026-11-05.md new file mode 100644 index 0000000..1e1fa0f --- /dev/null +++ b/docs/review/device-qa/ambient-gate-2026-11-05.md @@ -0,0 +1,13 @@ +# Ambient gate + +Read 2026-11-05 from `AmbientMotionLoader.decode` on `ef68565`. No recipe was changed. + +The shipped periods and the caps of 12 and 1 stay in [ambient-motion-2026-11-04.md](ambient-motion-2026-11-04.md). The roll, the drop, and the hash stay in [ambient-roll-2026-11-05.md](ambient-roll-2026-11-05.md). + +The schema is `ambient-motion-001`. The animation version is `animation-civic-seam-001`. A wrong animation version throws `schemaVersion`, the same error as a wrong schema. An unexpected catalog key or recipe key throws `unexpectedKey`. A missing recipe key throws `missingKey`. `maxConcurrentAmbient` and `maxConcurrentRare` must be integers, the recipe array must be non-empty, or the file throws `invalidJSON`. Either budget below 1, or a max ambient below the persistent id count, throws `budget` with the string `maxConcurrentAmbient`. + +A repeated recipe id throws `missingRecipe` with that id. Each required id must be present, or the loader throws `missingRecipe` with the missing id. The persistent ids are `fogDrift`, `trolleyWireSway`, `rooftopFans`, `cameraStatus`, `trafficSignals`, `pigeonsScatter`, `blinds`, `windChannels`, and `transitDisplay`. The rare ids are `greenParrotCrossing`, `autonomousVehicleHesitation`, `streetcarShadow`, `towerLightSync`, `phoenixBlink`, `occupantBlinds`, and `fogRevealNetwork`. + +A recipe's kind is `persistent` or `rare`. Its tier is `p0`, `p1`, or `p2`. `periodTicks` is above 0. `durationTicks` is above 0 and at most the period. `chancePercent` is from 0 through 100. `impliesGameplay`, `impliesCollision`, `impliesReward`, `impliesEnemy`, and `impliesObjective` must all be false, or the recipe throws `gameplayImplication`. `salience` must be `belowGameplay`. `cameraStatus` must keep its housing locked, and must not translate or rotate that housing. A rare recipe must set `pausesWhenCaptainTelegraph`. + +This does not change a recipe. diff --git a/docs/review/device-qa/catalog-gate-2026-11-05.md b/docs/review/device-qa/catalog-gate-2026-11-05.md new file mode 100644 index 0000000..8a3de61 --- /dev/null +++ b/docs/review/device-qa/catalog-gate-2026-11-05.md @@ -0,0 +1,15 @@ +# Catalog gate + +Read 2026-11-05 from `AssetCatalogLoader.decodeAndValidate` on `ef68565`. No asset was admitted. + +The visual refusal stays in [visual-gate-2026-11-05.md](visual-gate-2026-11-05.md). The clip refusal stays in [clip-gate-2026-11-05.md](clip-gate-2026-11-05.md). The specification pin stays in [replay-identity-2026-11-04.md](replay-identity-2026-11-04.md). + +The catalog schema is `asset-catalog-001`. `legacyRepository`, `legacyCommit`, and `specificationCommit` must equal `LegacyEvidence.repository`, `LegacyEvidence.commit`, and `ContractVersions.specificationCommit`. The catalog keys are exactly `schemaVersion`, `legacyRepository`, `legacyCommit`, `specificationCommit`, and `entries`. Each entry's keys are exactly `admissionDecision` and `record`. An unexpected key throws `unexpectedKey`. + +A record uses schema `asset-record-001`. It must carry `schemaVersion`, `assetId`, `kind`, `productionStatus`, `runtimeRequired`, `provenance`, `license`, `source`, `runtimePath`, `sha256`, `dimensions`, `colorSpace`, `alpha`, and `ownerContract`. `notes` may be present and is not required. The asset id must match `^[a-z0-9][a-zA-Z0-9_]*$` over the whole string. A sha256 that is present must match `^[0-9a-f]{64}$`. A repeated asset id throws `duplicateAssetId`. + +An `accepted` record needs a non-empty source, runtime path, sha256, and license. `excluded`, `rejected`, and `sfCandidate` refuse `runtimeRequired`. `originalAccepted` requires status `accepted`, provenance `projectOriginal`, `runtimeRequired`, and a non-empty path and sha256. `adaptedAdmitted` requires status `accepted`, provenance `adaptedLegacy`, `runtimeRequired`, and a source that starts with `legacy://`. `plannedOriginal` requires status `planned`, provenance `projectOriginal`, `runtimeRequired`, and a nil source, runtime path, and sha256. A runtime-required record whose provenance is not `projectOriginal`, and whose decision is neither `adaptedAdmitted` nor `originalAccepted`, is refused. A runtime path that starts with `ArtSources/` is refused. + +The presentation JSON must carry `requiredAssetIds`, `audioEventIds`, and `musicAssetIds`. Each id in those three arrays must be covered by `plannedOriginal`, `adaptedAdmitted`, or `originalAccepted`. + +This does not admit an asset. diff --git a/docs/review/device-qa/device-sample-2026-11-05.md b/docs/review/device-qa/device-sample-2026-11-05.md new file mode 100644 index 0000000..0adf0e3 --- /dev/null +++ b/docs/review/device-qa/device-sample-2026-11-05.md @@ -0,0 +1,11 @@ +# Device sample + +Read 2026-11-05 from `DeviceTelemetrySample.capture` on `ef68565`. No thermal label was changed. + +The baseline phone sentence stays in [retest-checklist.md](retest-checklist.md). Frame percentiles stay in [frame-times-2026-11-05.md](frame-times-2026-11-05.md). The file that stores this sample stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). Which outcomes are finished stays in [terminal-seal-2026-11-04.md](terminal-seal-2026-11-04.md). + +`hardwareModel` is the `utsname` machine bytes up to the first zero. `capture` reads `resident_size` through `task_info` with `MACH_TASK_BASIC_INFO`. A result other than `KERN_SUCCESS` stores 0. The thermal label is `nominal`, `fair`, `serious`, `critical`, or `unknown`. The memory-warning count is the integer passed in. + +`RunInstrumentation.noteMemoryWarning` adds one to that count. `recordSimulation` stores the first sample as the peak. A later sample replaces the peak when its `totalLive` is greater. `totalLive` is cameras, enemies, projectiles, mines, and the civic pool, added. A sample whose outcome is not `playing` replaces `terminalTelemetry`, including the upgrade choice. `evidence` uses schema `run-instrumentation-001` and carries the frame summary, `capture` of the warning count, the peak, and the terminal sample. `reset` clears the frame collector, the warning count, and both snapshots. + +This does not label a phone. diff --git a/docs/review/device-qa/draw-pass-2026-11-05.md b/docs/review/device-qa/draw-pass-2026-11-05.md new file mode 100644 index 0000000..d10c334 --- /dev/null +++ b/docs/review/device-qa/draw-pass-2026-11-05.md @@ -0,0 +1,11 @@ +# Draw pass + +Read 2026-11-05 from `WorldRenderer.render` on `ef68565`. No z position was changed. + +Who draws above whom stays in [world-layer-order-2026-11-03.md](world-layer-order-2026-11-03.md). The ground is built once, and the tile walk and the brightness mix stay in [ground-tint-2026-11-04.md](ground-tint-2026-11-04.md) and [ground-walk-2026-11-05.md](ground-walk-2026-11-05.md). Fog density stays in [fog-drift-2026-11-04.md](fog-drift-2026-11-04.md). + +`render` calls, in order, `renderGround`, `renderDecorations`, `renderFog`, `renderSolids`, `renderExtraction`, `renderCameras`, `renderTelegraphs`, `renderMines`, `renderSpawnSockets`, `renderActors`, `renderProjectiles`, and `renderMarkers`. `renderFog` runs when `reviewArt` is nil. Ordinary play leaves that pack nil, so the fog call runs. `installBenchmarkArt`, which stores a pack, sits inside `#if DEBUG || SS_RENDERER_TEST_SUPPORT`. + +This list is the order the methods run. Sibling draw order stays on `zPosition`. + +This does not change a z position. diff --git a/docs/review/device-qa/evidence-names-2026-11-05.md b/docs/review/device-qa/evidence-names-2026-11-05.md new file mode 100644 index 0000000..e78b41c --- /dev/null +++ b/docs/review/device-qa/evidence-names-2026-11-05.md @@ -0,0 +1,13 @@ +# Evidence names + +Read 2026-11-05 from `ReleaseEvidenceStore` on `ef68565`. No filename was changed. + +The export paths, the `QA_DAMAGE` lines, and the three-success bundle stay in [retest-checklist.md](retest-checklist.md). The 512-record log and the QA flag stay in [damage-log-2026-11-05.md](damage-log-2026-11-05.md). Gate C on the device-run record stays in [frame-times-2026-11-05.md](frame-times-2026-11-05.md). The playtest objects stay in [playtest-load-2026-11-05.md](playtest-load-2026-11-05.md). + +The store uses the Application Support subdirectory `ReleaseEvidence`. A device run is written atomically as `device-run--.json`. The class is `deviceClass` with each space replaced by a dash and then lowercased. The count is `consecutiveCompleteRuns`. The bytes are `evidence.canonical().serialize()`. + +A release candidate is `release-candidate-.json`. The digest is the first 12 characters of `peakDensityDigest`, the sha256 hex of the peak report's canonical JSON. `PlaytestSessions` is created under `ReleaseEvidence`. `exportReleaseCandidateWithBundledPlaytests` loads that directory, writes the candidate, then writes the expansion-gate file. That file is `expansion-gate-.json`, with the decision's raw value lowercased. + +`makeDeviceRunEvidence` calls `DeviceRunEvidence.performanceFloor` with the device class, the consecutive count, `frameTime.passesGateC()`, the memory-warning count, the thermal state, the resident bytes, and the atlas bytes. The session passes nil for the atlas bytes. + +This does not write a file during ordinary play. diff --git a/docs/review/device-qa/expansion-gate-2026-11-05.md b/docs/review/device-qa/expansion-gate-2026-11-05.md new file mode 100644 index 0000000..e632607 --- /dev/null +++ b/docs/review/device-qa/expansion-gate-2026-11-05.md @@ -0,0 +1,13 @@ +# Expansion gate + +Read 2026-11-05 from `ExpansionGateEvaluator.evaluate` on `ef68565`. No decision string was changed. + +The written filename stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). The three-success bundle stays in [retest-checklist.md](retest-checklist.md). + +`passed` is `EXPANSION_GATE_PASSED`. `failed` is `EXPANSION_GATE_FAILED`. `notComputable` is `EXPANSION_GATE_NOT_COMPUTABLE`. An open severity-one count above 0, or an open severity-two count above 0, returns `failed`. While `d013Settled` is false, or `d021Settled` is false, the result is `notComputable`. + +The required families are `GateRegistry.functional`, `determinism`, `stability`, `visual`, `arena`, `accessibility`, and `playtest`. A missing required id returns `notComputable`. A required result whose `passed` is false returns `failed`. Every required result `passed` returns `passed`. Any other shape returns `notComputable`. + +`defaultInputsFromRegistry` marks each `GateRegistry.evidence` entry whose kind is `automatedTest` as passed, and sets both settled flags false, so that input evaluates `notComputable`. `ExpansionGateReporter.evaluate` stores the decision `ReleaseCandidateEvidenceCollector.collect` computed, the open counts, the settled flags, the linked automated count, the pending count, and the sha256 of that candidate's canonical JSON. The report schema is `expansion-gate-report-001`. + +This does not close T908. diff --git a/docs/review/device-qa/frame-backing-2026-11-05.md b/docs/review/device-qa/frame-backing-2026-11-05.md new file mode 100644 index 0000000..630805b --- /dev/null +++ b/docs/review/device-qa/frame-backing-2026-11-05.md @@ -0,0 +1,13 @@ +# Frame backing + +Read 2026-11-05 from `ClipFrameLibrary.deliveredFrames` on `ef68565`. No frame was added. + +The catalog refusal this loader calls stays in [clip-gate-2026-11-05.md](clip-gate-2026-11-05.md). Nearest filtering, and an unbacked clip returning false, stay in [clip-playback-2026-11-04.md](clip-playback-2026-11-04.md). The facing letters stay in [facing-direction-2026-11-04.md](facing-direction-2026-11-04.md). + +`make` runs `ClipCatalogLoader.decode`, then keeps a delivered path only for a catalog entry whose admission is `adaptedAdmitted` or `originalAccepted`, whose production status is `accepted`, whose kind is `sprite`, and which has a runtime path. + +`deliveredFrames` returns nil when that direction's frame ids are empty. It returns nil when any of those ids has no delivered path. Otherwise it returns the paths in frame-id order. `isBacked` is that result being non-nil. A clip that lists directions, asked for a direction it does not list, has an empty id list and is unbacked. The ids themselves are the frame ids that contain `__`. A clip that lists no directions returns every frame id for any direction argument. + +`coverage` counts frame ids that have a path, over every frame id in the library. That count is a different number from all-or-nothing backing. + +This does not add a frame. diff --git a/docs/review/device-qa/frame-times-2026-11-05.md b/docs/review/device-qa/frame-times-2026-11-05.md new file mode 100644 index 0000000..c231a5c --- /dev/null +++ b/docs/review/device-qa/frame-times-2026-11-05.md @@ -0,0 +1,15 @@ +# Frame times + +Read 2026-11-05 from `FrameTimeCollector.summarize` on `ef68565`. No threshold was changed. + +The 300-frame QA log stays in [retest-checklist.md](retest-checklist.md). Where `passesGateC` is stored on a device run stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). + +`recordFrame` keeps a sample only when the delta from the previous timestamp is above 0 ms and below 1000 ms. The delta is `(timestamp - last) * 1000`. The first timestamp stores no sample. `reset` clears the samples and that timestamp. + +An empty buffer summarizes as sample count 0, each percentile 0, worst 0, and `sustainedBelowFloorFps` false. Otherwise the samples are sorted. The index for p 0.50, 0.95, and 0.99 is `(p * (count - 1))` rounded down, then clamped to the buffer. Worst is the last sorted sample. + +The sustained floor is `1000 / sustainedPresentationFloorFps`, and that constant is 55. A sample above the floor adds one to a streak. Three in a row set `sustainedBelowFloorFps`, and that flag stays set for the summary. A sample at or under the floor sets the streak back to 0. + +`passesGateC` requires a sample count above 0, p50 at most `16.67`, p95 at most `16.67`, p99 at most `25.0`, worst at most `50.0`, and the sustained flag clear. Those four limits are `frameTimeP50Ms`, `frameTimeP95Ms`, `frameTimeP99Ms`, and `frameTimeWorstMs`. `targetPresentationFps` is 60. + +This does not change a threshold. diff --git a/docs/review/device-qa/playtest-load-2026-11-05.md b/docs/review/device-qa/playtest-load-2026-11-05.md new file mode 100644 index 0000000..9c7decc --- /dev/null +++ b/docs/review/device-qa/playtest-load-2026-11-05.md @@ -0,0 +1,11 @@ +# Playtest load + +Read 2026-11-05 from `PlaytestEvidenceLoader.load` on `ef68565`. No session field was changed. + +The directory those files are read from stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). + +`load` requires a JSON object whose `taskId` is `T903` or `T904`, and a non-empty `sessions` array of objects. A bad root, any other task id, or an empty sessions array throws `PlaytestEvidenceError.invalidJSON`. Each session requires `participantId` as a string, `completedOnboardingWithoutInstruction` as a bool, and `describedExposureCorrectly` as a bool. A session missing one of those throws `invalidJSON`. `beganVoluntaryReplay` is that bool when the value is a bool, and false when the key is absent or the value is not a bool. `competentRunDurationMinutes` is stored as the `doubleValue` of an `NSNumber`, and is absent when that cast fails. + +`loadAll` lists the directory, skips hidden files, keeps names whose extension is `json`, and loads each one. + +This does not close T903 or T904. diff --git a/docs/review/device-qa/run-streak-2026-11-05.md b/docs/review/device-qa/run-streak-2026-11-05.md new file mode 100644 index 0000000..f18791f --- /dev/null +++ b/docs/review/device-qa/run-streak-2026-11-05.md @@ -0,0 +1,9 @@ +# Run streak + +Read 2026-11-05 from `DeviceRunTracker.noteTerminalOutcome` on `ef68565`. No streak was changed. + +When the session calls the tracker stays in [damage-log-2026-11-05.md](damage-log-2026-11-05.md). Which outcomes are finished stays in [terminal-seal-2026-11-04.md](terminal-seal-2026-11-04.md). The three-success bundle stays in [retest-checklist.md](retest-checklist.md). + +`consecutiveCompleteRuns` starts at 0. `success` adds 1. `failure` stores 0. `invalid` stores 0. Any other outcome stores 0. `reset` stores 0. + +This does not change a medal. From c8f6e2721224e12e83554982453961b4441c9737 Mon Sep 17 00:00:00 2001 From: Daniel Meyer Date: Thu, 5 Nov 2026 14:44:33 -0800 Subject: [PATCH 2/3] Record the playtest gates, the density peak, and the seam checks. Device-qa notes for the playtest gates, the device-run gates, the peak-density profiler, the ceiling status, the replay matrix, the asset intake, the bundle filter, the pacing windows, the environment groups, and the seam checks. No number was changed. --- docs/review/device-qa/README.md | 10 ++++++++++ docs/review/device-qa/asset-intake-2026-11-05.md | 15 +++++++++++++++ docs/review/device-qa/bundle-filter-2026-11-05.md | 15 +++++++++++++++ .../review/device-qa/ceiling-status-2026-11-05.md | 11 +++++++++++ .../device-qa/device-run-gates-2026-11-05.md | 13 +++++++++++++ docs/review/device-qa/env-groups-2026-11-05.md | 13 +++++++++++++ docs/review/device-qa/pacing-window-2026-11-05.md | 11 +++++++++++ docs/review/device-qa/peak-density-2026-11-05.md | 15 +++++++++++++++ .../review/device-qa/playtest-gates-2026-11-05.md | 11 +++++++++++ docs/review/device-qa/replay-matrix-2026-11-05.md | 13 +++++++++++++ docs/review/device-qa/seam-checks-2026-11-05.md | 15 +++++++++++++++ 11 files changed, 142 insertions(+) create mode 100644 docs/review/device-qa/asset-intake-2026-11-05.md create mode 100644 docs/review/device-qa/bundle-filter-2026-11-05.md create mode 100644 docs/review/device-qa/ceiling-status-2026-11-05.md create mode 100644 docs/review/device-qa/device-run-gates-2026-11-05.md create mode 100644 docs/review/device-qa/env-groups-2026-11-05.md create mode 100644 docs/review/device-qa/pacing-window-2026-11-05.md create mode 100644 docs/review/device-qa/peak-density-2026-11-05.md create mode 100644 docs/review/device-qa/playtest-gates-2026-11-05.md create mode 100644 docs/review/device-qa/replay-matrix-2026-11-05.md create mode 100644 docs/review/device-qa/seam-checks-2026-11-05.md diff --git a/docs/review/device-qa/README.md b/docs/review/device-qa/README.md index fd1fcbf..f87449a 100644 --- a/docs/review/device-qa/README.md +++ b/docs/review/device-qa/README.md @@ -286,5 +286,15 @@ Each note records one fact about bytes or draw calls already on main. None of th | [expansion-gate-2026-11-05.md](expansion-gate-2026-11-05.md) | The expansion gate, computed | | [draw-pass-2026-11-05.md](draw-pass-2026-11-05.md) | The world pass, in call order | | [device-sample-2026-11-05.md](device-sample-2026-11-05.md) | The phone sample, beside the frames | +| [playtest-gates-2026-11-05.md](playtest-gates-2026-11-05.md) | The playtest gates, five and four | +| [device-run-gates-2026-11-05.md](device-run-gates-2026-11-05.md) | The device-run gates, and the nil atlas | +| [peak-density-2026-11-05.md](peak-density-2026-11-05.md) | The peak the profiler keeps | +| [ceiling-status-2026-11-05.md](ceiling-status-2026-11-05.md) | The ceiling, measured then settled | +| [replay-matrix-2026-11-05.md](replay-matrix-2026-11-05.md) | The replay matrix, three times | +| [asset-intake-2026-11-05.md](asset-intake-2026-11-05.md) | The intake, against the files | +| [bundle-filter-2026-11-05.md](bundle-filter-2026-11-05.md) | The bundle, reachable ids | +| [pacing-window-2026-11-05.md](pacing-window-2026-11-05.md) | The pacing windows | +| [env-groups-2026-11-05.md](env-groups-2026-11-05.md) | A group, fully backed | +| [seam-checks-2026-11-05.md](seam-checks-2026-11-05.md) | The seam checks | Next session: follow [retest-checklist.md](retest-checklist.md) (D-105 allows device checks on any build). diff --git a/docs/review/device-qa/asset-intake-2026-11-05.md b/docs/review/device-qa/asset-intake-2026-11-05.md new file mode 100644 index 0000000..e535358 --- /dev/null +++ b/docs/review/device-qa/asset-intake-2026-11-05.md @@ -0,0 +1,15 @@ +# Asset intake + +Read 2026-11-05 from `AssetIntake.validate` on `ef68565`. No file was hashed. + +The catalog refusal stays in [catalog-gate-2026-11-05.md](catalog-gate-2026-11-05.md). + +`PNGHeader.parse` returns nil when the data is shorter than 26 bytes, the first 8 bytes are not the PNG signature, bytes 12 through 15 are not `IHDR`, or the width or height is below 1. Width is the big-endian integer at bytes 16 through 19. Height is the big-endian integer at bytes 20 through 23. The color type is byte 25. + +A runtime path that starts with `ArtSources/` appends `artSourcesInRuntimePath`. An accepted record with an empty runtime path appends `acceptedWithoutFile`. An accepted `.png` path that fails the delivery grammar appends `invalidDeliveryName`. A delivery name ends in `.png`, has an `@` in the stem, a scale of digits that starts at 1 through 9 and ends in `x`, and at least two `_` parts. The first character is lowercase or a digit. Each part is non-empty and uses letters and digits. + +Records whose source starts with `ArtSources/` are read under the evidence root. A missing file appends `missingEvidenceFile`. A size at or below 0 appends `emptyFile`. A recorded sha256 that already belongs to another asset id appends `duplicateHash`. The file bytes are not hashed. A `.png` whose header does not parse appends `emptyFile`. Recorded dimensions that differ from the header append `dimensionMismatch`. A recorded color space other than `sRGB` appends `colorSpace`. Color type 4 or 6 with recorded alpha `opaque` appends `colorSpace`. + +Regular files under `ArtSources/legacy-evidence`, other than `README.md`, append `undeclaredEvidenceFile` when that relative path is not one of the declared sources. + +This does not admit an asset. diff --git a/docs/review/device-qa/bundle-filter-2026-11-05.md b/docs/review/device-qa/bundle-filter-2026-11-05.md new file mode 100644 index 0000000..77e1dbb --- /dev/null +++ b/docs/review/device-qa/bundle-filter-2026-11-05.md @@ -0,0 +1,15 @@ +# Bundle filter + +Read 2026-11-05 from `RuntimeBundleFilter.project` on `ef68565`. No asset was added. + +The admission shapes stay in [catalog-gate-2026-11-05.md](catalog-gate-2026-11-05.md). The clip frame ids stay in [clip-gate-2026-11-05.md](clip-gate-2026-11-05.md). + +`reachableAssetIds` is the presentation contract's `requiredAssetIds`, `audioEventIds`, and `musicAssetIds`, plus `environmentAssetIds` when that array is present. It also includes every `frameIds` entry in `clip-metadata-001`. An absent environment array contributes nothing. + +An entry is bundle-eligible when its decision is `plannedOriginal`, `adaptedAdmitted`, or `originalAccepted`, it is runtime-required, its id is reachable, and its runtime path does not start with `ArtSources/`. `project` sorts the eligible ids and the excluded ids. + +`validate` appends `duplicateBundleAsset` for a repeated eligible id. An ineligible entry whose decision is outside those three, and which has a runtime path or is runtime-required, appends `legacyEvidenceInBundle`. That same entry appends `nonSanFranciscoInBundle` when the id contains `_atlanta_` or the decision is `excluded`, and a runtime path is present. A runtime-required entry in the three decisions whose id is not reachable appends `unreachableRuntimeRequired`. A runtime path that starts with `ArtSources/` appends `sourceEvidenceInBundle`. + +`PresentationAssetRegistry.bundled` stores that projection's ids, and copies the three presentation arrays from `presentation-assets-003`. `require` throws `unreachable` when the id is not in the bundle list. + +This does not admit an asset. diff --git a/docs/review/device-qa/ceiling-status-2026-11-05.md b/docs/review/device-qa/ceiling-status-2026-11-05.md new file mode 100644 index 0000000..74b383f --- /dev/null +++ b/docs/review/device-qa/ceiling-status-2026-11-05.md @@ -0,0 +1,11 @@ +# Ceiling status + +Read 2026-11-05 from `D021Ceilings.fromSimulation` on `ef68565`. No ceiling was changed. + +The scenarios that feed it stay in [peak-density-2026-11-05.md](peak-density-2026-11-05.md). The byte fields that settle a device run stay in [device-run-gates-2026-11-05.md](device-run-gates-2026-11-05.md). The expansion decision stays in [expansion-gate-2026-11-05.md](expansion-gate-2026-11-05.md). + +The schema is `d021-ceilings-001`. `fromSimulation` copies the report's aggregate peaks and sets the status to `simulationMeasured`. Resident bytes and atlas bytes stay nil, so `isSettled` is false. `withDeviceProfiling` stores both byte counts and sets the status to `settled`. `pendingDeviceProfiling` is the third status string. + +`metricsWithinCeilings` requires each of the seven peaks to be at most the stored ceiling. `sampleWithinSimulationCeilings` uses the same seven comparisons on one sample. `gateC011PassesSimulationBounds` requires every scenario in the report to sit within the ceilings. + +This does not settle a device. diff --git a/docs/review/device-qa/device-run-gates-2026-11-05.md b/docs/review/device-qa/device-run-gates-2026-11-05.md new file mode 100644 index 0000000..f4921c6 --- /dev/null +++ b/docs/review/device-qa/device-run-gates-2026-11-05.md @@ -0,0 +1,13 @@ +# Device-run gates + +Read 2026-11-05 from `DeviceRunEvidence.gateResults` on `ef68565`. No gate was changed. + +The filename stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). Gate C's thresholds stay in [frame-times-2026-11-05.md](frame-times-2026-11-05.md). The phone sample stays in [device-sample-2026-11-05.md](device-sample-2026-11-05.md). The streak stays in [run-streak-2026-11-05.md](run-streak-2026-11-05.md). The three-success bundle stays in [retest-checklist.md](retest-checklist.md). + +The schema is `device-run-evidence-001`. The task id is `T905`. `performanceFloor` counts a thermal state of `serious` or `critical` as one event, and every other string as zero. Its device-class parameter defaults to the string `iPhone 12`. The session call passes `hardwareModel`. + +`C-001` passes when the consecutive count is at least 3, frame time passes Gate C, memory warnings are 0, and thermal events are 0. `C-002`, `C-003`, `C-004`, `C-005`, and `C-006` pass when frame time passes Gate C. `C-007` passes when thermal events are 0. The source on each record is `deviceCapture`. + +`settledD021Ceilings` returns nil unless both resident bytes and atlas bytes are present. When both are present it returns the simulation ceilings after `withDeviceProfiling`. The session passes nil for the atlas bytes. The detached task returns before `exportReleaseCandidateWithBundledPlaytests` when those settled ceilings are nil. + +This does not label a phone. diff --git a/docs/review/device-qa/env-groups-2026-11-05.md b/docs/review/device-qa/env-groups-2026-11-05.md new file mode 100644 index 0000000..da46265 --- /dev/null +++ b/docs/review/device-qa/env-groups-2026-11-05.md @@ -0,0 +1,13 @@ +# Environment groups + +Read 2026-11-05 from `EnvironmentLibrary.isBacked` on `ef68565`. No texture was replaced. + +Nearest filtering stays in [env-filter-2026-11-05.md](env-filter-2026-11-05.md). The ground ids stay in [tiled-planes-2026-11-02.md](tiled-planes-2026-11-02.md). Clip backing stays in [frame-backing-2026-11-05.md](frame-backing-2026-11-05.md). + +The groups are `env_ground_`, `env_solid_`, `env_camera_`, `env_camera_captain_`, `env_prop_`, `env_motif_`, and `env_fog_`. The group of an id is the longest matching prefix, so a captain id is `captain` rather than `camera`. `isBacked` is true when the group has at least one declared id and every one of them has a delivered path. `path` returns nil unless that group is backed. + +A solid id `solid-04-civic-west` becomes `env_solid_04_civic_west`, by prefixing `env_` and replacing each `-` with `_`. The housing ids are `env_camera_municipal_dome`, `env_camera_storefront`, `env_camera_traffic_reader`, `env_camera_ornamental_civic`, and `env_camera_temporary_mast`. + +`bundled` reads `environmentAssetIds` from `presentation-assets-003`, or an empty list when that array is absent. It stores a path only for a declared id whose decision is `adaptedAdmitted` or `originalAccepted` and which has a runtime path. `coverage` counts delivered keys that are also declared, over the declared count. + +This does not replace a texture. diff --git a/docs/review/device-qa/pacing-window-2026-11-05.md b/docs/review/device-qa/pacing-window-2026-11-05.md new file mode 100644 index 0000000..58deac8 --- /dev/null +++ b/docs/review/device-qa/pacing-window-2026-11-05.md @@ -0,0 +1,11 @@ +# Pacing window + +Read 2026-11-05 from `PacingTimeline.observe` on `ef68565`. No window was changed. + +The zone names stay in [tiled-planes-2026-11-02.md](tiled-planes-2026-11-02.md). The tick rate stays on `SimulationClock.ticksPerSecond`, which is 60. + +This type does not write simulation state. `spawnAlley` starts at tick 0. A later observe records a segment only on its first start. `cameraCorridor` starts when the player zone is `Z-02`. `civicPlaza` starts on `waveStarted` for `M-A`, `pressureRoute` on `M-B`, and `lockdownRing` on `M-C`. `captainCourt` starts on `bossActivated`. `extraction` starts on `extractionArmed`. `endTick` is the first `runSucceeded`. + +Start seconds are the start tick divided by 60. `segmentsOffTarget` includes a segment that never started, and a segment whose start second, after `rounded()`, is outside its window. The windows are `spawnAlley` 0 through 5, `cameraCorridor` 5 through 30, `civicPlaza` 30 through 65, `pressureRoute` 65 through 110, `lockdownRing` 110 through 240, `captainCourt` 240 through 330, and `extraction` 330 through 360. `runWithinTarget` is nil until `runSucceeded`, and then it is whether that end second, after `rounded()`, is inside 300 through 480. + +This does not close T903 or T904. diff --git a/docs/review/device-qa/peak-density-2026-11-05.md b/docs/review/device-qa/peak-density-2026-11-05.md new file mode 100644 index 0000000..468a0f5 --- /dev/null +++ b/docs/review/device-qa/peak-density-2026-11-05.md @@ -0,0 +1,15 @@ +# Peak density + +Read 2026-11-05 from `PeakDensityProfiler.profile` on `ef68565`. No ceiling was changed. + +The digest prefix on the release-candidate filename stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). `totalLive` on a phone sample stays in [device-sample-2026-11-05.md](device-sample-2026-11-05.md). + +`profile` runs one complete-run scenario for each upgrade, under the key `complete-run-`, then `civic-pool-stress`. The report schema is `peak-density-001`. Each scenario keeps the max of cameras, enemies, projectiles, mines, civic-pool live, allocator next, and `totalLive`. Those maxes are independent. The aggregate is the same field-wise max across the scenarios. Canonical scenario keys are sorted. + +A complete run uses `Simulation.make(seed: 1)`. It samples, completes encounter `M-A`, samples, arms upgrade selection, samples, steps a command whose axes are 0 and whose upgrade index is that upgrade, samples, completes the combat graph, samples, steps a neutral command, samples, places the player on the extraction center, and samples after every later neutral step while the outcome is `playing`. + +The stress run uses seed 1, completes `M-A`, selects `ricochetPulse`, completes the combat graph, and fills the civic pool to `PerformanceThresholds.civicPoolCapacity`. It then takes 120 steps with `moveX` 32767, `moveY` 0, and dodge clear, and samples after each step. The full-stick limit stays in [player-movement-2026-11-04.md](player-movement-2026-11-04.md). + +Ordinary play does not call `profile`. + +This does not change a pool. diff --git a/docs/review/device-qa/playtest-gates-2026-11-05.md b/docs/review/device-qa/playtest-gates-2026-11-05.md new file mode 100644 index 0000000..072a17a --- /dev/null +++ b/docs/review/device-qa/playtest-gates-2026-11-05.md @@ -0,0 +1,11 @@ +# Playtest gates + +Read 2026-11-05 from `PlaytestEvidence.gateResults` on `ef68565`. No gate count was changed. + +The file load stays in [playtest-load-2026-11-05.md](playtest-load-2026-11-05.md). The directory stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). + +The schema is `playtest-evidence-001`. For `T903`, `G-001` passes when the session count is at least 5 and at least 4 sessions completed onboarding without instruction. `G-002` passes when the session count is at least 5 and at least 4 sessions described exposure correctly. For `T904`, `G-004` passes when the session count is at least 5 and at least 3 sessions began a voluntary replay. Any other task id returns an empty list. The source on each of those records is `playtest`. + +The canonical session stores `competentRunDurationMinutes` as the integer of `rounded()`, or null when the minutes are absent. + +This does not close T903 or T904. diff --git a/docs/review/device-qa/replay-matrix-2026-11-05.md b/docs/review/device-qa/replay-matrix-2026-11-05.md new file mode 100644 index 0000000..0152bd7 --- /dev/null +++ b/docs/review/device-qa/replay-matrix-2026-11-05.md @@ -0,0 +1,13 @@ +# Replay matrix + +Read 2026-11-05 from `ReplayMatrix.tripleRunDigest` on `ef68565`. No digest was changed. + +The envelope load stays in [replay-commands-2026-11-04.md](replay-commands-2026-11-04.md). + +The fixture version is `replay-matrix-001`. `load` requires that version and an `entries` array. Each entry needs an id, a fixture name, and an `expectedFinalDigest` of 64 characters. `tripleRunRequired` is the bool on the entry, or true when the value is absent or not a bool. + +`execute` loads the fixture. When that envelope's expected digest is nil, the run uses the entry's digest, and an `invalid` outcome throws `digestMismatch` with the result digest. When the envelope already carries an expected digest, a result digest that differs from the entry's digest throws `digestMismatch`. A failed execute throws `executionFailed`. + +`tripleRunDigest` runs `execute` three times. It throws `tripleRunMismatch` unless the three digests are equal, and then it returns that digest. + +This does not change a fixture. diff --git a/docs/review/device-qa/seam-checks-2026-11-05.md b/docs/review/device-qa/seam-checks-2026-11-05.md new file mode 100644 index 0000000..cef6dc8 --- /dev/null +++ b/docs/review/device-qa/seam-checks-2026-11-05.md @@ -0,0 +1,15 @@ +# Seam checks + +Read 2026-11-05 from `CivicSeamIdentity` on `ef68565`. No zone was moved. + +The seven zone names stay in [tiled-planes-2026-11-02.md](tiled-planes-2026-11-02.md). Bundle issues stay in [bundle-filter-2026-11-05.md](bundle-filter-2026-11-05.md). + +`zoneNamesMatchContract` requires each of those ids to carry that table's name on the manifest. The spine landmarks are `Z-02`, `Z-03`, `Z-06`, and `Z-07`. + +`threeGridCollision` requires the court to share the plaza's y and sit to its east, the service zone to sit north and east of the plaza, and the service zone to sit north and east of the spawn. `wedgeParcels` requires `Z-01` and `Z-04` each to have a half-size whose x differs from its y. `landmarkSightlines` requires the plaza east of transit, the court east of the plaza, the plaza north of transit, the court on the plaza's y, and Phoenix south of the court on the court's x. + +The prohibited labels are `golden gate`, `sutro`, `karl`, `market street`, `civic center`, `soma`, `chinatown`, and `cable car`. The label check lowercases the tutorial copy, the supplied captions, and the zone names, and rejects a string that contains one of those labels. The default captions are `Phoenix Steps open`. + +`catalogRejectsLiteralLandmarks` requires `legacy_san_francisco_landmark_bridge_distant_01` to be `rejected` with a nil runtime path. The content audit adds `literal-bridge-not-rejected` when that fails. It adds a token for each bundle issue. A runtime path whose id contains `_atlanta_` adds `non-sf-runtime-`. A runtime path whose id contains `landmark_bridge`, `_seal_`, or `_logo_` adds `prohibited-runtime-`. Notes that contain `copied artwork`, on a record with a runtime path, add `copied-artwork-runtime-`. The audit list is sorted. + +This does not close T802 or T807. From 7a5d4a2b49fbbb3a9c8f1a574c7030b792531e10 Mon Sep 17 00:00:00 2001 From: Daniel Meyer Date: Thu, 5 Nov 2026 15:07:21 -0800 Subject: [PATCH 3/3] Record the event order, the reachability checks, and the fairness reports. Device-qa notes for the publish sort, the upgrade match, the content bands, the awareness and heat load, the arena walk, the patrol proof, the camera-set checks, the destruction totals, the gate id spans, and the release-candidate collector. No number was changed. --- docs/review/device-qa/README.md | 10 +++++++++ .../device-qa/arena-reach-2026-11-05.md | 21 +++++++++++++++++++ .../device-qa/awareness-heat-2026-11-05.md | 9 ++++++++ .../device-qa/camera-fair-2026-11-05.md | 17 +++++++++++++++ .../device-qa/camera-receipt-2026-11-05.md | 7 +++++++ .../device-qa/candidate-collect-2026-11-05.md | 19 +++++++++++++++++ .../device-qa/content-bands-2026-11-05.md | 15 +++++++++++++ .../device-qa/event-order-2026-11-05.md | 11 ++++++++++ .../review/device-qa/gate-spans-2026-11-05.md | 11 ++++++++++ .../device-qa/patrol-fair-2026-11-05.md | 21 +++++++++++++++++++ .../device-qa/upgrade-match-2026-11-05.md | 15 +++++++++++++ 11 files changed, 156 insertions(+) create mode 100644 docs/review/device-qa/arena-reach-2026-11-05.md create mode 100644 docs/review/device-qa/awareness-heat-2026-11-05.md create mode 100644 docs/review/device-qa/camera-fair-2026-11-05.md create mode 100644 docs/review/device-qa/camera-receipt-2026-11-05.md create mode 100644 docs/review/device-qa/candidate-collect-2026-11-05.md create mode 100644 docs/review/device-qa/content-bands-2026-11-05.md create mode 100644 docs/review/device-qa/event-order-2026-11-05.md create mode 100644 docs/review/device-qa/gate-spans-2026-11-05.md create mode 100644 docs/review/device-qa/patrol-fair-2026-11-05.md create mode 100644 docs/review/device-qa/upgrade-match-2026-11-05.md diff --git a/docs/review/device-qa/README.md b/docs/review/device-qa/README.md index f87449a..ebc669a 100644 --- a/docs/review/device-qa/README.md +++ b/docs/review/device-qa/README.md @@ -296,5 +296,15 @@ Each note records one fact about bytes or draw calls already on main. None of th | [pacing-window-2026-11-05.md](pacing-window-2026-11-05.md) | The pacing windows | | [env-groups-2026-11-05.md](env-groups-2026-11-05.md) | A group, fully backed | | [seam-checks-2026-11-05.md](seam-checks-2026-11-05.md) | The seam checks | +| [event-order-2026-11-05.md](event-order-2026-11-05.md) | The published order | +| [upgrade-match-2026-11-05.md](upgrade-match-2026-11-05.md) | The upgrade block, matched | +| [content-bands-2026-11-05.md](content-bands-2026-11-05.md) | The bands, the angle, and the percents | +| [awareness-heat-2026-11-05.md](awareness-heat-2026-11-05.md) | The awareness block, and the heat table | +| [arena-reach-2026-11-05.md](arena-reach-2026-11-05.md) | The walk, and the spine | +| [patrol-fair-2026-11-05.md](patrol-fair-2026-11-05.md) | The patrol proof | +| [camera-fair-2026-11-05.md](camera-fair-2026-11-05.md) | The camera sets, checked | +| [camera-receipt-2026-11-05.md](camera-receipt-2026-11-05.md) | The destruction totals | +| [gate-spans-2026-11-05.md](gate-spans-2026-11-05.md) | The gate ids, and the unmapped ones | +| [candidate-collect-2026-11-05.md](candidate-collect-2026-11-05.md) | The candidate the collector builds | Next session: follow [retest-checklist.md](retest-checklist.md) (D-105 allows device checks on any build). diff --git a/docs/review/device-qa/arena-reach-2026-11-05.md b/docs/review/device-qa/arena-reach-2026-11-05.md new file mode 100644 index 0000000..e1fab5a --- /dev/null +++ b/docs/review/device-qa/arena-reach-2026-11-05.md @@ -0,0 +1,21 @@ +# The walk, and the spine + +Read 2026-11-05 from `ArenaReachability` on `ef68565`. No collision was changed. Presentation sprites never contribute. The live gate close stays in [forward-gates-2026-11-04.md](forward-gates-2026-11-04.md). The player radius stays in [player-sprite-bounds-2026-11-03.md](player-sprite-bounds-2026-11-03.md). The viewport pair stays in [camera-follow-2026-11-03.md](camera-follow-2026-11-03.md). [seam-checks-2026-11-05.md](seam-checks-2026-11-05.md) names `diagonalSpine`. + +`gridStep` is 16. The critical radius is 64, the combat radius is 80, the boss-corridor radius is 96, and the camera-stand radius is 64. The consecutive zone ids are `Z-01` through `Z-07`. + +`boxes` takes the permanent solids, then each gate whose id is in the closed set, then the extra solids. `isWalkable` is false when `Collision.clampCenter` moves the center. Otherwise it is true when the radius circle does not penetrate a solid. + +`flood` is a four-direction walk of those cells. `containsPoint` on a grid point reads that cell. Off the step, it also accepts the ceil cell on each axis that is off the step. `originWalkable` is true when the start is walkable or the queue is non-empty. + +`consecutiveZonesConnected` floods from the player spawn at the critical radius and requires every consecutive pair of zone boxes to intersect that flood. `bossCorridorClear` floods from the boss spawn at the boss-corridor radius and requires `Z-06`. `extractionReachable` uses the player radius from the spawn to the extraction center, and it closes `gate-boss-extraction` only when `bossGateClosed` is true. + +`maEscapeOpen` floods from the spawn with `gate-ma-forward` closed and asks whether `Z-03` intersects. `escapeApertureOpen` floods from the encounter trigger with that encounter's forward gate closed. The previous zone is `Z-02` for M-A, `Z-03` for M-B, and `Z-04` for M-C. That box is inset by `cellSizeUnits` on each half-size, floored at 0, and the flood must intersect the inset. + +`spawnAlleyProtected` is false when a socket's zone is `Z-01` or its position is inside `Z-01`, when an enemy socket, the elite, or the boss is inside `Z-01`, or when an enabled socket's cone contains the player spawn with a clear line of fire. + +`geometryInBounds` requires inclusive bounds on the player, elite, and boss spawns, the extraction box, the zones, the permanent solids, the gates, the triggers, the camera sockets, the enemy sockets, the extraction-pressure sockets, and the captain emitters. `authoredSolidOverlaps` names a spawn, extract center, elite, boss, trigger, or socket inside a permanent solid, a gate overlap of the spawn or of a socket, and a solid overlap of extraction, then sorts. `fieldOriginsInsideSolids` tests the truncated field-origin units against `boxes`. + +`viewportMatchesContract` requires the manifest viewport to equal the presentation camera on width, height, dead zone, and look-ahead, and the visible width and height to equal the pair in the camera-follow note. `diagonalSpine` requires `Z-01` through `Z-04`, each next center strictly greater in both x and y. `blockedCameraStands` returns the sockets that are not walkable at the supplied radius. + +This does not move a zone. diff --git a/docs/review/device-qa/awareness-heat-2026-11-05.md b/docs/review/device-qa/awareness-heat-2026-11-05.md new file mode 100644 index 0000000..d3e5d05 --- /dev/null +++ b/docs/review/device-qa/awareness-heat-2026-11-05.md @@ -0,0 +1,9 @@ +# The awareness block, and the heat table + +Read 2026-11-05 from `CombatContent.parseAwareness` on `ef68565`. No alert was added. The rank order and the live alert stay in [awareness-alerts-2026-11-04.md](awareness-alerts-2026-11-04.md). The stored heat counts stay in [heat-awareness-patrol-2026-11-04.md](heat-awareness-patrol-2026-11-04.md). + +An unknown awareness key, or a missing required key, fails closed. `appliesTo` must be an array of strings. An unknown name is `unknownArchetype`. `improperSearchDaemon` and `algorithmicModerate` are `wrongType`. Sight must be at least 1. Ally radius, drift percent, and drift stop must be at least 0. The ambush multiplier must be at least 1. A JSON boolean fails those integers. `surveillanceAlertState` must be a `DetectionState` raw value. `awareEncounters` must be an array of strings. `heatReinforcementsSpawnAware` must be a JSON boolean. + +`parseHeat` requires a known reinforcement archetype. `encounters` must be an array of strings. `byDetectionState` must name `hidden`, `observed`, `tracked`, `hunted`, and `lockdown`, each an integer at least 0. Any other key in that table is `wrongType`. + +This does not change a count. diff --git a/docs/review/device-qa/camera-fair-2026-11-05.md b/docs/review/device-qa/camera-fair-2026-11-05.md new file mode 100644 index 0000000..94d52f5 --- /dev/null +++ b/docs/review/device-qa/camera-fair-2026-11-05.md @@ -0,0 +1,17 @@ +# The camera sets, checked + +Read 2026-11-05 from `CameraFairness.evaluate` on `ef68565`. No socket was moved. The comment says this is never called from `ArenaManifest.bundled()` or `Simulation.make`. The legal-set enumeration stays in [camera-legal-set-2026-11-04.md](camera-legal-set-2026-11-04.md). The zone quotas stay in [placed-cameras-2026-11-04.md](placed-cameras-2026-11-04.md). The pulse range stays in [civic-pulse-2026-11-04.md](civic-pulse-2026-11-04.md). The grid step stays in [arena-reach-2026-11-05.md](arena-reach-2026-11-05.md). + +For each enabled socket the check stores whether the truncated origin is inside a solid, whether a circle of `mountCollisionRadiusUnits` penetrates Z-01, a field grid, and a pulse grid. A field cell is inside the cone at half the socket's field angle, with a clear line of fire. A pulse cell is walkable, within `Targeting.civicPulseRange` squared in Q8 of `targetAnchor`, with a clear line of fire from the cell to that anchor. + +A leaking socket is one whose mount penetrates Z-01, or whose field meets a walkable Z-01 cell. An unhittable socket has an empty pulse intersection with the spawn-reachable grid. Origin-in-solid sockets are listed on their own. + +Each legal set is keyed with `CameraPlacement.setKey`. Protected Z-01 is recorded when any selected mount penetrates Z-01, or the union field meets a walkable Z-01 cell. A choke is every gate box plus each consecutive-zone overlap. The key is recorded when any cell of a choke is covered by more than two fields. Pulse-stand and accessibility are both recorded when any selected pulse misses the reachable grid. + +`zeroContactMissing` walks the consecutive zones. The start is that zone's walkable cells outside the union field. An empty start is a miss. The flood stays on walkable cells outside the field, and a miss is when it does not meet the next zone's walkable cells outside the field. + +The branch flood starts at the M-B trigger cell, or at (1280, 1088) when that trigger is missing. Its blocked grid is empty, so the union field is not removed. The key is recorded when that cell is not walkable, or when the flood misses (1216, 1024) or (1600, 1152). + +Captain-safe is recorded when the boss corridor is not clear, or when no walkable court cell sits outside the union field. A missing court records the key. Extraction is recorded when the spawn flood does not contain the extraction center. That flood is computed once. Field-origin is recorded when a selected origin is inside a solid. Overlap is recorded when any pair overlaps at the mount radius or at the hit radius. That test is `dx² + dy² < (ra + rb)²`. + +This does not place a camera. diff --git a/docs/review/device-qa/camera-receipt-2026-11-05.md b/docs/review/device-qa/camera-receipt-2026-11-05.md new file mode 100644 index 0000000..24a91e9 --- /dev/null +++ b/docs/review/device-qa/camera-receipt-2026-11-05.md @@ -0,0 +1,7 @@ +# The destruction totals + +Read 2026-11-05 from `CameraDestructionReceiptSummary.project` on `ef68565`. No spike was added. The camera line stays in [hud-bindings-2026-11-04.md](hud-bindings-2026-11-04.md). + +Damaged is the count of cameras whose integrity is above 0 and below 3. Destroyed is `destructions.count`. Tamper is the sum, over those records, of `max(0, exposureAfter - exposureBefore)`. Objective destroyed is that same destroyed count. Objective total is `HUDLayout.cameraObjectiveTotal`. Objective complete is `networkBlackout`. + +This does not emit an event. diff --git a/docs/review/device-qa/candidate-collect-2026-11-05.md b/docs/review/device-qa/candidate-collect-2026-11-05.md new file mode 100644 index 0000000..ca2227d --- /dev/null +++ b/docs/review/device-qa/candidate-collect-2026-11-05.md @@ -0,0 +1,19 @@ +# The candidate the collector builds + +Read 2026-11-05 from `ReleaseCandidateEvidenceCollector.collect` on `ef68565`. No ceiling was settled. The decision stays in [expansion-gate-2026-11-05.md](expansion-gate-2026-11-05.md). The identity and the pin stay in [replay-identity-2026-11-04.md](replay-identity-2026-11-04.md). The file name stays in [evidence-names-2026-11-05.md](evidence-names-2026-11-05.md). The profile stays in [peak-density-2026-11-05.md](peak-density-2026-11-05.md). The ceiling statuses stay in [ceiling-status-2026-11-05.md](ceiling-status-2026-11-05.md). The triple run stays in [replay-matrix-2026-11-05.md](replay-matrix-2026-11-05.md). The gate spans stay in [gate-spans-2026-11-05.md](gate-spans-2026-11-05.md). + +The evidence schema is `release-candidate-evidence-001`. + +`DefectRegistry.tracked` is empty. `openSeverityOneDefects` and `openSeverityTwoDefects` count open records of those severities, so both are 0. Its canonical object uses schema `defect-registry-001` and stores those two counts plus the tracked records. A missing task reference is null. + +`Options` uses those registry counts when the caller passes nil. `d013Settled` defaults false. The playtest list and the device list default empty. `d021DeviceProfiling` defaults nil. + +`collect` profiles, then builds ceilings with `D021Ceilings.fromSimulation`. A supplied device value replaces that object. It stores each loaded replay-matrix entry's triple-run digest. For each upgrade it makes a simulation at seed 1, runs `testing_completeRunSuccess`, and stores `terminalDigest`, or `digest()` when that is nil. + +Gate results start as each automated evidence id with `passed` true. Playtest results and device results are appended. The linked automated ids are the automated evidence ids, sorted. The pending ids are the required families minus the supplied ids, sorted. + +The record stores `ContractVersions.specificationCommit`, `ReplayIdentity.current`, those maps, the linked ids, the pending ids, the decision, the open counts, `d013Settled`, whether the ceilings are settled, the sha256 of the peak canonical JSON, and the ceilings. + +Ordinary play does not call `collect`. + +This does not close T905 or T908. diff --git a/docs/review/device-qa/content-bands-2026-11-05.md b/docs/review/device-qa/content-bands-2026-11-05.md new file mode 100644 index 0000000..85f3db6 --- /dev/null +++ b/docs/review/device-qa/content-bands-2026-11-05.md @@ -0,0 +1,15 @@ +# The bands, the angle, and the percents + +Read 2026-11-05 from `CombatContent.decode` on `ef68565`. No band was moved. The phase table stays in [elite-boss-attacks-2026-11-04.md](elite-boss-attacks-2026-11-04.md). The court floor stays in [player-integrity-2026-11-04.md](player-integrity-2026-11-04.md). The upgrade match stays in [upgrade-match-2026-11-05.md](upgrade-match-2026-11-05.md). + +A payload that is not a JSON object throws `invalidJSON`. A `CombatContentError` is rethrown. Any other parse failure is `invalidJSON`. + +The load reads elite, boss, standard enemies, encounters, heat, awareness, patrol, and player. `validateUpgrades` runs last and is not stored. The elite and boss integer fields use the dictionary `int` cast. That cast does not call `JSONScalar.isBool`. + +`parseBossBands` requires the phase count to equal `BossPhase.receiptOrder.count`. Each id must match that order. Each `minHp` is an integer, not a boolean, in 1 through the boss hp, and strictly below the previous floor. The last floor must be 1, or the error is `wrongType` at `boss.phases[3].minHp`. + +`parsePatrol` requires `sightHalfAngleMilliDegrees` to be a key of `PatrolSpec.exactCosineSquared`. Integrity, speed, sight, that half-angle, and arrival must be at least 1. Dwell must be at least 0. A JSON boolean fails those fields. + +`parsePlayer` requires integrity at least 1. `damageTakenPercent`, `courtThresholdRestorePercent`, and `courtQuietRestorePercent` must be at least 0 and at most 100. + +This does not change a floor. diff --git a/docs/review/device-qa/event-order-2026-11-05.md b/docs/review/device-qa/event-order-2026-11-05.md new file mode 100644 index 0000000..fcf2935 --- /dev/null +++ b/docs/review/device-qa/event-order-2026-11-05.md @@ -0,0 +1,11 @@ +# The published order + +Read 2026-11-05 from `EventBuffer.publish` on `ef68565`. No event was added. + +`AuthoritativeEvent` stores schema `event-001`. Init copies `type.ordinal` into `ordinal` and stores `sequence` 0. The cases run from `runStarted` to `enemyAlerted` in declaration order, and the ordinal is ten times that position, counting from 1. + +`canonical` stores the schema, the tick, the ordinal, the sequence, the type's raw value, the primary and secondary ids as decimal strings or null, and the payload. It leaves out phase and insertion. + +`emit` appends one event and then adds one to the insertion counter. `publish` sorts by phase, then ordinal, then primary raw with a nil id as 0, then secondary raw with a nil id as 0, then insertion. It writes `sequence` to the sorted index, clears the pending list, sets insertion back to 0, and returns that list. + +This does not add an event type. diff --git a/docs/review/device-qa/gate-spans-2026-11-05.md b/docs/review/device-qa/gate-spans-2026-11-05.md new file mode 100644 index 0000000..401ac62 --- /dev/null +++ b/docs/review/device-qa/gate-spans-2026-11-05.md @@ -0,0 +1,11 @@ +# The gate ids, and the unmapped ones + +Read 2026-11-05 from `GateRegistry` on `ef68565`. No gate was closed. The decision stays in [expansion-gate-2026-11-05.md](expansion-gate-2026-11-05.md). The playtest counts stay in [playtest-gates-2026-11-05.md](playtest-gates-2026-11-05.md). The device gates stay in [device-run-gates-2026-11-05.md](device-run-gates-2026-11-05.md). + +`allGateIds` concatenates `functional`, `determinism`, `stability`, `visual`, `arena`, `accessibility`, and `playtest`. + +`functional` is `A-001` through `A-022`. `determinism` is `B-001` through `B-017`. `stability` is `C-001` through `C-011`. `visual` is `D-001` through `D-021`. `arena` is `E-001` through `E-020`. `accessibility` is `F-001` through `F-017`. `playtest` is `G-001` through `G-007`. + +The evidence kinds are `automatedTest`, `deviceCapture`, `manualReview`, and `playtest`. `evidence(for:)` returns the first record with that id. `automatedGateIds`, `deviceGateIds`, and `playtestGateIds` filter by kind. `unmappedGateIds` returns every id in `allGateIds` that the evidence list does not name. + +This does not close T902. diff --git a/docs/review/device-qa/patrol-fair-2026-11-05.md b/docs/review/device-qa/patrol-fair-2026-11-05.md new file mode 100644 index 0000000..40deaeb --- /dev/null +++ b/docs/review/device-qa/patrol-fair-2026-11-05.md @@ -0,0 +1,21 @@ +# The patrol proof + +Read 2026-11-05 from `PatrolFairness.evaluate` on `ef68565`. No patrol was moved. The zone quotas stay in [placed-cameras-2026-11-04.md](placed-cameras-2026-11-04.md). The grid step stays in [arena-reach-2026-11-05.md](arena-reach-2026-11-05.md). + +The header says the player takes no part in the motion. `passes` is all five lists empty: waypoint violations, unreached waypoints, unproven, protected coverage, and blocked ticks. + +`evaluate` starts with the waypoint list. Empty patrols return that report. More than three members appends `more than three members: the joint key does not fit` to unproven and returns. + +The Z-02 subset size is that zone's `requiredByZone` count, or the literal 2 when the zone is absent. Each such subset must contain a `tutorialEligible` socket and be compatible. The Z-03 size is that count, or the literal 1. Each such subset must be compatible. Compatible is false when any socket lists another's id in `incompatibleSocketIds`. + +Base solids are the permanent solids plus gates whose `initiallyClosed` is true. A mount id is `SelectedCamera.mountSolidPrefix` plus the socket id, and its half-size is `mountCollisionRadiusUnits`. A missing standard-enemy stat appends `a member's archetype has no content`. `cycles` stores that subset's tick count and period. A member whose states come within that member's radius plus 2 of a mount outside Z-02 is unproven. + +Each waypoint must lie inside its zone and outside every permanent solid, every gate, and every encounter trigger. + +`jointOrbit` steps the members with `EnemySystem.moveUnaware`. The joint key packs 21 bits per member. A repeated key returns, and the period is the distance back to the first sighting. The cap is the literal 36_000. Reaching the cap returns period 0. An interned id at or above `1 << 21` returns period 0 with the timeline so far. + +When `dwellTicks` is above 0, a waypoint counts as arrived where a stored state still names that target and `dwellRemaining` equals `dwellTicks` minus 1. When `dwellTicks` is 0, the arrived set is each stored state's previous target, wrapping around the waypoint count. + +`check` records protected coverage when a stored state sees the player spawn, or a Z-01 point on a lattice of half the grid step. The lattice includes the edges, walkable or not. Sight is `PatrolSystem.inCone` and a clear line of fire. The route grid is the union of Z-01, Z-02, and Z-03. A tick whose route still exists after one member's cone is widened to its whole-orbit union passes. Otherwise the exact cones decide. A failing tick is a blocked tick. The first three are named, and a count above 3 adds one summary line. + +This does not move a member. diff --git a/docs/review/device-qa/upgrade-match-2026-11-05.md b/docs/review/device-qa/upgrade-match-2026-11-05.md new file mode 100644 index 0000000..c38bcd1 --- /dev/null +++ b/docs/review/device-qa/upgrade-match-2026-11-05.md @@ -0,0 +1,15 @@ +# The upgrade block, matched + +Read 2026-11-05 from `CombatContent.validateUpgrades` on `ef68565`. No upgrade was added. The shipped numbers stay in [upgrade-effects-2026-11-03.md](upgrade-effects-2026-11-03.md). The continuation test stays in [ricochet-continuation-2026-11-04.md](ricochet-continuation-2026-11-04.md). + +`JSONScalar.isBool` is true when the value is an `NSNumber` whose CoreFoundation type is a boolean. `strictInts` rejects an unknown key, a missing key, a JSON boolean, a non-integer, and a value below that key's minimum. + +`validateUpgrades` runs after the rest of `decode` and is not stored. A key outside `signalJammer`, `ricochetPulse`, and `ghostStep` is `wrongType`. Each compared integer must equal the simulation constant named at that field. + +The jammer pulse ratio must be two integers, not booleans, with a denominator above 0. It is compared to `ExposureState.jammerPulseRatio`. The other jammer fields are compared to that type's contact reduction, minimum contact, heat-reinforcement reduction, and to `UpgradeID.signalJammer.selectionIndex`. + +`continuationRequiresEnemyKill` must be a boolean equal to `Targeting.ricochetContinuationRequiresEnemyKill`. Continuations must equal the literal 1. Range, enemy damage, and camera damage are compared to `Targeting.ricochetRange`, `Targeting.enemyDamage`, and `Targeting.cameraDamage`. + +Ghost dodge speed is compared to `PlayerBody.ghostDodgeSpeedUnitsPerTick` times 60. Dodge ticks and cooldown are compared to `PlayerBody.dodgeDurationTicks` and `PlayerBody.ghostDodgeCooldownTicks`. Post-dodge camera immunity is compared to `PlayerBody.ghostCameraImmunityTicks + 1 - PlayerBody.dodgeDurationTicks`. + +This does not add a fourth upgrade.