P23-03: 生活の観察

This commit is contained in:
Agent Control Runner
2026-10-15 10:32:29 +09:00
parent 0b7727a66c
commit 21b5d3a31a
36 changed files with 4133 additions and 66 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,253 @@
# P23-03 offline repair — evidence gap
Status: blocked, not static-complete. No acceptance credit. The failed 0b7727a
OS run remains failure history. No receipts, mirror files, product, values,
styles or test gates were changed. No git writes or engine/OS/collector/full
runs were performed.
## Corrected direction
The three previous pixel assumptions are superseded:
1. `viewport_to_screen` maps to content coordinates, not screenshot pixels.
Actual request-0002 and request-0004 CUA receipt calibration records window
size 1440x810, screenshot 1440x842, titlebar 32, backing scale 1 and stretch 1.
The image lookup must apply the calibrated content origin and independently
check agreement with actual CUA click offsets; the number 32 must not become
a checker constant. Existing affine composition checks remain required.
2. Source RGB cannot be required unchanged after JPEG conversion, NO_STAFF
dimming, ghost outlines and antialiasing. Palette identity is source
provenance, not a screenshot color oracle. Pixel proof needs measured
within-image noise and independent expected-class/state/shift alternatives.
3. Every report row must be accounted for, but not every row is visible. Use
report/audit fields for visible, clipped/clamped, outside with reason,
ghost/inactive and facility-state dimming. Require eligible visible coverage
of all six categories on both FRONT and BACK. Never silently skip a row.
The spec requires draw-consumed rows, pixel rectangles and provenance to
correspond; it does not require unchanged source RGB. The function docstring
now explicitly warns that the implementation still uses the superseded checks.
## Verified material and blocking dependency
Mirror:
`/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/offline-os-0b7727a-r07`
All 94 files named by MIRROR_INDEX.json were read and their SHA256 verified;
zero missing files or mismatches. The only indexed conversion record is
`os-pair/A/writer-request-0002/root-early-conversion.json`, with stdout/stderr.
The only decoded PNG is the corresponding `root-early-decoded.png`, SHA256
`206a9aafdc50d1f2f330690e14390de8c7ed5b0df024cedf8f2fbd35326f455a`.
The other three required stable screenshots are JPEG bytes despite .png names:
- `os-pair/A/writer-request-0002/screen-after.png`
- `os-pair/A/writer-request-0004/screen-before.png`
- `os-pair/A/writer-request-0004/screen-after.png`
Their magic starts `ffd8ffe000104a46`. No frozen decoded PNG/conversion records
for these files are in the mirror. Searching the original source root named
by MIRROR_INDEX for `*decoded*.png` and `*conversion*.json` also returned none.
The existing conversion verifier requires a separate PNG, original raw hash,
CUA call attribution and pinned `/usr/bin/sips` conversion record. Creating
replacement conversion assertions, rewriting receipt paths, or silently
changing that converter authority would not preserve this proof gate.
Minimal next input: Root supplies separate decoded PNGs and genuine conversion
records/logs for those three existing raw images, adds their hashes to a new
mirror inventory while preserving the original inventory and receipts. This
does not require another OS capture. Alternatively the commander must explicitly
specify an offline Python JPEG decoder authority and its provenance contract.
Worker must not execute sips under the Python-only direction.
Until then the requested full frozen before/after comparison, BACK cropped
pixel fixtures, calibrated noise and per-row pixel results cannot be submitted
as complete. Geometry/report preparation alone cannot meet all requested tests.
The behavioral repair and new fixtures/tests have not been implemented.
## Local commands and outputs
These read-only Python checks used the mirror path above as `p`:
```python
import json, pathlib, hashlib
p = pathlib.Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/offline-os-0b7727a-r07')
j = json.loads((p / 'MIRROR_INDEX.json').read_text())
bad = [r['path'] for r in j['files'] if not (p/r['path']).is_file()
or hashlib.sha256((p/r['path']).read_bytes()).hexdigest() != r['sha256']]
print('indexed', len(j['files']), 'mismatches', bad)
print('conversion records', [r['path'] for r in j['files'] if 'conversion' in r['path']])
```
Output:
```
indexed 94 mismatches []
conversion records ['os-pair/A/writer-request-0002/root-early-conversion.json', 'os-pair/A/writer-request-0002/root-early-conversion.json.stderr', 'os-pair/A/writer-request-0002/root-early-conversion.json.stdout']
```
Calling the existing `screenshot_pixels(p/name)` for each of the three paths
above (without manufacturing a conversion argument) produced:
```
writer-request-0002/screen-after.png: JPEG screenshot requires conversion record
writer-request-0004/screen-before.png: JPEG screenshot requires conversion record
writer-request-0004/screen-after.png: JPEG screenshot requires conversion record
```
Command: `python3 tools/test_p23_life_observation_evidence.py`
```
................................
----------------------------------------------------------------------
Ran 32 tests in 0.058s
OK
```
These are existing unit tests, not the requested new repair tests, and do not
prove the repair or acceptance. Root native/OS/full review remains pending.
Seven conditions, six categories, 30/88, no partial credit, timeout 900 and
cap null remain unchanged.
## Follow-on: 106-entry mirror (2026-10-15)
The missing-conversion dependency above is **resolved**, not a current blocker.
The prior text is retained as failure history. The worker read all 106 indexed
files and verified every byte count and SHA256, plus the preserved initial94
inventory SHA and unchanged entries. No mirror or original receipt was edited.
`OFFLINE_MIRROR_106_DIAGNOSTICS.json` contains the explicit original absolute
path -> mirrored absolute path -> equal indexed SHA256 mapping for all106 files,
including the eight named PNG/conversion files. Each conversion was checked
against its corresponding actual CUA screenshot call ID/raw hash, pinned sips
binary SHA, original argv, output PNG magic/dimensions/hash and stdout/stderr
hashes. Original raw/png path strings and argv remain unchanged. A read-only
resolver locates indexed mirrored bytes; it does not rewrite conversion records.
Direct reads of the source artifact directory were denied by the host
(`PermissionError: Operation not permitted`); equality to the frozen inventory
and conversion-record hashes was verified, not a fresh direct source-directory
comparison. This limitation is not represented as a missing PNG conversion.
New `--offline-mirror` preparation diagnostics check content/image dimensions,
backing scale, content origin derived from recorded titlebar, affine composition,
and agreement with the separately recorded click events. They independently
derive existing audit row identities/local rectangles from report positions and
source palette provenance. Every existing audit row has visible/clipped/outside
accounting and a calibrated screenshot rectangle. Room enum/operation names are
read from the actual FacilityInstance source. Dimming is derived from operation,
not merely the NO_STAFF state name. These are preparation diagnostics, not a
replacement acceptance pixel gate. In particular, audit font_width remains an
engine assertion, and receipt click events have not been verified against the
full helper-definition/tool trace. No source RGB tolerance was invented.
Actual frozen-frame results:
- FRONT request0002: 9 audit rows (3 room,1 queue,2 guest,3 business), all visible.
EV/staff/goods coverage pending. Two rooms are INACTIVE and one OPERATING.
One actual draw-consumed traffic overlay at cell[-6,2,0], local rect
[261,294,60,92], has no corresponding rectangle-audit row.
- BACK request0004: 4 audit rows (2 room,2 business), all visible and both rooms
INACTIVE. Queue/EV/staff/goods coverage pending; no eligible traffic overlay.
- Before/after temporal differences within all13 calibrated audit rectangles
are zero. This establishes stability only, not spatial JPEG error or correct
drawing class/state. It supplies no pixel acceptance credit.
- The existing independent typed-stream checker recomputed all14 frozen stream
records successfully; the CLI still returned blocked/pending(exit2), as required.
Current repair status: **incomplete / blocked**, not genuinely submitted.
The legacy `compare_rectangles` and its normal-OS call site have deliberately
not been relabelled as repaired. Remaining worker work includes an independent
expected-class/state/shift pixel discriminator with measured within-image noise,
state negatives, full ghost/inactive/traffic draw accounting, independent badge
width provenance and integration into the strict OS checker. The added
preparation path does not solve these items and must not be used for acceptance.
Missing frozen provenance includes the FRONT traffic-overlay rectangle audit,
opposite-layer ghost draw audit, and independent font measurements for badge
widths. Existing reports/source can support further preparation; these missing
records do not establish that a fresh OS capture is already necessary.
Local validation: Python AST syntax checks; 42 checker unit tests (existing32
plus10 preparation tests); actual mirrored checker commands above. Tests cover
wrong titlebar/click shifts, PID, nonfinite scales, clipped/outside rectangles,
measured temporal noise, report rectangle/identity/coverage/palette tampering.
They do not include the still-required pixel-state discriminator negatives.
No Godot, native, sips execution, collector, OS run, full regression, git write,
branch/integration, Runner action or agent delegation occurred. Root native,
complete A/B FRONT/BACK pan/zoom/distinct-PID OS matrix, mandatory full regression
and independent Opus review remain pending. Historical baselines remain32 tests,
17+17 semantic,319 native,7153/2585 and7/6/30/88; the new local42 count is not a
replacement acceptance baseline. Timeout900 and cap null are unchanged.
## Owner proof-method repair (2026-10-15)
The owner's recorded decision supersedes the proposed automatic class/state/
style discriminator, shift/swap alternative scoring, measured-noise gate and
independent badge-width measurement gate. They are no longer required work.
Palette RGB is provenance only. Font width remains the recorded engine value;
no independent font measurement or numeric image tolerance was introduced.
All earlier entries above are retained as history, not current requirements.
Implemented within the narrowed Python edit scope:
- Shared `frame_rows` now handles both offline and normal OS screenshot frames,
uses receipt calibration checked against actual click events, checks affine
composition, independently derives row identity/local geometry/text/palette,
validates room state/operation and actor enum names, and retains every audit
row with visibility, clipping reason, calibrated image rectangle and failures.
- The legacy `compare_rectangles` and its tests are removed and replaced by
grounded negatives. Fully outside eligible image crops and exactly uniform
crops fail; source RGB is never compared to screenshot RGB.
- `drawing_rectangle_rows` now expects every draw-consumed traffic overlay.
Missing/extra rows fail the mechanical frame, without synthesizing audit rows.
- Pure-Python PNG crops and review manifests are written for each frozen frame.
Manifests bind decoded whole-image/raw/conversion/request/receipt hashes,
per-row source hash, recorded font width, state, operation, layer and geometry.
A separate SHA file hashes the exact manifest bytes. Normal OS frames use the
same path and emit per-image review materials. Pixel categories receive no
credit without an independent direct-image review verdict bound to that SHA,
covering every eligible row with category/state/position verdicts. The worker
created no actual review verdict; synthetic verdict files exist only inside
temporary unit-test fixtures.
Current scope blocker, found by reading the actual dependencies:
`tests/fixtures/p23-03/passive_observation.gd:150` owns `draw_rectangles`, not
`world_preview.gd`. Its explicit room/queue/cabin/actor/badge loops at lines
152-171 do not consume traffic audit rows from the view or report. The transform
and palette audit path at lines172-182 only processes that locally built array.
`world_preview.gd:762` draws the traffic overlay, but has no existing rectangle
audit emitter or callback. Changing only world_preview cannot connect traffic
rows to the actual request's `rectangles` field without altering the fixture.
No inert emitter, mislabelled badge, or checker-generated observed audit was
added to pretend this connection exists. World source remains unchanged.
Minimal separate scope proposal: permit one read-only traffic-row loop in
`passive_observation.gd::draw_rectangles`, before its existing transform/palette
loop, consuming report traffic rows with density>0 or temporary_block. Use
category `traffic`, comma-separated cell coordinates as id, the report rectangle
and `scenes/main/world_preview.gd` as style source. Alternatively explicitly
authorize wiring a view-owned traffic audit emitter into that same fixture.
The broad contract lists this fixture, but the latest finish recipe explicitly
narrows edits to checker/tests and world only; the worker did not override it.
Step4 is therefore incomplete and this repair is **blocked**, not submitted.
Local validation: both Python files parse with `ast.parse`; 47 local checker
tests pass. New tests exercise blank crops, missing/extra/traffic rows, enum/name
errors, affine/click calibration shifts, image-outside crops, exact crop hashes,
wrong-manifest/missing/extra/rejected review rows and no credit without review.
The r07 offline CLI verified all106 indexed entries and exited2 as blocked:
- FRONT request0002: expected10 rows, observed9; mechanical failure
`offline rectangle coverage`. The traffic overlay at [-6,2,0] remains a genuine
unaudited overlay. EV/staff/goods coverage pending; zero pixel credit.
- BACK request0004: expected4/observed4; mechanical checks pass.
Queue/EV/staff/goods coverage pending; zero pixel credit.
Current review artifacts are in `offline-r07-review/`; complete diagnostics are
in `OFFLINE_REPAIR_OWNER_METHOD_DIAGNOSTICS.json`. The prior
`OFFLINE_MIRROR_106_DIAGNOSTICS.json` is preserved unchanged.
Still pending: Root frozen native checks, fresh A/B FRONT/BACK ordinary pan/zoom
OS observation and formal distinct-PID resume, the exact full177 regression,
and independent Opus direct-image/all-condition review. No Godot, Runner,
native/OS, full regression, git write, integration or agent execution occurred.
All7 conditions, all6 categories on both layers/layouts, source recomputation,
raw/request/CUA/conversion provenance, calibrated geometry, 30/88 without partial
credit, timeout900 and cap null remain unchanged. No acceptance PASS is claimed.
@@ -0,0 +1,922 @@
{
"errors": [
"offline rectangle coverage"
],
"expected_row_count": 10,
"images": [
{
"conversion_sha256": "d9e92a5b1925d6e1855f6eaff2820007d7db8dd79a5348ec801a38251b40fe90",
"raw_sha256": "ab073ed1055ca2356a050fdd47176c454c109ebe5c008b194ea8a5f149efa7f5",
"receipt_sha256": "184d50e702232ce259ca75b830992cb8e91d5175289a21812661325bf0024860",
"request_sha256": "172dc9a8f4bb684583f7417ac4c544172ee66b4134845fe540326df5598e6167",
"whole_image_sha256": "206a9aafdc50d1f2f330690e14390de8c7ed5b0df024cedf8f2fbd35326f455a"
},
{
"conversion_sha256": "b25b2224963f9a02ad87675969ab51c90aff29891b136095198afc2fe95acb73",
"raw_sha256": "7027a7f1813aa8c9fcff38e6e4b022ed718d6f9c9fd13d31ecc3d6d10818ac10",
"receipt_sha256": "184d50e702232ce259ca75b830992cb8e91d5175289a21812661325bf0024860",
"request_sha256": "172dc9a8f4bb684583f7417ac4c544172ee66b4134845fe540326df5598e6167",
"whole_image_sha256": "e6ab48ad864a1b5fdca2da587d95fe7949ac0c59e664a4524db66e3e861a73aa"
}
],
"mechanical_pass": false,
"rows": [
{
"category": "room",
"crops": [
{
"image_index": 0,
"path": "row-0000-image-0.png",
"sha256": "0811d7a2ac82283233b2b5179ddc6ac32a86fac3a211febae2c706ba36513286"
},
{
"image_index": 1,
"path": "row-0000-image-1.png",
"sha256": "0811d7a2ac82283233b2b5179ddc6ac32a86fac3a211febae2c706ba36513286"
}
],
"dimmed": true,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": null,
"id": "facility_00000002",
"layer": 0,
"local_rect": [
131.0,
388.0,
256.0,
192.0
],
"mechanical_pass": false,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 0,
"screenshot_rect": [
210.0,
485.0,
256.0,
192.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"34413b",
"8e6348",
"c39665",
"eee5c9",
"608d84",
"c6b999",
"a6b6a0",
"d4c7a5",
"acb9a5",
"897487",
"f1e9cb",
"b39b77",
"615563",
"a57852",
"587f7b",
"edc97c",
"d6ad52",
"709b94",
"a37b91",
"72b6b8",
"c1e0d4",
"445652",
"a2b2a3",
"d3aa8f",
"ca9c62",
"ffe9a8",
"6c8993",
"adc5b4",
"52836c",
"c7c6a6",
"a8b6aa",
"d8bf89",
"aec0bf",
"8a7155",
"617f82",
"3e514e",
"404f63",
"83958a",
"283a39",
"586c62",
"526177",
"dcc08b",
"9daa8d",
"879eaa",
"556e62",
"4d6472",
"e2bd6c",
"ba7157",
"bfbb9a",
"a9aa91",
"9ab9b3",
"d5e3d0",
"f2ca81",
"fff0d6",
"a67c53"
],
"style_source": "scripts/ui/pixel_tower_art.gd",
"style_source_sha256": "71df9b8400cb883a5bb1c89729f80c77e734df503fc54154be3e486adb8391df",
"text": null
},
{
"category": "room",
"crops": [
{
"image_index": 0,
"path": "row-0001-image-0.png",
"sha256": "bb79fa3c9cc46f2f7b9d31b2de85c3b6f6b4446718d3614cfeeba818ea87cb69"
},
{
"image_index": 1,
"path": "row-0001-image-1.png",
"sha256": "bb79fa3c9cc46f2f7b9d31b2de85c3b6f6b4446718d3614cfeeba818ea87cb69"
}
],
"dimmed": true,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": null,
"id": "facility_00000003",
"layer": 0,
"local_rect": [
1027.0,
388.0,
256.0,
192.0
],
"mechanical_pass": false,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 1,
"screenshot_rect": [
1106.0,
485.0,
256.0,
192.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"34413b",
"8e6348",
"c39665",
"eee5c9",
"608d84",
"c6b999",
"a6b6a0",
"d4c7a5",
"acb9a5",
"897487",
"f1e9cb",
"b39b77",
"615563",
"a57852",
"587f7b",
"edc97c",
"d6ad52",
"709b94",
"a37b91",
"72b6b8",
"c1e0d4",
"445652",
"a2b2a3",
"d3aa8f",
"ca9c62",
"ffe9a8",
"6c8993",
"adc5b4",
"52836c",
"c7c6a6",
"a8b6aa",
"d8bf89",
"aec0bf",
"8a7155",
"617f82",
"3e514e",
"404f63",
"83958a",
"283a39",
"586c62",
"526177",
"dcc08b",
"9daa8d",
"879eaa",
"556e62",
"4d6472",
"e2bd6c",
"ba7157",
"bfbb9a",
"a9aa91",
"9ab9b3",
"d5e3d0",
"f2ca81",
"fff0d6",
"a67c53"
],
"style_source": "scripts/ui/pixel_tower_art.gd",
"style_source_sha256": "71df9b8400cb883a5bb1c89729f80c77e734df503fc54154be3e486adb8391df",
"text": null
},
{
"category": "room",
"crops": [
{
"image_index": 0,
"path": "row-0002-image-0.png",
"sha256": "b90d74efdd21be0eb69555138dccacb62100825a908c6f17958e17a46321056b"
},
{
"image_index": 1,
"path": "row-0002-image-1.png",
"sha256": "b90d74efdd21be0eb69555138dccacb62100825a908c6f17958e17a46321056b"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": null,
"id": "facility_00000007",
"layer": 0,
"local_rect": [
131.0,
292.0,
128.0,
96.0
],
"mechanical_pass": false,
"operation": "OPERATING",
"reason": "whole rectangle in viewport",
"row_index": 2,
"screenshot_rect": [
210.0,
389.0,
128.0,
96.0
],
"state": "OPEN",
"state_name": "OPEN",
"style_colors": [
"34413b",
"8e6348",
"c39665",
"eee5c9",
"608d84",
"c6b999",
"a6b6a0",
"d4c7a5",
"acb9a5",
"897487",
"f1e9cb",
"b39b77",
"615563",
"a57852",
"587f7b",
"edc97c",
"d6ad52",
"709b94",
"a37b91",
"72b6b8",
"c1e0d4",
"445652",
"a2b2a3",
"d3aa8f",
"ca9c62",
"ffe9a8",
"6c8993",
"adc5b4",
"52836c",
"c7c6a6",
"a8b6aa",
"d8bf89",
"aec0bf",
"8a7155",
"617f82",
"3e514e",
"404f63",
"83958a",
"283a39",
"586c62",
"526177",
"dcc08b",
"9daa8d",
"879eaa",
"556e62",
"4d6472",
"e2bd6c",
"ba7157",
"bfbb9a",
"a9aa91",
"9ab9b3",
"d5e3d0",
"f2ca81",
"fff0d6",
"a67c53"
],
"style_source": "scripts/ui/pixel_tower_art.gd",
"style_source_sha256": "71df9b8400cb883a5bb1c89729f80c77e734df503fc54154be3e486adb8391df",
"text": null
},
{
"category": "queue",
"crops": [
{
"image_index": 0,
"path": "row-0003-image-0.png",
"sha256": "eeb6b5a301d017693ba3acd47d22b63348bc6536c5ddcfd593aa0d593e256aac"
},
{
"image_index": 1,
"path": "row-0003-image-1.png",
"sha256": "eeb6b5a301d017693ba3acd47d22b63348bc6536c5ddcfd593aa0d593e256aac"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": 36.0,
"id": "0",
"layer": 0,
"local_rect": [
259.0,
276.0,
42.0,
14.0
],
"mechanical_pass": false,
"operation": null,
"reason": "whole rectangle in viewport",
"row_index": 3,
"screenshot_rect": [
338.0,
373.0,
42.0,
14.0
],
"state": {
"members": [
"guest/guest-auto-00000019"
],
"overflow": 0,
"visible_count": 1
},
"state_name": null,
"style_colors": [
"b5cbb5",
"273c49",
"526d68",
"233d40",
"8cae98",
"749981",
"567e68",
"c6d3b6",
"d5dec4",
"574d39",
"776445",
"8a7350",
"69583d",
"65583e",
"385d45",
"47463e",
"5a594b",
"aaa083",
"829a8d",
"b69465",
"514839",
"8d7951",
"e1bd6c",
"dfd4ad",
"2f493f",
"e4d9b9",
"58784c",
"4c7950",
"5e8b57",
"739957",
"95ad69",
"648957",
"e34332",
"e3a62f",
"55c96b",
"f5d582",
"34413b",
"fff2ce",
"3c4e40",
"f5d58a",
"304b41",
"f0e2b9",
"303c40",
"d69a56",
"4ca5c7",
"efbd72",
"79d3e7",
"e5f8fa",
"64d48c",
"e16262",
"8ff0ab",
"ff8a80",
"52bdd0",
"8ce6ee",
"d7ebef",
"29483d",
"f0e2bd",
"8ca4af",
"6f8b67",
"427582",
"a24b45",
"a77d3f",
"6b6258",
"89914d",
"6f9a55",
"67518e",
"ffd080"
],
"style_source": "scenes/main/world_preview.gd",
"style_source_sha256": "add770377c190a5ccdde95379539de9753f7dabc703c1d58452ba590c6eda2f0",
"text": "待ち1人"
},
{
"category": "guest",
"crops": [
{
"image_index": 0,
"path": "row-0004-image-0.png",
"sha256": "e08ebb7ae2eb1f2f66360df04d7c132bed923ece9ba5d406c72f23c16bc71e34"
},
{
"image_index": 1,
"path": "row-0004-image-1.png",
"sha256": "e08ebb7ae2eb1f2f66360df04d7c132bed923ece9ba5d406c72f23c16bc71e34"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": null,
"id": "guest/guest-auto-00000019",
"layer": 0,
"local_rect": [
267.0,
325.0,
48.0,
54.0
],
"mechanical_pass": false,
"operation": null,
"reason": "whole rectangle in viewport",
"row_index": 4,
"screenshot_rect": [
346.0,
422.0,
48.0,
54.0
],
"state": "QUEUING",
"state_name": "QUEUING",
"style_colors": [
"688da0",
"b87659",
"a69a63",
"89789e",
"638d72",
"edc49d",
"c99371",
"a56f50",
"303c40",
"719d94",
"eee5c9",
"608d84",
"eee6c7",
"e3dfc2",
"956940",
"d3ad6b",
"b48850",
"af9059",
"d8bc78",
"f3ebcf",
"efe7ce",
"805e44"
],
"style_source": "scripts/ui/pixel_people.gd",
"style_source_sha256": "a171703083aa2d00f8be2b63e1daef9c41717c8fe59d8056878bc4b7df1e0b0c",
"text": null
},
{
"category": "guest",
"crops": [
{
"image_index": 0,
"path": "row-0005-image-0.png",
"sha256": "8b4be9869042638da9a5afdd90ce982baa16623c2b5e4cbedc50109680867522"
},
{
"image_index": 1,
"path": "row-0005-image-1.png",
"sha256": "8b4be9869042638da9a5afdd90ce982baa16623c2b5e4cbedc50109680867522"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": null,
"id": "guest/guest-auto-00000020",
"layer": 0,
"local_rect": [
139.0,
301.0,
48.0,
54.0
],
"mechanical_pass": false,
"operation": null,
"reason": "whole rectangle in viewport",
"row_index": 5,
"screenshot_rect": [
218.0,
398.0,
48.0,
54.0
],
"state": "RECEIVING_SERVICE",
"state_name": "RECEIVING_SERVICE",
"style_colors": [
"688da0",
"b87659",
"a69a63",
"89789e",
"638d72",
"edc49d",
"c99371",
"a56f50",
"303c40",
"719d94",
"eee5c9",
"608d84",
"eee6c7",
"e3dfc2",
"956940",
"d3ad6b",
"b48850",
"af9059",
"d8bc78",
"f3ebcf",
"efe7ce",
"805e44"
],
"style_source": "scripts/ui/pixel_people.gd",
"style_source_sha256": "a171703083aa2d00f8be2b63e1daef9c41717c8fe59d8056878bc4b7df1e0b0c",
"text": null
},
{
"category": "business",
"crops": [
{
"image_index": 0,
"path": "row-0006-image-0.png",
"sha256": "62525c0b9f2c189545666c134eb6f83b660d6efbc0698278ce049ace7e708d0a"
},
{
"image_index": 1,
"path": "row-0006-image-1.png",
"sha256": "62525c0b9f2c189545666c134eb6f83b660d6efbc0698278ce049ace7e708d0a"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": 73.0,
"id": "0",
"layer": 0,
"local_rect": [
133.0,
390.0,
79.0,
14.0
],
"mechanical_pass": false,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 6,
"screenshot_rect": [
212.0,
487.0,
79.0,
14.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"b5cbb5",
"273c49",
"526d68",
"233d40",
"8cae98",
"749981",
"567e68",
"c6d3b6",
"d5dec4",
"574d39",
"776445",
"8a7350",
"69583d",
"65583e",
"385d45",
"47463e",
"5a594b",
"aaa083",
"829a8d",
"b69465",
"514839",
"8d7951",
"e1bd6c",
"dfd4ad",
"2f493f",
"e4d9b9",
"58784c",
"4c7950",
"5e8b57",
"739957",
"95ad69",
"648957",
"e34332",
"e3a62f",
"55c96b",
"f5d582",
"34413b",
"fff2ce",
"3c4e40",
"f5d58a",
"304b41",
"f0e2b9",
"303c40",
"d69a56",
"4ca5c7",
"efbd72",
"79d3e7",
"e5f8fa",
"64d48c",
"e16262",
"8ff0ab",
"ff8a80",
"52bdd0",
"8ce6ee",
"d7ebef",
"29483d",
"f0e2bd",
"8ca4af",
"6f8b67",
"427582",
"a24b45",
"a77d3f",
"6b6258",
"89914d",
"6f9a55",
"67518e",
"ffd080"
],
"style_source": "scenes/main/world_preview.gd",
"style_source_sha256": "add770377c190a5ccdde95379539de9753f7dabc703c1d58452ba590c6eda2f0",
"text": "職員不足 未稼働"
},
{
"category": "business",
"crops": [
{
"image_index": 0,
"path": "row-0007-image-0.png",
"sha256": "379c49ce02077552289ae503c74a4a0032150ff6cec3ea20c42045f6a059ec3d"
},
{
"image_index": 1,
"path": "row-0007-image-1.png",
"sha256": "379c49ce02077552289ae503c74a4a0032150ff6cec3ea20c42045f6a059ec3d"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": 73.0,
"id": "1",
"layer": 0,
"local_rect": [
1029.0,
390.0,
79.0,
14.0
],
"mechanical_pass": false,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 7,
"screenshot_rect": [
1108.0,
487.0,
79.0,
14.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"b5cbb5",
"273c49",
"526d68",
"233d40",
"8cae98",
"749981",
"567e68",
"c6d3b6",
"d5dec4",
"574d39",
"776445",
"8a7350",
"69583d",
"65583e",
"385d45",
"47463e",
"5a594b",
"aaa083",
"829a8d",
"b69465",
"514839",
"8d7951",
"e1bd6c",
"dfd4ad",
"2f493f",
"e4d9b9",
"58784c",
"4c7950",
"5e8b57",
"739957",
"95ad69",
"648957",
"e34332",
"e3a62f",
"55c96b",
"f5d582",
"34413b",
"fff2ce",
"3c4e40",
"f5d58a",
"304b41",
"f0e2b9",
"303c40",
"d69a56",
"4ca5c7",
"efbd72",
"79d3e7",
"e5f8fa",
"64d48c",
"e16262",
"8ff0ab",
"ff8a80",
"52bdd0",
"8ce6ee",
"d7ebef",
"29483d",
"f0e2bd",
"8ca4af",
"6f8b67",
"427582",
"a24b45",
"a77d3f",
"6b6258",
"89914d",
"6f9a55",
"67518e",
"ffd080"
],
"style_source": "scenes/main/world_preview.gd",
"style_source_sha256": "add770377c190a5ccdde95379539de9753f7dabc703c1d58452ba590c6eda2f0",
"text": "職員不足 未稼働"
},
{
"category": "business",
"crops": [
{
"image_index": 0,
"path": "row-0008-image-0.png",
"sha256": "79ea1c2d0ff2d4e8f6c7ff20f019a4e264c480438e7528b8de85709b8d8eeda5"
},
{
"image_index": 1,
"path": "row-0008-image-1.png",
"sha256": "79ea1c2d0ff2d4e8f6c7ff20f019a4e264c480438e7528b8de85709b8d8eeda5"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [
"offline rectangle coverage"
],
"font_width": 69.0,
"id": "2",
"layer": 0,
"local_rect": [
133.0,
294.0,
75.0,
14.0
],
"mechanical_pass": false,
"operation": "OPERATING",
"reason": "whole rectangle in viewport",
"row_index": 8,
"screenshot_rect": [
212.0,
391.0,
75.0,
14.0
],
"state": "OPEN",
"state_name": "OPEN",
"style_colors": [
"b5cbb5",
"273c49",
"526d68",
"233d40",
"8cae98",
"749981",
"567e68",
"c6d3b6",
"d5dec4",
"574d39",
"776445",
"8a7350",
"69583d",
"65583e",
"385d45",
"47463e",
"5a594b",
"aaa083",
"829a8d",
"b69465",
"514839",
"8d7951",
"e1bd6c",
"dfd4ad",
"2f493f",
"e4d9b9",
"58784c",
"4c7950",
"5e8b57",
"739957",
"95ad69",
"648957",
"e34332",
"e3a62f",
"55c96b",
"f5d582",
"34413b",
"fff2ce",
"3c4e40",
"f5d58a",
"304b41",
"f0e2b9",
"303c40",
"d69a56",
"4ca5c7",
"efbd72",
"79d3e7",
"e5f8fa",
"64d48c",
"e16262",
"8ff0ab",
"ff8a80",
"52bdd0",
"8ce6ee",
"d7ebef",
"29483d",
"f0e2bd",
"8ca4af",
"6f8b67",
"427582",
"a24b45",
"a77d3f",
"6b6258",
"89914d",
"6f9a55",
"67518e",
"ffd080"
],
"style_source": "scenes/main/world_preview.gd",
"style_source_sha256": "add770377c190a5ccdde95379539de9753f7dabc703c1d58452ba590c6eda2f0",
"text": "営業中 利用1人"
}
]
}
@@ -0,0 +1 @@
646334f4ef3c78a8cfe5d68d3738c4d772cdd71cbf80b2dba7c153693ead4419
@@ -0,0 +1,443 @@
{
"errors": [],
"expected_row_count": 4,
"images": [
{
"conversion_sha256": "363c2bdb1da77458226d01e971b9aea1220fcc81f70fb5c3c816a12b7039fac3",
"raw_sha256": "2abb4675f9978e7b86599c4334732ebadcb7cd5c5c51deeacaa7d0dee858e9b9",
"receipt_sha256": "b393e4e0afcdbc72f8cbb3c88cbe6cab7fa2548855ee648c69c5ac61f7fffb17",
"request_sha256": "1a9ace68b3cf0ca997ea3e28ba1da1c30aa0cff72bf5d433d008ce578c7de093",
"whole_image_sha256": "abf7d2e89413431438f35cc3eecb5534a7257cb033906c5397592678b2e9bce4"
},
{
"conversion_sha256": "d9f5e79103258a50bb5d0866725a8488017a9cc8ef150312d16de9043618b55c",
"raw_sha256": "e5a7ac0c3ba3f001baae7c771304d1a9a9cf024240176e8f8735a9dcf1091b31",
"receipt_sha256": "b393e4e0afcdbc72f8cbb3c88cbe6cab7fa2548855ee648c69c5ac61f7fffb17",
"request_sha256": "1a9ace68b3cf0ca997ea3e28ba1da1c30aa0cff72bf5d433d008ce578c7de093",
"whole_image_sha256": "b82e6ef061d9b4e8cbc3321405553f3f447c8abfb61ce38532254130a704ee95"
}
],
"mechanical_pass": true,
"rows": [
{
"category": "room",
"crops": [
{
"image_index": 0,
"path": "row-0000-image-0.png",
"sha256": "94beea581b9586cda35d6c16a0bb8dccc7cad6c6cd0a9842ead3b34d19066f43"
},
{
"image_index": 1,
"path": "row-0000-image-1.png",
"sha256": "94beea581b9586cda35d6c16a0bb8dccc7cad6c6cd0a9842ead3b34d19066f43"
}
],
"dimmed": true,
"eligibility": "visible",
"failures": [],
"font_width": null,
"id": "facility_00000004",
"layer": 1,
"local_rect": [
131.0,
388.0,
256.0,
192.0
],
"mechanical_pass": true,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 0,
"screenshot_rect": [
210.0,
485.0,
256.0,
192.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"34413b",
"8e6348",
"c39665",
"eee5c9",
"608d84",
"c6b999",
"a6b6a0",
"d4c7a5",
"acb9a5",
"897487",
"f1e9cb",
"b39b77",
"615563",
"a57852",
"587f7b",
"edc97c",
"d6ad52",
"709b94",
"a37b91",
"72b6b8",
"c1e0d4",
"445652",
"a2b2a3",
"d3aa8f",
"ca9c62",
"ffe9a8",
"6c8993",
"adc5b4",
"52836c",
"c7c6a6",
"a8b6aa",
"d8bf89",
"aec0bf",
"8a7155",
"617f82",
"3e514e",
"404f63",
"83958a",
"283a39",
"586c62",
"526177",
"dcc08b",
"9daa8d",
"879eaa",
"556e62",
"4d6472",
"e2bd6c",
"ba7157",
"bfbb9a",
"a9aa91",
"9ab9b3",
"d5e3d0",
"f2ca81",
"fff0d6",
"a67c53"
],
"style_source": "scripts/ui/pixel_tower_art.gd",
"style_source_sha256": "71df9b8400cb883a5bb1c89729f80c77e734df503fc54154be3e486adb8391df",
"text": null
},
{
"category": "room",
"crops": [
{
"image_index": 0,
"path": "row-0001-image-0.png",
"sha256": "713f0f370de8ff40def7b5fbe9f071b8f5be3db1604b18bce5c6cf7e8eb62c54"
},
{
"image_index": 1,
"path": "row-0001-image-1.png",
"sha256": "713f0f370de8ff40def7b5fbe9f071b8f5be3db1604b18bce5c6cf7e8eb62c54"
}
],
"dimmed": true,
"eligibility": "visible",
"failures": [],
"font_width": null,
"id": "facility_00000005",
"layer": 1,
"local_rect": [
1027.0,
388.0,
256.0,
192.0
],
"mechanical_pass": true,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 1,
"screenshot_rect": [
1106.0,
485.0,
256.0,
192.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"34413b",
"8e6348",
"c39665",
"eee5c9",
"608d84",
"c6b999",
"a6b6a0",
"d4c7a5",
"acb9a5",
"897487",
"f1e9cb",
"b39b77",
"615563",
"a57852",
"587f7b",
"edc97c",
"d6ad52",
"709b94",
"a37b91",
"72b6b8",
"c1e0d4",
"445652",
"a2b2a3",
"d3aa8f",
"ca9c62",
"ffe9a8",
"6c8993",
"adc5b4",
"52836c",
"c7c6a6",
"a8b6aa",
"d8bf89",
"aec0bf",
"8a7155",
"617f82",
"3e514e",
"404f63",
"83958a",
"283a39",
"586c62",
"526177",
"dcc08b",
"9daa8d",
"879eaa",
"556e62",
"4d6472",
"e2bd6c",
"ba7157",
"bfbb9a",
"a9aa91",
"9ab9b3",
"d5e3d0",
"f2ca81",
"fff0d6",
"a67c53"
],
"style_source": "scripts/ui/pixel_tower_art.gd",
"style_source_sha256": "71df9b8400cb883a5bb1c89729f80c77e734df503fc54154be3e486adb8391df",
"text": null
},
{
"category": "business",
"crops": [
{
"image_index": 0,
"path": "row-0002-image-0.png",
"sha256": "a5ba5edbf9706e2b8a3ceb1de3d61e11074fbb731316e9686e2cec5ea98fc2ae"
},
{
"image_index": 1,
"path": "row-0002-image-1.png",
"sha256": "a5ba5edbf9706e2b8a3ceb1de3d61e11074fbb731316e9686e2cec5ea98fc2ae"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [],
"font_width": 73.0,
"id": "0",
"layer": 1,
"local_rect": [
133.0,
390.0,
79.0,
14.0
],
"mechanical_pass": true,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 2,
"screenshot_rect": [
212.0,
487.0,
79.0,
14.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"b5cbb5",
"273c49",
"526d68",
"233d40",
"8cae98",
"749981",
"567e68",
"c6d3b6",
"d5dec4",
"574d39",
"776445",
"8a7350",
"69583d",
"65583e",
"385d45",
"47463e",
"5a594b",
"aaa083",
"829a8d",
"b69465",
"514839",
"8d7951",
"e1bd6c",
"dfd4ad",
"2f493f",
"e4d9b9",
"58784c",
"4c7950",
"5e8b57",
"739957",
"95ad69",
"648957",
"e34332",
"e3a62f",
"55c96b",
"f5d582",
"34413b",
"fff2ce",
"3c4e40",
"f5d58a",
"304b41",
"f0e2b9",
"303c40",
"d69a56",
"4ca5c7",
"efbd72",
"79d3e7",
"e5f8fa",
"64d48c",
"e16262",
"8ff0ab",
"ff8a80",
"52bdd0",
"8ce6ee",
"d7ebef",
"29483d",
"f0e2bd",
"8ca4af",
"6f8b67",
"427582",
"a24b45",
"a77d3f",
"6b6258",
"89914d",
"6f9a55",
"67518e",
"ffd080"
],
"style_source": "scenes/main/world_preview.gd",
"style_source_sha256": "add770377c190a5ccdde95379539de9753f7dabc703c1d58452ba590c6eda2f0",
"text": "職員不足 未稼働"
},
{
"category": "business",
"crops": [
{
"image_index": 0,
"path": "row-0003-image-0.png",
"sha256": "e977d7db178d1d61310fe7e33a9c25873b62b584337d93ea29960d6f8c5bb869"
},
{
"image_index": 1,
"path": "row-0003-image-1.png",
"sha256": "e977d7db178d1d61310fe7e33a9c25873b62b584337d93ea29960d6f8c5bb869"
}
],
"dimmed": false,
"eligibility": "visible",
"failures": [],
"font_width": 73.0,
"id": "1",
"layer": 1,
"local_rect": [
1029.0,
390.0,
79.0,
14.0
],
"mechanical_pass": true,
"operation": "INACTIVE",
"reason": "whole rectangle in viewport",
"row_index": 3,
"screenshot_rect": [
1108.0,
487.0,
79.0,
14.0
],
"state": "NO_STAFF",
"state_name": "NO_STAFF",
"style_colors": [
"b5cbb5",
"273c49",
"526d68",
"233d40",
"8cae98",
"749981",
"567e68",
"c6d3b6",
"d5dec4",
"574d39",
"776445",
"8a7350",
"69583d",
"65583e",
"385d45",
"47463e",
"5a594b",
"aaa083",
"829a8d",
"b69465",
"514839",
"8d7951",
"e1bd6c",
"dfd4ad",
"2f493f",
"e4d9b9",
"58784c",
"4c7950",
"5e8b57",
"739957",
"95ad69",
"648957",
"e34332",
"e3a62f",
"55c96b",
"f5d582",
"34413b",
"fff2ce",
"3c4e40",
"f5d58a",
"304b41",
"f0e2b9",
"303c40",
"d69a56",
"4ca5c7",
"efbd72",
"79d3e7",
"e5f8fa",
"64d48c",
"e16262",
"8ff0ab",
"ff8a80",
"52bdd0",
"8ce6ee",
"d7ebef",
"29483d",
"f0e2bd",
"8ca4af",
"6f8b67",
"427582",
"a24b45",
"a77d3f",
"6b6258",
"89914d",
"6f9a55",
"67518e",
"ffd080"
],
"style_source": "scenes/main/world_preview.gd",
"style_source_sha256": "add770377c190a5ccdde95379539de9753f7dabc703c1d58452ba590c6eda2f0",
"text": "職員不足 未稼働"
}
]
}
@@ -0,0 +1 @@
f013e0d1d6231833b656859cc73ad0b58b389047e421ad2c43cc598820e21067
+6
View File
@@ -169,6 +169,12 @@ func draw_rectangles(report: Dictionary, view: WorldPreview) -> Dictionary:
for index: int in range(report.get(&"badges", []).size()):
var badge: Dictionary = report[&"badges"][index]
rows.append(badge_rectangle("goods" if badge[&"kind"] == &"supply" else "business", str(index), badge[&"at"], badge[&"text"]))
for entry: Dictionary in report.get(&"traffic", []):
var traffic: Dictionary = entry[&"traffic"]
if traffic[&"current_density"] > 0 or traffic[&"temporary_block"]:
var cell: Vector3i = entry[&"cell"]
rows.append({"category": "traffic", "id": str(cell.x) + "," + str(cell.y) + "," + str(cell.z),
"rect": entry[&"rect"], "style_source": "scenes/main/world_preview.gd"})
var transform := view.get_global_transform_with_canvas()
for row: Dictionary in rows:
row["viewport_rect"] = transform * (row["rect"] as Rect2)
+343 -45
View File
@@ -155,9 +155,9 @@ def image_kind(data):
raise ValueError('unknown screenshot byte magic')
def inspect_conversion(rec, cua_receipt, converter_sha256=None):
def inspect_conversion(rec, cua_receipt, converter_sha256=None, resolve_path=Path):
"""CUA receipt and converter pin are independent of conversion assertions."""
raw, png = Path(rec['raw_path']), Path(rec['png_path'])
raw, png = resolve_path(rec['raw_path']), resolve_path(rec['png_path'])
require(raw.resolve() != png.resolve() and not raw.is_symlink() and not png.is_symlink(),
'conversion must preserve a separate raw file')
require(rec['raw_sha256'] == sha(raw) == cua_receipt['raw_sha256']
@@ -169,7 +169,7 @@ def inspect_conversion(rec, cua_receipt, converter_sha256=None):
require(rec['converter'] == '/usr/bin/sips', 'conversion executable')
pin = converter_sha256 if converter_sha256 is not None else sha('/usr/bin/sips')
require(rec['converter_sha256'] == pin, 'converter SHA mismatch')
require(rec['argv'] == ['/usr/bin/sips', '-s', 'format', 'png', str(raw), '--out', str(png)],
require(rec['argv'] == ['/usr/bin/sips', '-s', 'format', 'png', rec['raw_path'], '--out', rec['png_path']],
'converter argv mismatch')
require(type(rec['exit_code']) is int and rec['exit_code'] == 0, 'converter exit code')
for key in ('stdout_sha256', 'stderr_sha256'):
@@ -206,27 +206,92 @@ def affine_rect(matrix, rect):
return lo + [max(p[i] for p in points)-lo[i] for i in (0, 1)]
def compare_rectangles(report, audit, local_to_viewport, viewport_to_screen, local_to_screen, image, root=ROOT):
"""All draw rows, full affine composition, and exact existing style colors.
Font advance is recorded by the pinned engine/font, not approximated using
a new text width threshold. No image similarity threshold is introduced.
def screenshot_transform(calibration, request, image_size, clicks):
"""Content -> window screenshot pixels, cross-checked against actual clicks.
This is independent of the renderer's content-space screen_rect. In
particular, window_position is a desktop coordinate, not an image origin.
No titlebar height, scale, or image extent is assumed by the checker.
"""
for point in ([0, 0], [1, 0], [0, 1]):
close(affine_point(viewport_to_screen, affine_point(local_to_viewport, point)),
affine_point(local_to_screen, point), 'viewport/screen composition')
rows = audit['rows']
scale = calibration['backing_scale']
stretch = calibration['canvas_stretch']
titlebar = calibration['titlebar_pixels']
require(all(type(v) in (int, float) and math.isfinite(v) for v in
(scale, stretch, titlebar)) and scale > 0 and stretch > 0 and titlebar >= 0,
'invalid screenshot calibration')
close(calibration['window_size'], request['window_size'], 'calibrated window size')
close(calibration['window_position'], request['window_position'], 'calibrated window position')
close(calibration['screenshot_pixels'], list(image_size), 'calibrated image size')
close([request['screen_scale'], request['content_scale_factor']], [scale, stretch],
'calibrated engine scale')
close([request['window_size'][0]*scale, request['window_size'][1]*scale+titlebar],
list(image_size), 'calibrated content extent')
matrix = [scale, 0, 0, scale, 0, titlebar]
matched = 0
for receipt in clicks:
if receipt.get('target_pid') != request['target_pid']:
continue
for event in receipt['events']:
if event.get('action') != 'click':
continue
close(event['calibration'], calibration, 'click calibration')
point = event['point']
close(affine_point(point['viewport_to_screen'], point['viewport']),
point['screen'], 'click content affine')
close(affine_point(matrix, point['screen']), event['coordinates'],
'click screenshot offset')
matched += 1
require(matched > 0, 'missing independent click calibration')
return matrix
def rect_intersection(a, b):
require(len(a) == len(b) == 4 and all(type(v) in (int, float) and math.isfinite(v)
for v in a+b) and min(a[2:]+b[2:]) >= 0, 'invalid clipping rectangle')
x, y = max(a[0], b[0]), max(a[1], b[1])
return [x, y, max(0, min(a[0]+a[2], b[0]+b[2])-x),
max(0, min(a[1]+a[3], b[1]+b[3])-y)]
def rectangle_eligibility(rect, viewport):
clipped = rect_intersection(list(rect), [0, 0, *viewport])
if not clipped[2] or not clipped[3]:
return 'outside', clipped
return ('visible' if clipped == list(rect) else 'clipped'), clipped
def paired_crop_noise(before, after, rect):
"""Measured temporal differences, never an invented JPEG RGB tolerance.
A stable pair alone does not establish class or state identity. The result
is diagnostic data only and must not be promoted to pixels_<category>.
"""
require(before[:2] == after[:2], 'paired image dimensions')
x, y, w, h = rect_intersection(list(rect), [0, 0, *before[:2]])
errors = [max(abs(a-b) for a, b in zip(before[2][py][px][:3], after[2][py][px][:3]))
for py in range(math.ceil(y), math.floor(y+h))
for px in range(math.ceil(x), math.floor(x+w))]
require(errors, 'empty noise crop')
return dict(samples=len(errors), max_channel_delta=max(errors),
mean_channel_delta=sum(errors)/len(errors), unchanged=sum(v == 0 for v in errors))
def drawing_rectangle_rows(report):
"""Derive identities and local positions; do not read audit verdicts."""
expected = []
tower, people, world = 'scripts/ui/pixel_tower_art.gd', 'scripts/ui/pixel_people.gd', 'scenes/main/world_preview.gd'
tower = 'scripts/ui/pixel_tower_art.gd'
people = 'scripts/ui/pixel_people.gd'
world = 'scenes/main/world_preview.gd'
def actor(a, category=None):
expected.append((category or ('staff' if a['staff'] else 'guest'), a['key'],
a['at'] + [16*a['scale'], 18*a['scale']], people, None))
def badge(category, identifier, at, text):
expected.append((category, identifier, at + [None, 14], world, text))
def badge(category, identifier, b):
expected.append((category, identifier, b['at'] + [None, 14], world, b['text']))
for fid, room in report['rooms'].items():
if 'rect' in room:
expected.append(('room', fid, room['rect'], tower, None))
for i, b in enumerate(report['landing_badges']):
badge('queue', str(i), b['at'], b['text'])
badge('queue', str(i), b)
for cabin in report['cabins']:
expected.append(('EV', cabin['shaft_id'], cabin['rect'], tower, None))
for a in cabin['passengers']:
@@ -236,36 +301,254 @@ def compare_rectangles(report, audit, local_to_viewport, viewport_to_screen, loc
if a['staff'] and a['key'].removeprefix('staff/') in report['resting_staff']:
actor(a, 'rest')
for i, b in enumerate(report['badges']):
badge('goods' if b['kind'] == 'supply' else 'business', str(i), b['at'], b['text'])
require(len(rows) == len(expected), 'rectangle coverage')
width, height, pixels = image
checked = []
for row, (category, identifier, rect, source, text) in zip(rows, expected):
require((row['category'], row['id'], row['style_source']) == (category, identifier, source), 'rectangle row identity')
badge('goods' if b['kind'] == 'supply' else 'business', str(i), b)
for row in report.get('traffic', []):
if row['traffic']['current_density'] > 0 or row['traffic']['temporary_block']:
expected.append(('traffic', ','.join(map(str, row['cell'])), row['rect'], world, None))
return expected
def validate_drawing_rows(report, audit, root=ROOT):
expected = drawing_rectangle_rows(report)
require(len(audit['rows']) == len(expected), 'offline rectangle coverage')
for row, (category, identifier, rect, source, text) in zip(audit['rows'], expected):
require((row['category'], row['id'], row['style_source']) == (category, identifier, source),
'offline rectangle identity')
rect = list(rect)
if text is not None:
require(row['text'] == text and type(row['font_width']) in (int, float) and row['font_width'] > 0, 'badge font/text')
require(row['text'] == text and type(row['font_width']) in (int, float)
and math.isfinite(row['font_width']) and row['font_width'] > 0, 'offline font/text')
rect[2] = row['font_width'] + 6
close(row['rect'], rect, 'local drawing rectangle')
viewport = affine_rect(local_to_viewport, rect)
screen = affine_rect(viewport_to_screen, viewport)
close(row['viewport_rect'], viewport, 'viewport rectangle')
close(row['screen_rect'], screen, 'screen rectangle')
close(row['rect'], rect, 'offline report drawing rectangle')
colors = list(dict.fromkeys(re.findall(r'Color\("([0-9a-fA-F]{6})"', (root/source).read_text())))
require(row['style_colors'] == colors and colors, 'drawing style palette')
x, y, w, h = screen
require(w > 0 and h > 0 and x >= 0 and y >= 0 and x+w <= width and y+h <= height,
'drawing rectangle outside screenshot')
# Badge ink is distinct from its translucent backing. Actor ink/skin
# and facility colors are taken verbatim from their pinned draw source.
palette = {'fff2ce'} if text is not None else set(colors)
palette -= {'303c40', 'c6d3b6', 'd5dec4'} # shadow / sky decorations
rgb = {tuple(bytes.fromhex(c)) for c in palette}
found = any(tuple(pixels[py][px][:3]) in rgb
for py in range(math.ceil(y), math.floor(y+h))
for px in range(math.ceil(x), math.floor(x+w)))
require(found, 'missing drawing pixels: ' + category + '/' + str(identifier))
checked.append(category + '/' + str(identifier))
return checked
require(row['style_colors'] == colors and colors, 'offline source palette provenance')
def encode_png(rows):
"""Lossless review crop from decoded pixels, without an imaging dependency."""
require(rows and rows[0], 'empty crop')
width, channels = len(rows[0]), len(rows[0][0])
require(channels in (3, 4), 'crop channels')
def chunk(kind, body):
return (struct.pack('>I', len(body)) + kind + body
+ struct.pack('>I', zlib.crc32(kind + body)))
raw = b''.join(b'\0' + bytes(v for pixel in row for v in pixel) for row in rows)
return (b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack(
'>IIBBBBB', width, len(rows), 8, 2 if channels == 3 else 6, 0, 0, 0))
+ chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b''))
def frame_rows(report, audit, request, receipt, images, clicks, root=ROOT):
"""One mechanical path for frozen diagnostics and actual OS frames.
Errors are retained as failures, including incomplete frozen audits. Every
supplied row is accounted for; no palette value is an image oracle.
"""
errors, rows = [], []
try:
validate_drawing_rows(report, audit, root)
except (ValueError, KeyError, TypeError) as exc:
errors.append(str(exc))
calibration = screenshot_transform(receipt['calibration'], request, images[0][:2], clicks)
for image in images:
require(image[:2] == images[0][:2], 'frame image dimensions')
for point in ([0, 0], [1, 0], [0, 1]):
close(affine_point(request['viewport_to_screen'], affine_point(request['local_to_viewport'], point)),
affine_point(request['local_to_screen'], point), 'frame affine composition')
for index, row in enumerate(audit['rows']):
failures = list(errors)
close(row['viewport_rect'], affine_rect(request['local_to_viewport'], row['rect']),
'frame viewport rectangle')
close(row['screen_rect'], affine_rect(request['local_to_screen'], row['rect']),
'frame content rectangle')
status, clipped = rectangle_eligibility(row['rect'], report['viewport'])
screenshot_rect = affine_rect(calibration, affine_rect(request['local_to_screen'], clipped))
room = report['rooms'].get(row['id']) if row['category'] == 'room' else None
if row['category'] in ('business', 'goods'):
room = report['rooms'].get(report['badges'][int(row['id'])]['facility'])
operation, state = None, None
if room is not None:
states = enum_names(root, 'scripts/facility/facility_instance.gd', 'State')
operations = enum_names(root, 'scripts/facility/facility_instance.gd', 'OperationState')
require(type(room['state']) is int and 0 <= room['state'] < len(states), 'frame room enum')
state = states[room['state']]
require(state == room['state_name'], 'frame room state name')
require(type(room['operation']) is int and 0 <= room['operation'] < len(operations), 'frame operation enum')
operation = operations[room['operation']]
actors = list(report['actor_drawings']) + [a for c in report['cabins'] for a in c['passengers']]
actor = next((a for a in actors if a['key'] == row['id']), None)
if actor is not None:
actor_states = enum_names(root, 'scripts/staff/staff_runtime.gd' if actor['staff']
else 'scripts/guest/guest_runtime.gd', 'State')
require(type(actor['state']) is int and 0 <= actor['state'] < len(actor_states),
'frame actor state enum')
state = actor_states[actor['state']]
if row['category'] == 'queue':
badge = report['landing_badges'][int(row['id'])]
state = {k: badge[k] for k in ('members', 'visible_count', 'overflow')}
if row['category'] == 'EV':
cabin = next(c for c in report['cabins'] if c['shaft_id'] == row['id'])
state = {k: cabin[k] for k in ('operational', 'doors_open')}
if row['category'] == 'traffic':
traffic = next(r['traffic'] for r in report.get('traffic', [])
if ','.join(map(str, r['cell'])) == row['id'])
state = {k: traffic[k] for k in ('current_density', 'temporary_block')}
result = dict(row_index=index, category=row['category'], id=row['id'], layer=report['layer'],
state=state, state_name=state if isinstance(state, str) else None, operation=operation,
dimmed=row['category'] == 'room' and operation != 'OPERATING',
text=row.get('text'), local_rect=row['rect'], screenshot_rect=screenshot_rect,
style_source=row['style_source'], style_source_sha256=sha(root/row['style_source']),
style_colors=row['style_colors'], font_width=row.get('font_width'),
eligibility=status, reason={'visible': 'whole rectangle in viewport',
'clipped': 'rectangle intersects viewport', 'outside': 'no viewport intersection'}[status],
crops=[])
if status != 'outside':
for number, (width, height, pixels) in enumerate(images):
x, y, w, h = rect_intersection(screenshot_rect, [0, 0, width, height])
crop = [line[math.ceil(x):math.floor(x+w)]
for line in pixels[math.ceil(y):math.floor(y+h)]]
if not crop or not crop[0]:
failures.append('eligible crop outside image')
continue
if len({pixel for line in crop for pixel in line}) == 1:
failures.append('blank crop')
data = encode_png(crop)
result['crops'].append(dict(image_index=number, sha256=hashlib.sha256(data).hexdigest(),
png_bytes=data))
result['mechanical_pass'] = not failures
result['failures'] = failures
rows.append(result)
return dict(rows=rows, errors=errors, mechanical_pass=not errors and all(r['mechanical_pass'] for r in rows),
expected_row_count=len(drawing_rectangle_rows(report)))
def write_frame_review(directory, frame, provenance):
directory = Path(directory)
directory.mkdir(parents=True, exist_ok=True)
manifest = copy.deepcopy(frame)
manifest['images'] = provenance
for row in manifest['rows']:
for crop in row['crops']:
name = 'row-%04d-image-%d.png' % (row['row_index'], crop['image_index'])
(directory/name).write_bytes(crop.pop('png_bytes'))
crop['path'] = name
data = (json.dumps(manifest, sort_keys=True, ensure_ascii=False, indent=2) + '\n').encode()
(directory/'manifest.json').write_bytes(data)
digest = hashlib.sha256(data).hexdigest()
(directory/'manifest.sha256').write_text(digest + '\n')
return manifest, digest
def reviewed_pixel_categories(manifest, digest, verdict_path):
"""The worker never writes verdicts; absent independent review earns nothing."""
if not Path(verdict_path).is_file():
return set()
verdict = json.loads(Path(verdict_path).read_bytes())
require(verdict['manifest_sha256'] == digest, 'review manifest SHA mismatch')
require(verdict['reviewer'] == 'independent-opus-direct-image', 'independent image reviewer required')
eligible = [r for r in manifest['rows'] if r['eligibility'] != 'outside']
require([v['row_index'] for v in verdict['rows']] == [r['row_index'] for r in eligible],
'review row coverage')
for row, decision in zip(eligible, verdict['rows']):
require(all(decision[k] is True for k in ('category', 'state', 'position')), 'review row rejected')
if not manifest['mechanical_pass']:
return set()
return {r['category'] for r in eligible if r['mechanical_pass']}
def mirrored_inventory(directory):
"""Read every indexed byte; preserve original receipt paths as authority."""
directory = Path(directory).resolve()
index = json.loads((directory/'MIRROR_INDEX.json').read_bytes())
require(Path(index['mirror']).resolve() == directory, 'mirror root mismatch')
paths, mapping = set(), []
source = Path(index['source'])
for row in index['files']:
name = Path(row['path'])
require(not name.is_absolute() and '..' not in name.parts and str(name) not in paths,
'invalid/duplicate mirror path')
paths.add(str(name))
local = directory/name
require(local.resolve().is_relative_to(directory) and not local.is_symlink(), 'mirror escape')
data = local.read_bytes()
digest = hashlib.sha256(data).hexdigest()
require(digest == row['sha256'] and len(data) == row['bytes'], 'mirror hash/size mismatch')
mapping.append(dict(original=str(source/name), mirror=str(local), sha256=digest))
prior = index['previous_inventory']
prior_name = Path(prior['path'])
require(not prior_name.is_absolute() and '..' not in prior_name.parts
and (directory/prior_name).resolve().is_relative_to(directory)
and not (directory/prior_name).is_symlink(), 'previous inventory escape')
require(sha(directory/prior['path']) == prior['sha256'], 'previous mirror inventory hash')
old = json.loads((directory/prior['path']).read_bytes())
require(all(row in index['files'] for row in old['files']), 'previous mirror entries changed')
return index, mapping
def inspect_offline_mirror(directory):
"""Preparation diagnostics, deliberately separate from the acceptance gate.
Frozen incomplete draw audits cannot acquire missing rows by relabelling
source-derived rectangles as observed engine output.
"""
directory = Path(directory).resolve()
index, mapping = mirrored_inventory(directory)
by_original = {row['original']: row for row in mapping}
def resolve(original):
require(original in by_original, 'unindexed original path')
return Path(by_original[original]['mirror'])
pair = directory/'os-pair/A'
clicks = [json.loads((pair/name/'cua-receipt.json').read_bytes())
for name in ('writer-request-0001', 'writer-request-0003')]
results, inspected = [], []
for name in ('writer-request-0002', 'writer-request-0004'):
folder = pair/name
request = json.loads((folder/'request.json').read_bytes())
receipt = json.loads((folder/'cua-receipt.json').read_bytes())
require(sha(folder/'request.json') == receipt['request_sha256'], 'offline request binding')
require(receipt['target_pid'] == request['target_pid'], 'offline screenshot PID')
report, audit = decode(request['report']), decode(request['rectangles'])
images = []
for number, suffix in enumerate(('', '-after')):
conversion_path = folder/('root-early-conversion'+suffix+'.json')
conversion = json.loads(conversion_path.read_bytes())
shot = receipt['screenshots'][number]
require(resolve(conversion['raw_path']) == directory/shot['path'], 'offline raw path binding')
image = inspect_conversion(conversion, shot, resolve_path=resolve)
for stream in ('stdout', 'stderr'):
require(sha(Path(str(conversion_path)+'.'+stream)) == conversion[stream+'_sha256'],
'offline converter log hash')
images.append(image)
for original in (str(Path(index['source'])/conversion_path.relative_to(directory)),
conversion['png_path']):
inspected.append(by_original[original])
frame = frame_rows(report, audit, request, receipt, images, clicks, ROOT)
provenance = []
for number, suffix in enumerate(('', '-after')):
conversion_path = folder/('root-early-conversion'+suffix+'.json')
conversion = json.loads(conversion_path.read_bytes())
provenance.append(dict(whole_image_sha256=conversion['png_sha256'],
raw_sha256=conversion['raw_sha256'],
conversion_sha256=sha(conversion_path),
request_sha256=sha(folder/'request.json'),
receipt_sha256=sha(folder/'cua-receipt.json')))
review_dir = ROOT/'docs/plans/original-completion-2026-10-02/execution/p23-03/offline-r07-review'/name
manifest, digest = write_frame_review(review_dir, frame, provenance)
credited = reviewed_pixel_categories(manifest, digest, review_dir/'independent-verdict.json')
present = {r['category'] for r in frame['rows'] if r['eligibility'] != 'outside'}
overlays = [dict(cell=r['cell'], rect=r['rect']) for r in report['traffic']
if (r['traffic']['current_density'] > 0 or r['traffic']['temporary_block'])
and not any(a['category'] == 'traffic' and a['id'] == ','.join(map(str, r['cell']))
for a in audit['rows'])]
results.append(dict(request=name, layer=report['layer'], manifest_sha256=digest,
manifest_path=str(review_dir/'manifest.json'), rows=manifest['rows'],
mechanical_pass=frame['mechanical_pass'], errors=frame['errors'],
unaudited_traffic_overlays=overlays,
pending_categories=[c for c in CATEGORIES if c not in present],
pixel_categories=sorted(credited), independent_review='pending'))
return dict(task_id='P23-03', acceptance='pending', status='blocked',
inventory_count=len(mapping), path_map=mapping, inspected_eight=inspected,
frames=results, root_native_os_full_regression_independent_review='pending')
def enum_names(root, relative, name):
@@ -1171,9 +1454,18 @@ def inspect_os_requests(directory, layout, role, pid, root=ROOT):
# Screenshot actions observe a stable report; input before/after
# screenshots are provenance only and never relabelled as this frame.
if request['action'] == 'screenshot':
checked = compare_rectangles(report, decode(request['rectangles']), request['local_to_viewport'],
request['viewport_to_screen'], request['local_to_screen'], image, root)
pixel_categories.update(c.split('/')[0] for c in checked)
clicks = [document(json.loads(path(name).read_bytes())['input_receipt'])
for name in listing['requests']]
frame = frame_rows(report, decode(request['rectangles']), request, cua, [image], clicks, root)
review_dir = request_path.parent/('review-image-' + str(number))
manifest, manifest_sha = write_frame_review(review_dir, frame, [dict(
whole_image_sha256=binding['png_sha256'], raw_sha256=shot['raw_sha256'],
conversion_sha256=sha(path(binding['conversion'])) if conversion else None,
request_sha256=digest, receipt_sha256=sha(path(ack['input_receipt'])))])
require(frame['mechanical_pass'], 'frame mechanical checks: ' + str(frame['errors'])
+ str([r['failures'] for r in frame['rows'] if not r['mechanical_pass']]))
pixel_categories.update(reviewed_pixel_categories(
manifest, manifest_sha, review_dir/'independent-verdict.json'))
records.append(dict(request=request, ack=ack, observed=observed, cua=cua, request_sha256=digest))
require(len(seen) == len(bindings), 'unattributed screenshot bindings')
return records, pixel_categories
@@ -1301,9 +1593,15 @@ def main(argv=None):
parser.add_argument('--verify-only', action='store_true')
parser.add_argument('--project-root', type=Path, default=ROOT)
parser.add_argument('--diagnostics', type=Path)
parser.add_argument('--offline-mirror', type=Path)
parser.add_argument('--layout-a', type=Path)
parser.add_argument('--layout-b', type=Path)
args = parser.parse_args(argv)
if args.offline_mirror:
require(not any((args.verify_only, args.diagnostics, args.layout_a, args.layout_b)),
'offline preparation must not be combined with acceptance checks')
print(json.dumps(inspect_offline_mirror(args.offline_mirror), ensure_ascii=False))
return 2
if args.verify_only:
build = os.environ.get('BUILD_OUTPUT_DIR')
if not build:
+210 -21
View File
@@ -288,27 +288,6 @@ class InfrastructureTests(unittest.TestCase):
if change == 'id': q[0]['ack']['sequence'] = 2
with self.assertRaises(ValueError): check.inspect_resume(writer, r, saved, q, members)
def test_rectangle_pixel_and_transform_negatives(self):
source = 'scripts/ui/pixel_people.gd'
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"', (check.ROOT/source).read_text())))
report = dict(rooms={}, landing_badges=[], cabins=[], resting_staff={}, badges=[],
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1)])
row = dict(category='staff', id='staff/s', rect=[1, 1, 16, 18], viewport_rect=[1, 1, 16, 18],
screen_rect=[1, 1, 16, 18], style_source=source, style_colors=colors)
audit = dict(rows=[row])
matrix = [1, 0, 0, 1, 0, 0]
image = (24, 24, [[(113, 157, 148)]*24 for _ in range(24)])
self.assertEqual(check.compare_rectangles(report, audit, matrix, matrix, matrix, image), ['staff/staff/s'])
for change in ('rectangle', 'pixels', 'transform', 'coverage', 'palette'):
with self.subTest(change=change):
a, m, img = copy.deepcopy(audit), matrix[:], image
if change == 'rectangle': a['rows'][0]['rect'][0] += 1
if change == 'pixels': img = (24, 24, [[(0, 0, 0)]*24 for _ in range(24)])
if change == 'transform': m[4] = 10
if change == 'coverage': a['rows'].clear()
if change == 'palette': a['rows'][0]['style_colors'] = ['000000']
with self.assertRaises(ValueError): check.compare_rectangles(report, a, matrix, m, matrix, img)
def test_interpolation_history_tampering(self):
state = dict(systems=dict(runtime=dict(guests=dict(guests=[]), staff=dict(staff=[]),
elevators=dict(shafts=[], trips=[], current_minute=0))))
@@ -568,5 +547,215 @@ class ComparisonTests(unittest.TestCase):
check.compare_layouts(a, b)
class OfflineGeometryTests(unittest.TestCase):
"""Synthetic calibration examples; no OS or acceptance credit."""
def setUp(self):
self.calibration = dict(window_size=[100, 80], window_position=[240, 150],
screenshot_pixels=[200, 190], backing_scale=2,
canvas_stretch=1, titlebar_pixels=30)
self.request = dict(window_size=[100, 80], window_position=[240, 150],
screen_scale=2, content_scale_factor=1, target_pid=123)
self.clicks = [dict(target_pid=123, events=[dict(action='click',
coordinates=[20, 70], calibration=copy.deepcopy(self.calibration),
point=dict(screen=[10, 20], viewport=[10, 20],
viewport_to_screen=[1, 0, 0, 1, 0, 0]))])]
def transform(self):
return check.screenshot_transform(self.calibration, self.request, [200, 190], self.clicks)
def test_uses_measured_scale_origin_not_desktop_position(self):
self.assertEqual(check.affine_rect(self.transform(), [10, 20, 5, 7]), [20, 70, 10, 14])
def test_shifted_titlebar_fails_independent_click(self):
self.calibration['titlebar_pixels'] += 1
self.calibration['screenshot_pixels'][1] += 1
self.clicks[0]['events'][0]['calibration'] = copy.deepcopy(self.calibration)
with self.assertRaises(ValueError):
check.screenshot_transform(self.calibration, self.request, [200, 191], self.clicks)
def test_wrong_click_offset_fails(self):
self.clicks[0]['events'][0]['coordinates'][1] -= 30
with self.assertRaisesRegex(ValueError, 'click screenshot offset'):
self.transform()
def test_wrong_pid_cannot_calibrate(self):
self.clicks[0]['target_pid'] += 1
with self.assertRaisesRegex(ValueError, 'missing independent click'):
self.transform()
def test_nonfinite_and_false_scale_rejected(self):
for scale in (False, float('nan'), 0, -1):
self.calibration['backing_scale'] = scale
with self.assertRaisesRegex(ValueError, 'invalid screenshot calibration'):
self.transform()
def test_all_visibility_cases_are_accounted_for(self):
self.assertEqual(check.rectangle_eligibility([2, 3, 4, 5], [10, 10]),
('visible', [2, 3, 4, 5]))
self.assertEqual(check.rectangle_eligibility([-2, 3, 4, 5], [10, 10]),
('clipped', [0, 3, 2, 5]))
self.assertEqual(check.rectangle_eligibility([10, 3, 4, 5], [10, 10])[0], 'outside')
self.assertEqual(check.rectangle_eligibility([2, 3, 0, 5], [10, 10])[0], 'outside')
def test_invalid_rectangles_rejected(self):
for rect in ([0, 0, -1, 4], [float('nan'), 0, 1, 4]):
with self.assertRaisesRegex(ValueError, 'invalid clipping rectangle'):
check.rectangle_eligibility(rect, [10, 10])
def test_noise_is_measured_and_does_not_assert_class(self):
before = (2, 2, [[(10, 20, 30)]*2 for _ in range(2)])
after = copy.deepcopy(before)
after[2][1][1] = (10, 27, 30)
noise = check.paired_crop_noise(before, after, [0, 0, 2, 2])
self.assertEqual(noise, dict(samples=4, max_channel_delta=7,
mean_channel_delta=1.75, unchanged=3))
self.assertNotIn('pass', noise)
self.assertNotIn('state', noise)
def test_wrong_dimensions_and_empty_crop_rejected(self):
image = (1, 1, [[(0, 0, 0)]])
with self.assertRaisesRegex(ValueError, 'paired image dimensions'):
check.paired_crop_noise(image, (2, 1, []), [0, 0, 1, 1])
with self.assertRaisesRegex(ValueError, 'empty noise crop'):
check.paired_crop_noise(image, image, [1, 0, 1, 1])
def test_report_positions_and_source_provenance_are_recomputed(self):
source = 'scripts/ui/pixel_people.gd'
report = dict(rooms={}, landing_badges=[], cabins=[], resting_staff={}, badges=[],
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1)])
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
(check.ROOT/source).read_text())))
audit = dict(rows=[dict(category='staff', id='staff/s', rect=[1, 1, 16, 18],
style_source=source, style_colors=colors)])
check.validate_drawing_rows(report, audit)
for change in ('shift', 'identity', 'missing', 'palette'):
tampered = copy.deepcopy(audit)
if change == 'shift': tampered['rows'][0]['rect'][0] += 1
if change == 'identity': tampered['rows'][0]['category'] = 'guest'
if change == 'missing': tampered['rows'] = []
if change == 'palette': tampered['rows'][0]['style_colors'] = ['000000']
with self.subTest(change=change), self.assertRaises(ValueError):
check.validate_drawing_rows(report, tampered)
class FrameReviewTests(unittest.TestCase):
def setUp(self):
OfflineGeometryTests.setUp(self)
self.request.update(local_to_viewport=[1, 0, 0, 1, 0, 0],
viewport_to_screen=[1, 0, 0, 1, 0, 0],
local_to_screen=[1, 0, 0, 1, 0, 0])
self.report = dict(layer=0, viewport=[100, 80], rooms={}, cabins=[],
landing_badges=[], badges=[], resting_staff={}, traffic=[],
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1, state=0)])
source = 'scripts/ui/pixel_people.gd'
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
(check.ROOT/source).read_text())))
self.audit = dict(rows=[dict(category='staff', id='staff/s', rect=[1, 1, 16, 18],
viewport_rect=[1, 1, 16, 18], screen_rect=[1, 1, 16, 18],
style_source=source, style_colors=colors)])
self.image = (200, 190, [[(x % 256, y % 256, 0) for x in range(200)] for y in range(190)])
def frame(self):
return check.frame_rows(self.report, self.audit, self.request,
dict(calibration=self.calibration), [self.image], self.clicks)
def test_blank_crop_fails_without_threshold(self):
self.image = (200, 190, [[(0, 0, 0)] * 200 for _ in range(190)])
result = self.frame()
self.assertFalse(result['mechanical_pass'])
self.assertIn('blank crop', result['rows'][0]['failures'])
def test_missing_extra_and_unaudited_traffic_fail(self):
for change in ('missing', 'extra', 'traffic'):
with self.subTest(change=change):
original = copy.deepcopy(self.audit)
if change == 'missing': self.audit['rows'].clear()
if change == 'extra': self.audit['rows'] *= 2
if change == 'traffic':
self.report['traffic'] = [dict(cell=[-6, 2, 0], rect=[1, 1, 5, 5],
traffic=dict(current_density=1, temporary_block=False))]
result = self.frame()
self.assertFalse(result['mechanical_pass'])
self.assertEqual(len(result['rows']), len(self.audit['rows']))
self.assertIn('offline rectangle coverage', result['errors'])
self.audit = original
def test_dense_or_blocked_traffic_row_closes_audit_gap(self):
source = 'scenes/main/world_preview.gd'
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
(check.ROOT/source).read_text())))
for density, blocked in ((1, False), (0, True), (0, False)):
with self.subTest(density=density, blocked=blocked):
self.report['traffic'] = [dict(cell=[-6, 2, 0], rect=[1, 1, 5, 5],
traffic=dict(current_density=density, temporary_block=blocked))]
active = density > 0 or blocked
missing = self.frame()
self.assertEqual(missing['mechanical_pass'], not active)
if active:
self.assertIn('offline rectangle coverage', missing['errors'])
self.audit['rows'].append(dict(category='traffic', id='-6,2,0',
rect=[1, 1, 5, 5], viewport_rect=[1, 1, 5, 5],
screen_rect=[1, 1, 5, 5], style_source=source, style_colors=colors))
complete = self.frame()
self.assertTrue(complete['mechanical_pass'], complete['errors'])
self.audit['rows'].pop()
def test_wrong_state_enum_and_name_fail(self):
source = 'scripts/ui/pixel_tower_art.gd'
self.report['actor_drawings'] = []
room = dict(rect=[1, 1, 16, 18], state=3, state_name='NO_STAFF', operation=0)
self.report['rooms'] = {'r': room}
self.audit['rows'][0].update(category='room', id='r', style_source=source,
style_colors=list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
(check.ROOT/source).read_text()))))
self.assertEqual(self.frame()['rows'][0]['operation'], 'INACTIVE')
for state, name in ((99, 'NO_STAFF'), (3, 'OPEN'), (True, 'NO_ACCESS')):
room.update(state=state, state_name=name)
with self.assertRaises(ValueError): self.frame()
def test_frame_rejects_affine_and_calibration_shift(self):
self.request['local_to_screen'][4] += 1
with self.assertRaisesRegex(ValueError, 'affine composition'): self.frame()
self.request['local_to_screen'][4] -= 1
self.clicks[0]['events'][0]['coordinates'][1] += 1
with self.assertRaisesRegex(ValueError, 'click screenshot offset'): self.frame()
def test_eligible_crop_fully_outside_image_fails(self):
for key in ('viewport_to_screen', 'local_to_screen'):
self.request[key][4] = 300
self.audit['rows'][0]['screen_rect'][0] += 300
result = self.frame()
self.assertFalse(result['mechanical_pass'])
self.assertIn('eligible crop outside image', result['rows'][0]['failures'])
def test_manifest_crop_and_independent_review_binding(self):
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
manifest, digest = check.write_frame_review(directory, self.frame(),
[dict(whole_image_sha256='a'*64, raw_sha256='b'*64, conversion_sha256='c'*64)])
self.assertEqual(check.sha(directory/'manifest.json'), digest)
crop = manifest['rows'][0]['crops'][0]
self.assertEqual(check.sha(directory/crop['path']), crop['sha256'])
self.assertEqual(check.png_pixels((directory/crop['path']).read_bytes())[:2], (32, 36))
verdict_path = directory/'independent-verdict.json'
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), set())
verdict = dict(manifest_sha256=digest, reviewer='independent-opus-direct-image',
rows=[dict(row_index=0, category=True, state=True, position=True)])
for change in ('wrong_sha', 'missing_row', 'extra_row', 'rejected_state'):
bad = copy.deepcopy(verdict)
if change == 'wrong_sha': bad['manifest_sha256'] = '0'*64
if change == 'missing_row': bad['rows'] = []
if change == 'extra_row': bad['rows'] *= 2
if change == 'rejected_state': bad['rows'][0]['state'] = False
verdict_path.write_text(json.dumps(bad))
with self.subTest(change=change), self.assertRaises(ValueError):
check.reviewed_pixel_categories(manifest, digest, verdict_path)
verdict_path.write_text(json.dumps(verdict))
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), {'staff'})
manifest['mechanical_pass'] = False
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), set())
if __name__ == '__main__':
unittest.main()