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.