Compare commits
@@ -3702,6 +3702,17 @@
|
||||
"scope": "normal placement; native and OS pending"
|
||||
}
|
||||
}
|
||||
,{
|
||||
"test": "tests/ui/test_p23_life_observation.gd",
|
||||
"template": "P23 life observation: <checks> checks, <failures> failures",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "750254af7d4b8b00508e25d1ef744837d3a7d14ddebe12398e8014bf5e8c68e8",
|
||||
"authority": {
|
||||
"path": "docs/spec/acceptance/P23-03.json",
|
||||
"scope": "component and read-only observation only; native unexecuted; whole unit blocked"
|
||||
}
|
||||
}
|
||||
],
|
||||
"unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting",
|
||||
"roster_count_is_not_hardcoded": true,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# P23-03 Phase 0 — API and state map
|
||||
|
||||
Base: `860136f7099f5e3aee99da5798a627a77f2d8eea`. Static inspection only; no engine execution.
|
||||
|
||||
## Exact source references
|
||||
|
||||
Line numbers below refer to the base; SHA256 covers the complete file.
|
||||
|
||||
| Source | Lines / API | SHA256 |
|
||||
| --- | --- | --- |
|
||||
| autoload/simulation_runtime.gd | 203 initialize(clock,state,seed); 225 seed assignment; 466 submit_command; 508 get_rng_stream; 511 create_snapshot; 1483 enable_production_operations; 1780 default demand configuration; 2831 arrival boundary; 2949 apply arrivals; 4137 queue_manual_checkpoint; 4141 save_to_path; 4169 load_from_path; 5066 seed streams; 6103 spawn_guest executor; 7629 build_facility executor; 10859 staff management report; 12464 supply management report | 4696df4a77feb465a5a3ba8b806569f7bcbe1d03c88289acfb78a3928f307d67 |
|
||||
| autoload/game_state.gd | 20 reset | cf0e884910220e54c56b5875983e53de439af9d893be1e1b65c2121d2a30722f |
|
||||
| scripts/core/simulation_kernel.gd | 91 reset; 104 advance_minutes; 122 advance_one_minute; 175 set_paused | 341c0c878c4894adbac628d0db6362ff69fc3347bdd9e61e50936e15dc2d1edb |
|
||||
| scripts/facility/facility_service.gd | 109 get_instances; 656 create_snapshot | ceb4a3e9224587423f273cdb2b56e6843ab176ac1fc4fe79dd498b5ed5c33b77 |
|
||||
| scripts/guest/guest_service.gd | 170 get_guests; 982 create_snapshot | 56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e |
|
||||
| scripts/staff/staff_service.gd | 166 get_staff_members; 682 get_tasks; 1064 create_snapshot | 355ad5c3c9f85dc4d4ffc3bc4293e36f81078c403d1662512a735b2c8d815fb3 |
|
||||
| scripts/transport/elevator_service.gd | 345 get_report; 362 create_snapshot | cea09dd9204551c26b31b30cd3aa847cc77e3b75fdac31662b9694a95fb3580c |
|
||||
| scripts/world/path_service.gd | 545 get_cell_traffic | 447135729a1106639a6eee6754af3195c8a8b458ca26980ae91fa39d80dcfbd4 |
|
||||
| scripts/supply/supply_logistics_bridge.gd | 122 get_report | b9e394af788595d2744b71f1907c8e7c3d762d38ff49992b1ee74e63b9639e80 |
|
||||
| scripts/facility/facility_instance.gd | 8 OperationState; 10 State; 30 state and subsequent live fields | 0c6aa493f3299611e05dd26aa429dda8cf7b5c479e4d09632036fb862f79a324 |
|
||||
| tests/transport/test_runtime_transport_comparison.gd | 91 existing fixed-demand setup; 109 real paid build command | ba577397fa8db5a6cf92cfd398dd7a5ece49e6b59856f14772b67b51ddc0bde9 |
|
||||
|
||||
## Deterministic demand capability
|
||||
|
||||
An existing deterministic input path is available without Runtime edits: fresh clock/state, `initialize(clock,state,seed)`, then the same scheduled `submit_command(&"spawn_guest", payload, ..., execute_at, idempotency_key)` sequence. The executor validates registered definitions and real walkable cells, creates wallets and GuestRuntime actors, and verifies Path actor projection and movement credit. Explicit IDs must avoid the reserved `guest-auto-` namespace. Inputs and processed results must both be recorded; a seed/hash alone does not prove identical arrivals.
|
||||
|
||||
The existing transport comparison exercises public spawning, real paid construction and Runtime-owned movement. It is fixed-demand experimentation, not proof of natural autonomous demand or all six observation categories. Its numeric assertions are not adopted as new acceptance thresholds. Automatic production arrivals are a separate path (`enable_production_operations`): population, weather and terminal-state gates mean the same seed alone cannot guarantee identical accepted arrivals. Layouts must use identical production settings and compare actual arrival inputs/results.
|
||||
|
||||
`build_facility` resolves the registered definition, runs `_build_facility_precheck`, then calls `construction_service.start_construction` with `definition.build_minutes`. Construction cost, time and capacities must come from definitions. B may add only the predeclared existing corridor/EV through this command.
|
||||
|
||||
## Clock and observation
|
||||
|
||||
`reset` defines initial time. Paused manual `advance_minutes` is explicitly supported; abort/pause boundaries must be checked. Commands run before minute increment, then time boundaries, mana, construction, logistics, facility completion, vitals, incidents, queues, AI and publication.
|
||||
|
||||
Facility/guest/staff getters expose typed objects; presentation must copy values without mutating them. Elevator reports contain shafts/trips. Path traffic provides actual density/capacity/block fields. Logistics reports contain requests/deliveries, source/destination IDs, carrier, requirements, weight, state/reason and task ID. They do not contain warehouse stock: requested quantities must never be labelled stock.
|
||||
|
||||
## Formal save/load and evidence
|
||||
|
||||
Use `queue_manual_checkpoint` or `save_to_path`, then `load_from_path` under a distinct real PID. The load API pauses the clock and invokes SaveService package validation before restore. `restore_snapshot` alone is not formal resume. Preserve all package members, source/spec/engine/settings pins, whole typed sparse snapshots and actual CUA/process/cleanup evidence.
|
||||
|
||||
Original references read: BASELINE.txt lines 10,13,16,25,37,43,69,83,106,216; IMPLEMENTATION_PLAN.md line 1800; immutable P23-03 spec; recorded direction 83e836e. All seven conditions remain mandatory, with no partial credit. Root native/OS/resume/full-regression evidence is missing.
|
||||
@@ -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,72 @@
|
||||
# Offline checker repair on 55c29f1
|
||||
|
||||
Source HEAD: 55c29f119234dd6f993c604c4edc6f377fe3951c. Registration base remains
|
||||
860136f7099f5e3aee99da5798a627a77f2d8eea. No git writes or engine execution.
|
||||
|
||||
Read access: MIRROR_INDEX.json and all 2,622 named files were successfully read
|
||||
under `/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/offline-evidence-55c29f1-r06`.
|
||||
No mirror bytes were changed. The initial shell heredoc could not create its
|
||||
temporary file; the read check was executed using Python `-c` instead.
|
||||
|
||||
## First defects and production cause
|
||||
|
||||
Both layouts first failed at frame-030, checker line 328, with KeyError:
|
||||
`shaft_id`. State objects:
|
||||
|
||||
- A: `layout-A/objects/ac3ed64a6e6162918e4d44c45b2f78d09e50d473b607f3469350a06074a7efb3.json`
|
||||
- B: `layout-B/objects/97ea43afe3d1b21602ebe2a602e7b7d796ff05f03983cd4d1b3edc9157e39a08.json`
|
||||
|
||||
The exception came from `shaft['shaft_id']`, not `t['shaft_id']`. Every trip in
|
||||
these failing frames has shaft_id. Saved shaft rows have it only in config.
|
||||
There is no evidence of shaft-less QUEUED trips to support accepting that shape.
|
||||
|
||||
`autoload/simulation_runtime.gd:541` writes elevators.create_snapshot().
|
||||
`scripts/transport/elevator_service.gd:362-367` copies the saved cars without
|
||||
flattening config; get_report at 345-359 merges config into each report row before
|
||||
counting queued trips. request at 169-171 assigns shaft_id on creation, and the
|
||||
restore validator at 412-454 requires the exact trip keys and shaft/connector
|
||||
agreement. The connector spelling is defined at 617-618; trips are ordered at
|
||||
707-710. `scenes/main/world_preview.gd:973-975` consumes get_report and sorts
|
||||
trips by enqueue time/actor. The checker now counts using the merged row identity.
|
||||
|
||||
## Reviewable changes
|
||||
|
||||
- `tools/check_p23_life_observation_evidence.py`: replace
|
||||
`t['shaft_id'] == shaft['shaft_id']` with `t['shaft_id'] == row['shaft_id']`;
|
||||
validate exact trip keys, companion identity, shaft/stops/connector attribution
|
||||
and QUEUED/BOARDED state before recomputing. Whole report equality remains strict.
|
||||
- `tests/fixtures/p23-03/queued_elevator.json`: retain one actual frame-030
|
||||
queued trip and its shaft, without a cabin seat. Distilled unit data only.
|
||||
- `tools/test_p23_life_observation_evidence.py`: queued shape positive and
|
||||
shaft attribution, waiting_count, missing/extra key negatives; update two
|
||||
pre-existing abbreviated synthetic fixtures to the actual shaft/trip shape.
|
||||
- This repair report. The complete implementation diff is available through
|
||||
`git diff -- tools/check_p23_life_observation_evidence.py tools/test_p23_life_observation_evidence.py`;
|
||||
the new fixture and this file are untracked submission files.
|
||||
|
||||
## Local results
|
||||
|
||||
`inspect_stream` on each exact absolute mirror directory:
|
||||
|
||||
```
|
||||
layout-A inspect_stream PASS 17 frames
|
||||
layout-B inspect_stream PASS 17 frames
|
||||
```
|
||||
|
||||
For both, labels are frame-000, frame-030, frame-060, frame-090, frame-120,
|
||||
frame-150, frame-180, frame-210, frame-240, frame-270, frame-300, frame-330,
|
||||
frame-360, frame-390, frame-420, frame-450, frame-480. Each returns fields:
|
||||
guests, staff, tasks, elevators, room_service_fields, queues, traffic, goods,
|
||||
cabin_geometry. Every frame also passes the existing two-motion-history-frame
|
||||
check, unchanged. No later schema/semantic mismatch was observed.
|
||||
|
||||
Python syntax AST checks pass. Local checker tests: 32 tests, OK.
|
||||
The initial test run exposed two abbreviated synthetic fixtures; subsequent
|
||||
queue expectations were corrected for actual lift landing exclusion. Tests
|
||||
were preserved. git diff --check passes; changed files are within allowed_paths.
|
||||
completion_marker_contracts retains its one required P23 strict template entry;
|
||||
no marker changes. Seven conditions, six categories and 30/88 remain unchanged.
|
||||
|
||||
Root native results (319 tests, A/B PASS), matched metric values (48 metrics,
|
||||
7153/2585), production data and services were not modified. Root OS evidence
|
||||
remains pending as expected at worker submission; this repair grants no acceptance.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"action": "request-repair",
|
||||
"task_id": "P23-03",
|
||||
"reason": "Same-task repair for P23-03. Keep submitted 336a8cd and all earlier work, and keep every failure record (R10 B: 484 checks / 11 failures; validate 4958d8f55b6b458694bf158769ce3bff). Root cause, confirmed in the code: in tests/fixtures/p23-03/observation.gd, prepare() at lines 48-77 adds up only the registered construction_cost of the builds, including B's EV, to set a common diagnostic budget. Lines 93-99 then hire 5 candidates (staff_akane/gina/chika/iris/juno) and buy 40 RAW_FOOD without including either cost in that budget. Construction uses up all of B's cash, so all 5 hires fail with VALIDATION_FAILED (available 0) and the purchase fails with INSUFFICIENT_FUNDS. A succeeds. This is an omission in the fixture's budget, not a gap in production values.\n\nRepair, inside allowed paths only (tests/fixtures/p23-03/**, tools/check_p23_life_observation_evidence.py, tools/test_p23_life_observation_evidence.py, docs/plans/.../execution/p23-03/** metadata):\n(1) In prepare(), raise the common A/B diagnostic `needed` by:\n- the actual signing_cost of each of the 5 candidates, read at runtime from the registered candidate definitions (the catalog's signing_cost field, scripts/data/mvp_content_catalog.gd:323);\n- the gold cost of buying 40 RAW_FOOD, taken from the existing quote path (SupplyCatalog unit_price via PurchaseQuoteV2.make / the supply_management_service quote). Use effective_total if the actual quote applies a modifier.\nRead these values from the runtime registry/quote at the time they are needed. Do not hard-code numbers, add a margin, change default resources, or change product/runtime data. Record each cost source (definition/candidate/item id with its value), the totals, and the resulting deltas in the budget record. A and B must get the same deltas. The improvement EV cost stays in both budgets, as it is now.\n(2) Have the fixture check the receipt of each setup command (build, set_facility_conditions, hire, assignment, purchase, kitchen production) and expect() it to succeed, so a failed setup step shows up as a named failure and is not silently skipped.\n(3) Keep everything else the same: the common A/B inputs, the declared demand/arrival cohort, the native comparison's 48-demand, the 7 conditions and 6 categories, staff/goods coverage, normal OS plus distinct-PID load, full177, the independent Opus review, integration/final, 30/88 with no partial credit, the OS child timeout of 3600, worker/native 900 and the null cap.\n(4) Add a cheap local Python check, in the checker or its test, that statically or via the recorded budget confirms every planned spend (construction, signing, purchase) is counted in needed/deltas. Include a negative test for a missing spend category, so this mistake is caught before an expensive OS replay.\n(5) Update the p23-03 execution metadata to describe the finding and the repair honestly.\n\nThe worker runs only Python syntax checks and the checker's local tests, then makes a genuine submit. No Godot, native, OS or full regression runs, and Root evidence is expected to be missing at submission. Afterwards Root runs the exact final proof on the frozen source, reusing only evidence whose applicability matches. No new UI, product/runtime changes, broad hardening, billing or permission changes.",
|
||||
"codex_model": "gpt-6.1-sol",
|
||||
"codex_model_reason": "The repair is small and well understood: one fixture function needs signing and purchase costs added from existing catalog fields and the quote API, plus receipt assertions and a local Python preflight with a negative test. The root cause is already confirmed and nothing here needs new design or deep cross-system reasoning, so the default Sol is enough on quality and cheaper and faster than Astra."
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"action": "run",
|
||||
"task_id": "P23-03",
|
||||
"reason": "The Runner shows P23-03 in repair_ready with 19 repairs, and the owner has dispatched the recorded R10 budget repair decision. run is a valid action and it is the next step, so this is not a repeat of a completed transition. The run keeps the R10 direction exactly as narrow as recorded and starts from the clean submitted base 336a8cd0b30db1add11869ebceab847db693f531. The worker stays inside the task contract's allowed_paths. It may only run local Python checks: syntax, the checker, and the checker's negative tests. It does not run Godot, native, OS, or full regression; Root runs those only after a genuine submission. The worker must say that Root evidence is missing at submission rather than mark anything PASS. Root's capacity recovery runs separately and must finish before proof replay; Root does not edit source. This run does not change the spec, the acceptance denominator, the budget or caps, or approval, and it does not touch main integration. Independent Opus review and Root validation are still required before acceptance.",
|
||||
"codex_model": "gpt-6-astra",
|
||||
"codex_model_reason": "I chose gpt-6-astra over the default because the difficulty justifies it. (1) The frozen spec's worker_instructions name one Codex Astra worker, and the recorded commander direction (run 83e836e2...) set codex_model gpt-6-astra. Changing the model during a narrow repair would break the recorded direction. (2) There have been 19 repair cycles, which shows the remaining defects are subtle consistency problems across the observation report, the strict checker that recomputes fields, the evidence manifests, and the same-demand layout A/B budget and seed setup. These need careful reasoning across files more than speed. (3) The worker only runs local Python, so per-run usage is bounded. A stronger model that gets it right in one pass should cost less overall than more failed cycles on the default model."
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# R10 diagnostic budget repair submission
|
||||
|
||||
Status: submitted for Root verification; no acceptance credit claimed.
|
||||
Contract base: `860136f7099f5e3aee99da5798a627a77f2d8eea`.
|
||||
Starting submitted source: `336a8cd0b30db1add11869ebceab847db693f531`.
|
||||
|
||||
The mirrored OPUS_R10_BUDGET_REPAIR_DIRECTION.json and
|
||||
ROOT_OS_R10_BUDGET_FINDING.json resolve the input gap recorded in
|
||||
R10_CONTEXT_GAP.md. That historical note and all copied evidence are retained.
|
||||
The failed R10 B run remains 484 checks / 11 failures, validate
|
||||
`4958d8f55b6b458694bf158769ce3bff`; it provides no acceptance credit.
|
||||
|
||||
The observation fixture counted construction only. Its common A/B budget now
|
||||
also counts all five planned candidates' registered catalog signing_cost and
|
||||
the 40 RAW_FOOD purchase. The shared assignment list and purchase quantity
|
||||
feed both budgeting and actual setup commands. PurchaseQuoteV2.make reads the
|
||||
current SupplyCatalog unit_price; the service's eligibility branch selects
|
||||
base_total or effective_total. The live supply preview is recorded and checked
|
||||
against that budget immediately before purchase. No margin or production
|
||||
value is added. Both budgets still include B's improvement construction.
|
||||
The budget record retains construction rows and adds construction totals,
|
||||
candidate sources/costs and signing total, item/unit price/quote/purchase total,
|
||||
needed, initial resources and deltas.
|
||||
|
||||
Every setup operation already passed through command(), which asserts its
|
||||
CommandResult.is_success() and records its receipt. Failure names now include
|
||||
the payload, distinguishing individual builds, hires and assignments. This
|
||||
continues to cover conditions, purchases and kitchen production without skips.
|
||||
|
||||
A cheap checker preflight verifies all three spend categories enter needed
|
||||
before deltas and that the setup shares its assignment/quantity plan. Local
|
||||
negative tests remove each spend increment and delta coverage. These are
|
||||
static checks, not runtime evidence or acceptance assertions.
|
||||
|
||||
Worker validation: 51 local Python checker tests passed; Python syntax checks
|
||||
passed. No Godot, native, OS, full regression, Runner operation, git write,
|
||||
production checkout access or additional agent was used.
|
||||
|
||||
Root evidence for this repaired source is missing at submission, as expected.
|
||||
Root must freeze the source and perform the exact native and normal OS A/B
|
||||
proof, distinct-PID load, mandatory full177 regression, independent Opus review,
|
||||
integration/final and ledger. Reuse requires matching applicability. The native
|
||||
48-demand, observation cohort, seven conditions/six categories, staff/goods
|
||||
coverage, 30/88 with no partial credit, OS child timeout 3600, worker/native
|
||||
900 and null cap are unchanged.
|
||||
@@ -0,0 +1,45 @@
|
||||
# R10 repair context gap
|
||||
|
||||
Status: blocked on missing recorded R10 decision, not on missing Root proof.
|
||||
|
||||
The read-only HEAD check returned the requested submitted starting point
|
||||
`336a8cd0b30db1add11869ebceab847db693f531`. The contract base remains
|
||||
`860136f7099f5e3aee99da5798a627a77f2d8eea`. The initial worktree contained an
|
||||
untracked `tools/__pycache__/`; it was preserved.
|
||||
|
||||
The supplied run direction requires keeping the recorded R10 budget repair
|
||||
exactly narrow, but does not include that decision's concrete repair instructions
|
||||
or location. The available `OPUS_CONCRETE_IMPLEMENTATION_DIRECTION.json` is the
|
||||
initial recipe (run `83e836e232c74975aaf51f4fab5a1af9`), not the R10 decision.
|
||||
No R10 decision was found in this checkout's P23 execution materials or fixtures;
|
||||
`.review-material` is absent. A targeted JSON/Markdown search for R10 budget
|
||||
direction in the sibling P23 runtime directory found no match. Access to the
|
||||
normal production checkout's P23 execution directory and sibling validation
|
||||
checkouts returned `Operation not permitted`; no access workaround was attempted.
|
||||
|
||||
Static context inspected before considering source edits:
|
||||
|
||||
- `API_AND_STATE_MAP.md` describes deterministic initialization and public
|
||||
arrival/build commands. Current `simulation_runtime.gd:203` still accepts the
|
||||
explicit seed, and `game_state.gd:20` resets resources to defaults.
|
||||
- `layout_comparison.gd:21` declares the budget as `GameState.reset`; its run
|
||||
resets state before initialization and records the initial snapshot before
|
||||
issuing layout-specific build commands.
|
||||
- `observation.gd:49` onward derives a common diagnostic construction budget
|
||||
from registered definition costs, including the declared B improvement, and
|
||||
issues `change_resources` before building. This is a separate observation path.
|
||||
- `check_p23_life_observation_evidence.py:1212` requires equality of A/B initial
|
||||
snapshots. These observations do not establish which precise repair R10
|
||||
authorized, or justify inventing budget/seed policy.
|
||||
|
||||
Required continuation input: the complete recorded R10 budget repair decision
|
||||
and its referenced failure evidence, mirrored into readable task materials (or
|
||||
provided directly), including any prescribed budget/seed and checker changes.
|
||||
No specification, acceptance, budget, production, fixture, checker, renderer,
|
||||
or policy change was inferred. Only this context-gap note was added.
|
||||
|
||||
No tests were run: there is no source repair to validate. No Godot, native, OS,
|
||||
full regression, Runner operation, git write, integration, or additional agent
|
||||
was used. Root exact-candidate native/OS/resume/full-regression evidence and
|
||||
independent Opus review remain missing and required; no PASS or acceptance
|
||||
credit is claimed. Root capacity recovery remains separate.
|
||||
@@ -0,0 +1,443 @@
|
||||
# P23-03 repair inspection
|
||||
|
||||
## Repair 26 — submitted for Root validation; no acceptance credit
|
||||
|
||||
This section supersedes the status below. Preserved the five premodified files,
|
||||
both R13 fixtures, all 86 existing tests, and every historical section. Historical
|
||||
49 and the twelve false R13 judgments are unchanged. Seven conditions, six
|
||||
categories and 30/88 are unchanged. No Runtime, gameplay, save, economy, mana,
|
||||
asset or UI-entry source was changed.
|
||||
|
||||
Implemented the remaining local work:
|
||||
|
||||
- Both independent packers now rebuild vertical components from fitted feet and
|
||||
pose cores after every scale reduction. Scale ceilings decrease monotonically,
|
||||
so termination has a finite measure and no iteration-limit exemption. Stable
|
||||
components use the existing ordered forward/backward fitting and p=1 retained
|
||||
member aggregation. A positive separates a previously intersecting component
|
||||
at p=2; retaining the stale component would incorrectly reduce it to p=1.
|
||||
- Whole-state fixtures cover queue lanes plus work staff, queue precedence and
|
||||
order, the actual R13 cafeteria/access-cell geometry, carried and treated
|
||||
patients, occupied rest facilities, queued staff, passenger/floor coexistence
|
||||
on FRONT/BACK, and independently calculated no-fit aggregates. Negatives
|
||||
include stale groups, pose/feet, carrier-relative positions, category/region/
|
||||
transit spoofing, missing identities and displaced aggregation badges.
|
||||
- Cabin candidates no longer silently truncate after two seat rows. Additional
|
||||
identities reuse the existing two-row anchor pattern and participate in the
|
||||
same independent fit/no-fit decision. The synthetic saturation configuration
|
||||
retains every boarded identity in member lists; it changes no definition.
|
||||
- OS and offline callers use the shared hash-bound drawing_context loader.
|
||||
Tests cover valid context, absent fields/objects, malformed raw/rest context,
|
||||
report binding and object hash mismatches. Frame tests include valid transit
|
||||
and occupied-rest authority, missing/malformed rest authority, and conservative
|
||||
rejection of overlap without raw context. No flags grant exemptions.
|
||||
- GDScript component fixture now includes four occupants, scale-driven regroup,
|
||||
and actual _fit_life_actors aggregation with nine drawn and ten retained IDs,
|
||||
badge text and position. This fixture has NOT been executed by Godot.
|
||||
|
||||
Source/consumer audit:
|
||||
|
||||
- _build_life_observation calls fitting before publishing; _draw_life_overlay
|
||||
consumes fitted floor/passenger rows and the aggregation badge at/text.
|
||||
_draw_observed_actor consumes at/scale/mode/frame/flip. Logical cell, preferred
|
||||
center and state remain separate. Existing click handling selects world
|
||||
facilities/construction/stairs; there is no actor hit-test to relocate.
|
||||
- passive_observation.draw_rectangles and Python drawing_rectangle_rows consume
|
||||
fitted actor extents and actual aggregation badge coordinates. frame_rows
|
||||
carries all member identities and drawn_keys into review metadata. Whole typed
|
||||
diagnostic captures preserve the same report; compare_actor_geometry rebuilds
|
||||
classification, bounds, fit and aggregation independently. compare_rest invokes
|
||||
complete geometry for occupied resting facilities. compare_motion binds the
|
||||
actual published report/history, not unfitted preferred positions.
|
||||
- inspect_offline_mirror and inspect_os_requests both pass drawing_context's
|
||||
hash-bound state/rest objects to frame_rows. inspect_stream separately runs
|
||||
rest, cabin, motion and complete geometry checks. Missing objects raise and
|
||||
cannot become a successful exemption. Unit tests exercise this shared loader;
|
||||
no real OS/offline evidence was rerun or relabeled.
|
||||
- strict_test_markers.load_contracts rejects duplicate suite rows. The existing
|
||||
single P23 row already declares the exact two source print sites' template
|
||||
`P23 life observation: <checks> checks, <failures> failures`. parse_raw obtains
|
||||
counts only from actual logs; source_sha256_at_schema_audit is historical
|
||||
metadata, not a current-source acceptance bypass. No marker-contract append
|
||||
is required, no suite-policy row was added, and no marker was fabricated.
|
||||
Root's source map must bind the final modified fixture and all changed files.
|
||||
|
||||
Local verification: 87, 91, 93, 95, 97, 98 and 99 tests passed as work progressed;
|
||||
the final rerun passed **99 tests in 0.213s**, exit 0. The original expected R13 row11/29 rejection
|
||||
is retained. Python compile checks for checker, tests and runner passed without
|
||||
bytecode output. git diff --check passed. No tests were deleted or weakened.
|
||||
Read-only discovery encountered inaccessible sibling worktrees and nonexistent
|
||||
BUILD_OUTPUT_DIR shorthand; the actual allocated build directory supplied both
|
||||
R13 direction files. A shell glob lookup had no match; no source was lost.
|
||||
|
||||
Root native/OS evidence, the 177-test full regression and independent Opus
|
||||
all-condition review remain missing, as expected for worker submission.
|
||||
Submission is not acceptance and earns no unit credit. No Godot, native/OS,
|
||||
full regression, Runner, provider, git write or agent was run.
|
||||
|
||||
## Repair 25 continuation — current status: blocked, implementation incomplete
|
||||
|
||||
This section supersedes the status of the historical sections below. There is
|
||||
no specification, permission, or floor-interpretation gap. The remaining blocker
|
||||
is unfinished implementation and verification, not missing Root evidence alone.
|
||||
All 809 pre-existing added lines were preserved and extended. No candidate is
|
||||
claimed implemented or ready for acceptance. Seven conditions, six categories,
|
||||
and 30/88 remain exact; historical 49 is preserved without relabeling.
|
||||
|
||||
Read before editing: AGENTS.md, Phase 0 API_AND_STATE_MAP, recorded concrete
|
||||
direction, both authoritative R13 geometry/region directions, renderer,
|
||||
checker/tests, diagnostic/passive collectors, Runtime initialize/spawn/save/load
|
||||
entry points, enums, marker parser and existing contract. No new assets or
|
||||
Runtime/gameplay/save/UI-entry changes.
|
||||
|
||||
Changes in this continuation:
|
||||
|
||||
- Drawing-row validation accepts the existing raw-state/rest-audit context and
|
||||
invokes complete geometry classification and fitting. OS frames pass the
|
||||
hash-bound stream objects; offline image review loads the matching writer
|
||||
capture and checks its report binding before passing the same objects.
|
||||
- Track validation checks the raw actor endpoint, source-valid adjacent origin,
|
||||
finite nonfuture times and boolean pause. A drawing's region/in_transit flags
|
||||
cannot grant an exemption. Without raw context the old conservative check
|
||||
remains, and frame output explicitly says `absent: fail closed, no transit
|
||||
exemptions`. Historical images have not been rerun or relabeled.
|
||||
- Rest selection still independently validates routes, costs, locks and
|
||||
eligibility. For actual occupied rest facilities its geometry now runs through
|
||||
complete cross-kind fitting, including queue precedence and independently
|
||||
reconstructed aggregation identities/no-fit, instead of requiring every
|
||||
rest-slot identity to have an unshifted drawing row.
|
||||
- Added the actual +m badge to Python and GDScript image-audit rectangle lists;
|
||||
frame metadata retains all aggregate members and drawn keys. This additional
|
||||
diagnostic row type does not change the six acceptance categories.
|
||||
- Added full state-to-geometry positives for actual track interpolation,
|
||||
staff/guest mixed packing on FRONT/BACK, fractional low-world-pixel scale and
|
||||
saturated aggregation with all identities retained. Added track/time/pause,
|
||||
forged-region/out-of-cell, aggregate identity loss and badge omission checks.
|
||||
R13 row11/29 remains an expected FAIL. Existing tests were not removed.
|
||||
|
||||
Precise outstanding TODOs before an implemented submission:
|
||||
|
||||
1. Complete full state-to-geometry positives for queue lane + work staff,
|
||||
passenger + floor, carried/resting/treated staff and cabin aggregation.
|
||||
Exercise compare_rest with an actual occupied facility, legitimate no-fit
|
||||
aggregate and queued staff; its new integration lacks those full fixtures.
|
||||
2. Complete full semantic negatives for queue precedence/order and forcing the
|
||||
receiving R13 guest back into its access cell. Existing primitive receiving
|
||||
fixture tests and overlay negatives are retained but are not substitutes for
|
||||
every required state-to-geometry case.
|
||||
3. Audit and implement regrouping after scale changes in BOTH packers. They
|
||||
currently form vertical groups before fitting; fitted feet/cores can change
|
||||
vertical intersections. Do not claim the required regrouping positive passes.
|
||||
Confirm carrier-relative pose/feet, cabin member coverage, and badge placement
|
||||
through the required semantic cases before declaring the renderer finished.
|
||||
4. Add full frame/offline/OS-context unit cases (including rest authority and
|
||||
malformed/missing stream context). Standalone/full transit paths have tests;
|
||||
production caller wiring was read but no OS/offline evidence run occurred.
|
||||
5. Finish the GDScript UI fixture and source-authority integration audit. Existing
|
||||
four-occupant UI preparation is retained and unexecuted. Marker contracts and
|
||||
suite policies are unchanged; do not append a duplicate suite row or fabricate
|
||||
an acceptance marker. If an authority append is necessary, only the single
|
||||
strict contract-permitted entry is authorized.
|
||||
|
||||
Local checks actually executed:
|
||||
|
||||
- Checker unit command `PYTHONDONTWRITEBYTECODE=1 python3
|
||||
tools/test_p23_life_observation_evidence.py`: 78 tests OK (0.104s), 80 OK
|
||||
(0.112s), 83 OK (0.134s), 85 OK (0.112s), then **86 OK (0.124s)**.
|
||||
No checker unit failure occurred in this continuation.
|
||||
- No-bytecode `compile(Path(p).read_bytes(), p, "exec")` syntax check for
|
||||
checker, checker tests and Root runner: all three OK, exit 0.
|
||||
- Read-only `git diff --check`: exit 0, no output.
|
||||
- One shell heredoc edit attempt failed before execution (`can't create temp
|
||||
file for here document: operation not permitted`); applied the intended edit
|
||||
with apply_patch afterward. One read-only search named a nonexistent
|
||||
`tools/regression/suite_policy.py`; read the actual JSON policy and runner
|
||||
directly. These were tool/read failures, not test passes.
|
||||
|
||||
No Godot/native/OS, full regression, Runner, provider, git write, integration or
|
||||
other agent was run. Root's **new native/OS evidence**, the **177-test full
|
||||
regression**, and the **independent Opus all-condition review** are still missing.
|
||||
Those expected later proofs alone are not the reason for this blocked status.
|
||||
|
||||
## Repair 24 continuation — current status: blocked, implementation incomplete
|
||||
|
||||
The floor/anchored-region interpretation is RESOLVED. No clarification or
|
||||
permission is requested. This status is an incomplete implementation, not an
|
||||
external specification blocker and not a submitted candidate. Preserve this
|
||||
worktree and every earlier uncommitted change. Historical sections below are
|
||||
retained as history, not current interpretations.
|
||||
|
||||
Read the current renderer, RoomPresentation, the recorded concrete direction,
|
||||
both R13 direction files, Phase 0 API map, Runtime initialization/spawn/formal
|
||||
save/load sources, and cleaning pose dependencies. No domain inputs or assets
|
||||
were changed. Seven conditions, six categories and 30/88 remain exact.
|
||||
|
||||
Implemented preparation in this continuation:
|
||||
|
||||
- Added per-actor cell/track, lane, facility, work-column and carried-patient
|
||||
bounds, inset by world pixel; added original logical cell to drawing rows.
|
||||
Staff queue placement now precedes the special staff anchors.
|
||||
- Added a cross-kind vertical grouping and forward/backward interval packer,
|
||||
integer scale trials and deterministic retained-member aggregation with a
|
||||
readable +m badge. Cabin rows participate. Preferred centers and pose fields
|
||||
remain separate from fitted drawing coordinates. This GDScript has NOT been
|
||||
executed and is not established as native-correct.
|
||||
- Added independent Python interval fitting and replaced original-scale
|
||||
assertions with reconstructed centers, bounds, poses, fitted rows and
|
||||
aggregation comparison. Queue and room reports are cross-checked before
|
||||
geometry classification; boarded keys are rebuilt from EV reports.
|
||||
- Added tests for the 56px four-mover case (all p=2), mixed actor kinds,
|
||||
saturation with all identities retained, individual intervals, and R13's
|
||||
receiving cafeteria guest outside its unchanged access cell. Added a full
|
||||
synthetic state-to-geometry four-mover case and mutations for cell, state,
|
||||
scale, position, missing/duplicate actors and unnecessary aggregation.
|
||||
- Added the four-occupant GDScript component fixture. It is NOT native evidence.
|
||||
|
||||
Known remaining implementation work (must be resolved before submission):
|
||||
|
||||
1. The full geometry entry point passes independently derived transit keys to
|
||||
pairwise checking, but standalone drawing-row/offline checks still use the
|
||||
conservative default with no transit exemptions. Reconcile those paths
|
||||
using authoritative inputs; never trust an in_transit/region flag.
|
||||
2. Existing compare_rest still expects each visible slot in actor_drawings and
|
||||
its original rest center. Reconcile legitimate aggregation and the new
|
||||
staff queue precedence without bypassing the independent no-fit check.
|
||||
3. Finish full classification tests for queue/work mixed groups, queue
|
||||
precedence/order, spoofed category, forced access-cell receiving position,
|
||||
carried/resting/treated staff, passengers and FRONT/BACK. Existing primitive
|
||||
packing positives are not a substitute for these full semantic cases.
|
||||
4. Audit pose/feet and regrouping after scale changes, low-world-pixel views,
|
||||
carried patients and cabin aggregation; validate the actual GDScript and
|
||||
report consumers through Root only after worker readiness. No new native
|
||||
correctness or complete collision-removal claim is made here.
|
||||
|
||||
Checks actually executed in this continuation, all local:
|
||||
|
||||
- `PYTHONDONTWRITEBYTECODE=1 python3 tools/test_p23_life_observation_evidence.py`:
|
||||
71 tests OK (0.093s), 76 tests OK (0.102s), then **78 tests OK (0.111s)**.
|
||||
Existing R13 row11/29 remains an expected rejection. No unit test failure
|
||||
occurred during this continuation; earlier failures remain recorded below.
|
||||
- No-bytecode `compile(Path(p).read_bytes(), p, "exec")` for checker,
|
||||
checker tests and Root runner: all three syntax checks OK, exit 0.
|
||||
- One dependency lookup used the wrong cleaning-bridge directory and returned
|
||||
an rg missing-file error; located and read the actual source under
|
||||
scripts/environment. This was a read failure, not a passing test.
|
||||
|
||||
No Godot/native/OS, full regression, Runner, provider, git write or agent was
|
||||
run. Root's **new native/OS evidence**, the **177-test full regression**, and
|
||||
the **independent Opus all-condition review** are still missing. Historical
|
||||
49 remains preserved and is not relabeled. No partial acceptance is claimed.
|
||||
|
||||
The inspection below is retained history. The repair 24 section above records
|
||||
the current status; the former full-box impossibility argument is superseded.
|
||||
|
||||
Scope: the seven immutable acceptance conditions and existing allowed paths.
|
||||
Validation: local Python syntax and checker unit tests only; no Godot execution.
|
||||
|
||||
Read the immutable acceptance spec, recorded concrete implementation direction,
|
||||
existing Phase 0 API map, renderer, actor art, and independent checker.
|
||||
The R13 provider stdout, command, and prompt SHA256 values match 01-actual.json.
|
||||
The actual structured review contains 8 frames and 132 rows. Its summary's
|
||||
168-row claim is not used to replace the structured rows. False judgments remain
|
||||
false. The reader-request-0003 whole screenshot confirms overlapping actors and
|
||||
obscured traffic presentation. Historical runtime evidence is unchanged.
|
||||
|
||||
Root native/OS evidence for a new candidate, the 177-test full regression, and
|
||||
independent Opus all-condition review remain required and are not worker tests.
|
||||
|
||||
## Status: blocked; partial independent checker preparation only
|
||||
|
||||
The renderer repair and traffic geometry/layer audit are **not implemented**.
|
||||
No claim of submission readiness or acceptance is made. No renderer, Runtime,
|
||||
policy, immutable spec, source authority pin, or historical evidence was changed.
|
||||
|
||||
### Placement specification gap and concrete failing evidence
|
||||
|
||||
In the original `os-pair/A/reader-request-0003/request.json`, SHA256
|
||||
`7dc3ee0bcc428e9d103726ee2448c767f1dd4107f9490833dc860c0b5140068c`,
|
||||
the decoded report contains guests `os-p23-012` through `os-p23-015`, all
|
||||
`MOVING`, logical cell `[1,1,0]`, with no service or queue request. Their
|
||||
tracks' `from` and `cell` are both `[1,1,0]`. The current cell is 64x96 pixels
|
||||
(`traffic` row 40's inset rect is `[709,390,60,92]`). Each actor's current
|
||||
draw/report footprint is 48x54 pixels: the 16x18 PixelPeople grid at scale 3.
|
||||
The four rectangles require area 10368, while the cell has area 6144.
|
||||
Consequently offsets alone, preserving those sizes and containing all four
|
||||
rectangles in that cell, cannot satisfy the requested nonoverlap invariant.
|
||||
This is already present in the supplied evidence, not a hypothetical load.
|
||||
|
||||
Source references: `world_preview.gd::_cell_dimensions`, `_actor_drawing`,
|
||||
`_tracked_actor_position`; checker `drawing_rectangle_rows` and
|
||||
`compare_actor_geometry`; `pixel_people.gd`'s 16x18 drawing grid.
|
||||
|
||||
The direction specifies offsets inside room/cell art, containment consistent
|
||||
with logical cell/state, retained motion, and no visual redesign. It does not
|
||||
resolve what presentation change is permitted when existing footprints cannot
|
||||
fit. Shrinking actors or expanding the permitted projection area could solve
|
||||
this capacity issue, but neither is silently treated here as authorized by an
|
||||
offset repair. This is a question about the admissible presentation, not a
|
||||
claim that every possible rendering solution is impossible.
|
||||
|
||||
Minimal proposal for the owner: specify whether deterministic scale reduction
|
||||
within the original logical cell is permitted (including the required readable
|
||||
minimum and behavior when it still cannot fit), or define an allowed room/lane
|
||||
projection for these non-queued moving actors. No Runtime capacity change,
|
||||
actor omission, or acceptance waiver is proposed. Retain all seven conditions.
|
||||
|
||||
### Independent work preserved
|
||||
|
||||
Added `validate_actor_nonoverlap` to both the semantic geometry path and offline
|
||||
drawing-row gate. It recomputes full footprints from raw `at`/`scale`, includes
|
||||
cabin passengers, rejects duplicate identities and invalid numeric geometry,
|
||||
and does not use eligibility labels or asserted booleans. Edge contact is
|
||||
allowed; positive-area overlap is rejected with actor identities.
|
||||
|
||||
Five added unit tests cover edge contact, overlap despite forged verdicts and
|
||||
off-screen labels, cabin/floor overlap, invalid footprints, and the actual R13
|
||||
false row 11 against staff row 29. The small historical-negative fixture copies
|
||||
only the needed raw drawing inputs and preserves `[11,false,false,false]` with
|
||||
request/manifest hashes. Its inputs were checked against the original typed
|
||||
report. The original review files and all their rows remain untouched.
|
||||
|
||||
The original manifest hash matches
|
||||
`53aa8e2f12e5d0a089529457fcd55ee1ba0600e8dffff2225bb8b057d007fe1a`;
|
||||
all crops in that manifest pass SHA verification. Opened the whole decoded image
|
||||
and crops 11, 29, 40 directly; crops 11 and 29 show the same covered figure.
|
||||
|
||||
Still missing: collision-free renderer placement, draw-consumed band rectangle,
|
||||
actual layer/geometry audits, and the requested additional cell/state, band,
|
||||
layer, and stale-frame negatives. Passing these local tests does not resolve
|
||||
those missing deliverables.
|
||||
|
||||
### Local checks actually run
|
||||
|
||||
Before edits:
|
||||
`PYTHONDONTWRITEBYTECODE=1 python3 tools/test_p23_life_observation_evidence.py`
|
||||
returned exit 0, `Ran 51 tests in 0.142s`, `OK`.
|
||||
|
||||
After edits the same command returned exit 0,
|
||||
`Ran 56 tests in 0.093s`, `OK`.
|
||||
|
||||
Syntax command (no bytecode output):
|
||||
```
|
||||
python3 -c 'from pathlib import Path; files=["tools/check_p23_life_observation_evidence.py","tools/test_p23_life_observation_evidence.py","tools/run_p23_life_observation.py"]; [(compile(Path(p).read_bytes(),p,"exec"),print("syntax OK:",p)) for p in files]'
|
||||
```
|
||||
Exit 0; printed `syntax OK:` for each of those three files.
|
||||
`git diff --check` returned exit 0 with no output (read-only check).
|
||||
No Godot, native/OS, full regression, provider, Runner, git write, or agent
|
||||
execution occurred. The initial shell heredoc attempt failed before execution
|
||||
because the shell could not create its temporary file; subsequent inspection
|
||||
used `python3 -c`.
|
||||
|
||||
## Recorded geometry direction continuation — blocked pending placement clarification
|
||||
|
||||
Read the complete `BUILD_OUTPUT_DIR/negative-image-review-r13/
|
||||
OPUS_GEOMETRY_RESOLUTION_DIRECTION.json`, the owner run direction, the current
|
||||
PixelPeople and WorldPreview sources, RoomPresentation slots, the existing
|
||||
Phase 0 audit and the actual initialize/spawn/save/load API sources. The spec
|
||||
still matches its contracted **stripped-text** SHA256. No spec, Runtime, save,
|
||||
asset, UI entry, gameplay value, authority entry or suite policy was changed.
|
||||
|
||||
### Implemented independent preparation
|
||||
|
||||
- Replaced the full-box overlap invariant with identity cores. The table is
|
||||
pinned to PixelPeople SHA256
|
||||
`a171703083aa2d00f8be2b63e1daef9c41717c8fe59d8056878bc4b7df1e0b0c`.
|
||||
Upright walking/carry-walking phases 2 and 6 shift the core up one grid unit.
|
||||
Standard-room sleep phases 2, 3 and 4 extend its top from y=11 to y=10,
|
||||
keeping the bottom at y=16, as required by the actual breathing marks.
|
||||
Flips use the actual 16-grid mirror. Missing pose inputs now fail closed.
|
||||
- Preserved the edge, forged-verdict, cabin, invalid-geometry and R13 tests.
|
||||
Edge coordinates now use 6x12 core extents instead of the rejected 16x18
|
||||
full-box extents; the deliberate overlap moves from [15,17] to [5,11].
|
||||
Added a passing full-box-overlap/disjoint-core case and bob, breathing,
|
||||
flip, missing-pose, duplicate, forged-transit and source-pin tests.
|
||||
R13 row11/29 still fails: both recomputed cores are [669,520,18,36].
|
||||
The fixture's newly required mode/frame/flip values were copied from the
|
||||
original request and checked against it. False judgments remain false.
|
||||
- Added the LifeOverlay Node2D between InactiveLayerRooms and LayerLens,
|
||||
connected its draw signal, and queued its redraw from the parent draw.
|
||||
The overlay draws traffic first, then cabin passengers and floor actors.
|
||||
Existing inactive-art alpha remains 0.22. Actor rows and animation inputs
|
||||
are unchanged. The report records observation/consumed serials, actual
|
||||
child indices and the passes executed in the callback.
|
||||
- Added the prescribed band rectangle at cell_bottom - 2*world_pixel,
|
||||
with existing thickness clamp, colour thresholds and 0.90 opacity.
|
||||
The blocked X is contained in the band pass. `compare_traffic` independently
|
||||
recomputes both cell and band rectangles from typed state. Overlay checks
|
||||
reject stale serial, reversed passes, child-order errors, a core covered
|
||||
by the band, and forged band geometry. Source wiring/order is also checked,
|
||||
so valid report flags do not excuse the tested reversed source callback.
|
||||
FRONT/BACK local synthetic checks and GDScript serial/order assertions were
|
||||
added. GDScript was **not executed**.
|
||||
|
||||
### Exact outstanding interpretation
|
||||
|
||||
Placement rule 1 gives service/rest/treatment anchors a facility region, while
|
||||
rule 2 says a floor actor must *also* remain in its own logical cell. It is
|
||||
unclear whether “floor actor” excludes these facility-anchored actors or means
|
||||
every non-passenger. This is consequential in the actual R13 request, not only
|
||||
in a fabricated layout:
|
||||
|
||||
- `guest-auto-00000044` receives `service_00000009` at cafeteria
|
||||
`facility_00000003`. Its unchanged logical cell is [5,0,0]. The recorded
|
||||
traffic inset [965,486,60,92] reconstructs cell [963,484,64,96].
|
||||
The facility rect is [1027,388,256,192], and its four-pixel inset starts at
|
||||
x=1031. There is no common x interval with the logical cell ending x=1027.
|
||||
- `guest-auto-00000019` receives `service_00000002` at standard room
|
||||
`facility_00000007`, rect [131,292,128,96]. Its logical access cell [-6,2,0]
|
||||
reconstructs to [259,292,64,96]. The facility inset ends x=255; again there
|
||||
is no common x interval. Its room pose is standard_room_sleep.
|
||||
|
||||
`r13_service_region_gap.json` preserves the needed original report fields,
|
||||
with source request SHA256
|
||||
`7dc3ee0bcc428e9d103726ee2448c767f1dd4107f9490833dc860c0b5140068c`.
|
||||
Direct comparison verified every copied guest, traffic and room rectangle.
|
||||
A diagnostic unit test demonstrates the empty intersection for both examples.
|
||||
These extracted fields are not substituted for whole typed acceptance evidence.
|
||||
|
||||
This does **not** establish that the new specification is impossible: rule 7
|
||||
allows aggregation when p=1 cannot fit. If floor means all non-passengers, these
|
||||
otherwise roomy facilities must aggregate their service guests with zero drawn
|
||||
members. If floor means unanchored actors, those guests can be packed and drawn
|
||||
in their facility region. The initial commentary's “constraint conflict” is
|
||||
therefore narrowed to this interpretation question. No cell restriction was
|
||||
silently removed and no zero-drawn service aggregation was silently adopted.
|
||||
An asynchronous question asks the owner to select the intended interpretation.
|
||||
|
||||
### Remaining implementation and evidence
|
||||
|
||||
Ordered placement, scale fitting, no-fit aggregation, queue-order preservation,
|
||||
carrier-relative fitted placement, transit recomputation/exemptions and their
|
||||
checker integration remain unimplemented. The checker remains conservative:
|
||||
an asserted in_transit flag never bypasses overlap, but the required genuine
|
||||
transit exception is not implemented. The existing geometry checker still
|
||||
expects original actor positions/scales. The four-occupant GDScript fixture,
|
||||
positive packing cases (staff/guest, passenger/floor, queue, saturation and
|
||||
carried patient), and the new containment/aggregation/queue-order negatives
|
||||
remain outstanding. The partial renderer changes are not a submitted candidate
|
||||
and are not claimed to solve R13 actor collisions. All existing tests remain.
|
||||
|
||||
Still missing: Root **new native/OS evidence**, the **177-test full regression**,
|
||||
and **independent Opus all-condition review** for the final candidate.
|
||||
Historical 49 is preserved and is not relabeled or credited to these edits.
|
||||
Seven immutable conditions and all6/30/88 remain exact, without partial credit.
|
||||
|
||||
### Checks actually run in this continuation
|
||||
|
||||
- `PYTHONDONTWRITEBYTECODE=1 python3 tools/test_p23_life_observation_evidence.py`:
|
||||
first core run: 63 tests, 7 failures and 1 error because older synthetic
|
||||
reports omitted pose fields. Updated those fixtures with explicit poses;
|
||||
63 tests then passed (0.089s), followed by 64 (0.094s) after the bounds
|
||||
diagnostic. First overlay run: 71 tests, 1 error because the older spatial
|
||||
fixture lacked the new band rectangle. Updated that fixture with explicit
|
||||
band geometry and consumed-frame fields. Final run: **71 tests, OK, 0.081s**.
|
||||
- The existing no-bytecode `compile(Path(p).read_bytes(), p, "exec")` syntax
|
||||
check passed for checker, checker tests and Root runner script (exit 0).
|
||||
- Direct Python read-only comparisons verified both extracted R13 fixtures
|
||||
against the original SHA-bound request and verified the immutable spec pin.
|
||||
- A shell heredoc read attempt failed before execution with “can't create
|
||||
temp file”; inspection continued with `python3 -c`.
|
||||
|
||||
No Godot, native/OS, full regression, Runner, nested provider, git write or
|
||||
other-agent execution occurred. Only allowed files were edited. The worktree's
|
||||
previous uncommitted work was retained and extended.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Repair28 continuation / repair29 source handback
|
||||
|
||||
Record: SOURCE-READY, source-only submission for P23-03. This is not acceptance.
|
||||
The preserved repair28 implementation is unchanged by this continuation. Only the
|
||||
existing P23 marker source audit SHA and this new note are changed in repair29.
|
||||
Older notes, raw receipts and failure history remain unchanged.
|
||||
|
||||
## Input and preserved-state verification
|
||||
|
||||
Read all six Root inputs in full from the explicitly supplied
|
||||
`.runtime/P23-03/build/audit-support/root-repair29-input/` directory outside W.
|
||||
Each byte count and SHA256 matches MANIFEST.json. Its authoritative patch is
|
||||
64,737 bytes (SHA256 6b0a2a236a08f1c473850c06f7a761d1f0f8f4749db271771550d409d46e37b0),
|
||||
despite the older instruction's 384318-byte description. No input is missing.
|
||||
Before editing, all eight W file lengths and SHA256 values matched
|
||||
PRESERVED_MANIFEST.json; `git diff --binary` equalled PRESERVED_DIFF.patch byte
|
||||
for byte. The untracked r15 fixture was separately verified by its manifest SHA.
|
||||
|
||||
## A–E disposition and source map
|
||||
|
||||
| Item | Existing implementation / disposition |
|
||||
| --- | --- |
|
||||
| A1–A2 | scenes/main/world_preview.gd:1057 expands stops by reported shaft width; :1076 stops outward lanes at the footprint; :1094 retains member/visible/overflow accounting and badge offset. tools/check_p23_life_observation_evidence.py:965 independently reconstructs queues. |
|
||||
| A3 | scripts/ui/room_presentation.gd:77 supplies count, operation, doors and phase text; scenes/main/world_preview.gd:1224 draws existing PixelArt cabin/gate and badge on LifeOverlay; :811 draws cabins above inactive-layer context. |
|
||||
| A4 | scenes/main/world_preview.gd:640 collects opaque obstacles, :674 subtracts rectangles, :831 draws every traffic row including fully covered rows beneath opaque later cabins/badges. No density row is discarded. |
|
||||
| A5 | scenes/main/world_preview.gd:564 deep-copies the consumed model, including shaft_footprint (:1097), badge_rect (:1286), traffic_obstacles (:649), segment_rects (:665). |
|
||||
| B(a–d) | tests/ui/test_p23_life_observation.gd:32 covers width-2, overflow, empty cabin, both layers, operation/doors/phase and traffic geometry. tools/test_p23_life_observation_evidence.py:40 adds meaningful R15 intrusion/traffic and typed-state negatives. tests/fixtures/p23-03/passive_observation.gd:194 uses the reproducible badge rectangle. |
|
||||
| C1–C2 | tools/check_p23_life_observation_evidence.py:1840 validates external S/M/T/Q, allowed changes, deterministic merge tree, S-only/M-only blobs and complete T source/settings/input/QA bindings; :1799 and :1821 enforce the structural marker join retaining M/P18. tools/run_p23_life_observation.py:245 consumes external roles; :334 records v2 evidence. tools/test_p23_life_observation_evidence.py:135 covers composition negatives. |
|
||||
| C2(vi), C3 | tools/check_p23_life_observation_evidence.py:2108 refuses v1 history as current evidence; :1986 allows one screenshot only for screenshot requests, retaining real-input pairs. Existing staff, OS receipt, distinct-PID resume and image gates remain. Root owns final full regression and independent review. |
|
||||
| D | The earlier assertion that kitchen full177 was consistent is withdrawn: p04-final-product-full-r01 failed the relationships-v2 kitchen envelope (legacy/version/original_relationships added; threshold_candidates/next_candidate_sequence missing). Old 177 gives no T credit. No kitchen source or overlay was changed. ACTUAL_SUPPORT_COMPLETED.json records approved QA3a9c support integration 5041b88bdf25d0deaf3cb335be981896522c4880 (reported main tree df9b2851edaa7d02a55b834328cf9e9a9bb7203a). Root must bind the actual later M and its kitchen QA blob to T; neither 9b nor any old full result is fixed as authority. The four D05 choices are not worker blockers. |
|
||||
| E; repair29(b–c) | This note supplies the continuation handback and structured SOURCE-READY record. |
|
||||
| repair29(a), step 4 | completion_marker_contracts.json:3710 now pins the actual final GDScript test SHA. Only that existing row's audit SHA changes; template, required flag, authority scope and every other row remain intact. |
|
||||
|
||||
No concrete remaining A–C source omission was identified in this bounded check;
|
||||
therefore no code or test was changed and no new negative test was needed.
|
||||
|
||||
## R15 negative fixture provenance
|
||||
|
||||
tests/fixtures/p23-03/repair28_r15_negative.json is an extraction of historical
|
||||
local-coordinate fields, not a raw receipt. It identifies source 9a26f9a,
|
||||
manifest a25e15f00d09acb16b05e180a6263c3771c84449bd6e0c99b6968fd739727bd2,
|
||||
request 7099ca03da3cef5819e7965f6fdea8777b9995a9e12806e870e844ec2b02b9a5,
|
||||
R15 os-pair/A/reader-request-0003/request.json and negative rows 6/17.
|
||||
Tests calculate counterfactual geometry only in memory. This extraction and all
|
||||
synthetic projections grant no S/T execution or OS credit and do not relabel raw history.
|
||||
|
||||
## Frozen changed-source SHA256
|
||||
|
||||
| Path | SHA256 |
|
||||
| --- | --- |
|
||||
| scenes/main/world_preview.gd | 6c43e19fbce8ff5c442017ab22443f39f65f356c308ee895355cd7678d307f33 |
|
||||
| scripts/ui/room_presentation.gd | 2eda851534777575298ea005e2e6d0ee90785a81357d5b9c34cfb95f615a8181 |
|
||||
| tests/fixtures/p23-03/passive_observation.gd | 812c2a3b399b079333e4eae0fbf78946f1c39041e6859e65f8bc7c44aaad6bfc |
|
||||
| tests/fixtures/p23-03/repair28_r15_negative.json | 8525cdfbc504583778624ab103efb4642374715be73187e97bdbf18d180a1616 |
|
||||
| tests/ui/test_p23_life_observation.gd | 610ff2f33ec0e42bdca55d692e640c9bd8fe9244b9b49b0263419b1998ff9e9e |
|
||||
| tools/check_p23_life_observation_evidence.py | 767f3452cd9f0671e25bfda113153b0b1471fcbfdfa7aba6133f991a61cf306d |
|
||||
| tools/run_p23_life_observation.py | 1fa85aa53886887f9611997194240bf6e112f831e8f36ea3aff982d12538bdec |
|
||||
| tools/test_p23_life_observation_evidence.py | 4a52215b125f3445abde9992ee69ad9b09fc114977e359b52f190c966e6d3c39 |
|
||||
|
||||
## Verification and pending Root evidence
|
||||
|
||||
ROOT_LOCAL_TEST_RESULT.json is authoritative: `python3 -B
|
||||
tools/test_p23_life_observation_evidence.py`, exit 0, **123 tests, 0 failures**,
|
||||
0.267 seconds. The earlier worker count of 121 is superseded. These unchanged
|
||||
tests were not rerun in repair29; no Python source changed, so no syntax rerun
|
||||
was needed. Manifest/hash/diff comparisons via `python3 -B -c` exited 0.
|
||||
`git diff --check` exited 0. Final `python3 -B -c` hash/byte comparison exited 0:
|
||||
all eight preserved sources remain unchanged, and the marker file differs from
|
||||
HEAD by exactly the one authorized SHA replacement. Marker file SHA256 is
|
||||
4dbba39cad454d02a004ee3d000457c3e13f7fc017dcfdb847aca7b192a8e30b.
|
||||
The P23 marker entry SHA256 is
|
||||
f83f022c44db6f89fc18d634f7ec9ce54b56450299c0e30fb1275cd2b88b6b39
|
||||
(UTF-8 JSON, sorted keys, compact separators, ensure_ascii=False).
|
||||
Initial shell attempts failed before validation: heredoc temporary-file creation
|
||||
was denied (exit 1); `python` was unavailable (exit 127). The successful checks
|
||||
used `python3 -B -c` without temporary files.
|
||||
|
||||
Still missing and explicitly Root's work: exact-source native/component and
|
||||
impact evidence, same-demand A/B, normal OS on both layouts across all six
|
||||
categories and FRONT/BACK/pan/zoom, formal save and distinct-PID normal load,
|
||||
actual CUA receipts and independent bound-frame image judgments, one mandatory
|
||||
full regression on T, and independent Opus all-condition review. Root binds
|
||||
S/M/T/Q after submission and performs normal integration/final gates. No worker
|
||||
engine, OS, Runner, Git write, main operation or subagent was used. Seven
|
||||
conditions, six categories, 30/88 denominator, formal13, max repairs null and
|
||||
all counters/caps remain unchanged. No acceptance verdict is issued.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Repair 28: traffic projection checker
|
||||
|
||||
Scope: checker, its Python tests, one minimal decoded fixture and this note. Renderer, Runtime, save paths and controller unchanged. No git writes, Godot/native/OS, Runner or full regression operations.
|
||||
|
||||
## Separate routing and defect records
|
||||
|
||||
- Normal negative validate 97b6cdf9 failed because the current index was absent. Historical 49 was preserved by canonical rename retaining index SHA c0cc9e…. That missing-current-proof result is not evidence of this static defect.
|
||||
- Independent readiness 887f9684 (READINESS_19FC3F5_ACTUAL.json) identifies the actual checker overconstraint. ROOT_19FC3F5_BAND_MISMATCH_REPRO.json and OPUS_19FC3F5_BAND_SCOPE_DIRECTION.json describe its repro and authorized scope.
|
||||
|
||||
All seven repair27 input files were read/decoded directly and their byte counts and SHA256 checked against MANIFEST.json (original paths retained in fixture provenance). The original frame-180 landing densities remain zero. The new 45 KB fixture contains only the unchanged decoded fields used by overlay validation: report frame/source metadata, actor drawings, cabins, traffic and camera/elevator track fields; raw world bounds, elevators and staff tasks. It is an extraction, not a raw receipt. No unused megabytes of state, rest or motion were copied. The positive test is explicitly COUNTERFACTUAL: only its in-memory report densities at (0,1,0) and (1,1,0) become 1, covering guests 018/019. It grants no engine/OS acceptance.
|
||||
|
||||
## Correction and caller audit
|
||||
|
||||
compare_traffic passes its actual snapshot into validate_life_overlay(report, root=ROOT, snapshot=None). inspect_stream already binds that state to its raw object and invokes compare_traffic; native layout and OS writer/reader stream validation therefore receive raw authority. The controller run_p23_life_observation.py has no direct validate_life_overlay/compare_traffic caller. Offline/OS drawing paths already use drawing_context to bind report/state/rest and retain compare_rest, compare_cabin_geometry and compare_actor_geometry. No new drawing gate was added there. Existing direct unit callers without state remain fail closed.
|
||||
|
||||
Cabin exemptions apply only to passenger entries belonging to that same raw shaft; cabin geometry reuses compare_cabin_geometry. A floor entry cannot inherit a passenger exemption by copying its key. Carried exemptions require actual active rescue transport binding and reuse compare_actor_geometry for original binding, track and raised-region fitting, excluding boarded/queue placements. Existing rest-authority checks remain unchanged. Missing-carrier KeyError remains deferred and fails closed.
|
||||
|
||||
Serial, consumed serial, reported passes, source child/pass order, source connection/parent restrictions, band rectangle and same-report checks remain unchanged. Floor cores keep the same strict intersection predicate. Every intersecting actor contributes a clipped horizontal interval. Sorted interval union must leave a nonzero uncovered span; no ratio or threshold was introduced.
|
||||
|
||||
## Local verification
|
||||
|
||||
- Python 3 -B compile() of both changed Python files: passed without writing bytecode.
|
||||
- Entire checker suite: 108 tests passed (all 99 existing plus 9 new); original R13 row11/29 negative retained. New tests cover counterfactual cabin success, occupied and blocked full union occlusion, spoofed cabin rectangle/membership, absent raw state, floor copy of cabin key, missing carrier binding, and carried binding/track/position mutations. Existing reversed report/source pass tests remain.
|
||||
- git diff --check: passed.
|
||||
|
||||
Root native/component, A/B on both layouts, normal OS, one full177 and independent Opus all-condition pixel review are expected after submission and do not block worker submission. No such unexecuted checks are claimed passed. Registered timeout 900 s, seven conditions, six categories, 30/88 and formal credit 0 remain unchanged.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Repair 35 — source submission
|
||||
|
||||
Scope: P23-03 cabin-intrusion presentation repair only. Source input is
|
||||
`93248f4d79a3be71d7a1ac0592e9219d405cd6b7`; recorded tested Q is
|
||||
`428f653cdac375c1d4589e6c2d6138e914acf9da`. No Runtime, demand, assets,
|
||||
acceptance, policy, process ownership or timeout changes.
|
||||
|
||||
## Exact inputs and Phase 0
|
||||
|
||||
First read the repair35 `EXACT_INPUTS.json` under
|
||||
`/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/build/audit-support/cabin-repair35-exact-inputs/`.
|
||||
Read its complete supplied `original_full_request.txt`, `actual_Opus_repair.json`
|
||||
and `actual_Opus_run.json`; all three supplied SHA256 pins match. Provenance-only
|
||||
source locations were not needed. Read the sibling actual-worker preflight.
|
||||
|
||||
Read the SHA-verified preserved all87 probe
|
||||
`audit-support/cabin-repair34-exact-inputs/CURRENT932_ALL_CABIN_INTRUSION_PROBE.json`
|
||||
under the same BUILD (SHA256
|
||||
`da395d697e0ec1bd2fb805757aa320314bbc840260f6062d12ecf5d859ceebd0`).
|
||||
Read all 87 complete collision rows and all their whole SHA-verified typed
|
||||
state/report objects: 19 distinct stream/state/report pairs, 31 distinct objects.
|
||||
Also decoded all referenced objects in all 161 current stream captures and read
|
||||
every file in the supplied r16 stage (1962 files, including the actual Q sources).
|
||||
No required evidence was truncated, compressed, relabelled or edited.
|
||||
|
||||
Used that preserved evidence to apply the new candidate-region rule: 70 rows
|
||||
select a corridor landing and 17 require identity aggregation because no lane is
|
||||
available. All 87 candidate regions intersect a cabin; outside-class rows: zero.
|
||||
This is static application to old negative evidence, not new rendered proof.
|
||||
|
||||
The existing `API_AND_STATE_MAP.md` remains the deterministic-demand/save-load
|
||||
audit. Directly checked current GuestRuntime enum (`MOVING` = 3, lines 6–19),
|
||||
WorldService `get_occupant`/`get_entity_cells`/`is_walkable` (194/204/228), and
|
||||
ElevatorService's copied public report (345). These read-only APIs supply the
|
||||
same queue lane topology and full-width shaft geometry; no new API is needed.
|
||||
Current complete dependency SHA256:
|
||||
|
||||
| Source | SHA256 |
|
||||
| --- | --- |
|
||||
| scripts/world/world_service.gd | add67a0799f961a7dce1d382ea6dac67022fbd013f8c4c6b11321df96f37944a |
|
||||
| scripts/transport/elevator_service.gd | cea09dd9204551c26b31b30cd3aa847cc77e3b75fdac31662b9694a95fb3580c |
|
||||
| scripts/guest/guest_runtime.gd | 57ba99e5d977b5302987f4359a19056f1ab09c8fb7e4762ee8d79cbb9316741a |
|
||||
|
||||
The immutable spec matches `8568457d…a9c89f` using the existing checker's
|
||||
`read_text().strip()` hash convention. Original acceptance and recorded complete
|
||||
Opus repair/run directions remain authoritative.
|
||||
|
||||
## Six pose classes
|
||||
|
||||
| Case | Product, world_preview.gd | Independent checker |
|
||||
| --- | --- | --- |
|
||||
| (a) MOVING/idle footprint actor | 1608 candidate region, 1651 cabin rule, 1743 center before fitting | 1532 candidate region, 1577 cabin rule, 1584 center before jitter |
|
||||
| (b) Lift-to-corridor walking transit | 1555 merged track bounds, 1565 landing becomes non-transit | 1532 merged track bounds, 1458 landing becomes non-transit |
|
||||
| (c) Carrier and raised patient | 1624 recursively relocated carrier region, then minus 12p | 1556 recursively relocated carrier region, then minus 12p; 1612 center |
|
||||
| (d) Fractional cabin crossing adjacent floor | 1565 positive-area intersection with actual cabin rect | 1458 intersection after independent cabin/badge validation at 1505 |
|
||||
| (e) No corridor lane | 1590 landing badge anchor, 1759 aggregation with every identity | 1482 landing badge anchor, 1703 identity aggregation |
|
||||
| (f) Queue plus relocated actor | 1602 prefer unreserved lane, otherwise existing packing | 1487 reservation choice, 1712 existing packing |
|
||||
|
||||
Room slots, queue placement/overflow, cabin/passenger geometry and overlay order
|
||||
are retained. The new landing centers preserve original jitter; carried patients
|
||||
use the relocated carrier before the 12p raise. No-lane actors share a counted
|
||||
aggregation at the floor's landing badge anchor, retaining key/id/state/cell/mode.
|
||||
Report-only region/boarded assertions cannot authorize a position.
|
||||
|
||||
`validate_cabin_intrusion`, `validate_actor_nonoverlap`, `actor_track_transit`
|
||||
are byte-identical to the supplied Q checker; `PIXEL_PEOPLE_CORE_SHA256` is
|
||||
unchanged. The existing P23-03 marker source-audit SHA alone was updated; all
|
||||
other marker rows and suite-policy are preserved.
|
||||
|
||||
`run_p23_life_observation.py:315` inspects both completed headless layout streams
|
||||
inside the existing exception/receipt/cleanup path, before OS collection.
|
||||
Failure therefore prevents OS collection. This is staging, not OS evidence.
|
||||
|
||||
## Verification and remaining Root work
|
||||
|
||||
- `py_compile` passes for the runner, checker and checker tests.
|
||||
- The full Python checker test file passes: 142 tests, including the seven new
|
||||
tests covering (a)–(f), old in-cabin positions, tampered flags, missing actors,
|
||||
missing aggregation identities, combined no-lane identities and unwarranted
|
||||
relocation. Existing 135 tests are retained.
|
||||
- Native assertions were extended at `tests/ui/test_p23_life_observation.gd:56`
|
||||
for same-floor and fractional moving cabins on both layers; not executed here.
|
||||
- No Godot, native/OS collection, full regression, Git writes, Runner operations
|
||||
or subagents were run. Root native/OS evidence is missing.
|
||||
|
||||
Root staging remains: affected native/UI test; typed headless A/B with early
|
||||
strict inspection and new-output intrusion probe requiring zero; then current
|
||||
S/M/T/Q OS r17; then one final full 179 and independent review. Keep all r16
|
||||
negative inputs and the all87 probe versioned. Seven conditions,
|
||||
30/88/formal-13 and no partial credit remain unchanged.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Repair36 worker submission
|
||||
|
||||
Scope: world_preview observation labels, independent checker, Python/GD controls.
|
||||
No Runtime, service, data, art, policy, original negative, or acceptance changes.
|
||||
Input authority: registered BUILD `audit-support/badge-repair36-exact-inputs/EXACT_INPUTS.json`, source 26f60095; original paths are provenance identifiers.
|
||||
|
||||
The resolver places fixed cabin rectangles/badges first, then business, supply,
|
||||
landing, aggregation in array order. A positive-area collision advances to the
|
||||
first blocking rectangle's right edge and repeats. With finite rectangles of
|
||||
positive width this always terminates on the anchor row; the specified upper
|
||||
and lower fallback rows are therefore unreachable. No margin, pixel threshold,
|
||||
clipping, truncation, member alteration, or new viewport rule is introduced.
|
||||
Original anchors and ordered blocker identities are retained in deep-copy reports.
|
||||
Traffic obstacles use the resolved positions; actor placement/traffic subtraction
|
||||
and cabin/transit guards retain their existing implementations.
|
||||
|
||||
The checker separately projects anchors back into the original report shape for
|
||||
existing raw-state queue/business/aggregation recomputation. It independently
|
||||
replays label geometry and validates reported positions and blocker identities.
|
||||
All label pairs must have zero positive-area intersection. Both production image
|
||||
review consumers require explicit indexed `judgments`; the legacy `rows` helper
|
||||
interface remains available for unchanged historical unit controls only. Compact
|
||||
bits are never accepted for new image-review evidence.
|
||||
|
||||
## Static all40 comparison
|
||||
|
||||
Inputs: all40 original manifests plus their unchanged original requests in
|
||||
`p23-normal-os-26f-r17`, read only. This is a counterfactual static projection,
|
||||
not a new rendered screenshot or native/OS acceptance.
|
||||
|
||||
Original manifest label pairs: exactly one intersection, B/writer-request-0021,
|
||||
rows 5 (queue) and 26 (aggregation). Resolved labels: zero intersections in all
|
||||
40 frames, including fixed cabin labels. Thirty frames have no movement.
|
||||
The following is the complete movement list in local canvas coordinates:
|
||||
|
||||
| Frame | Label | Original | Resolved | displaced_by |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| A/reader-request-0003 | landing/0 | 563,468 | 755,468 | cabin/0/rect |
|
||||
| A/writer-request-0014 | landing/0 | 563,468 | 845,468 | cabin/0/badge |
|
||||
| A/writer-request-0021 | landing/0 | 563,468 | 875,468 | cabin/0/badge |
|
||||
| A/writer-request-0025 | landing/0 | 563,468 | 855,468 | cabin/0/badge |
|
||||
| A/writer-request-0030 | landing/0 | 563,468 | 755,468 | cabin/0/rect |
|
||||
| B/reader-request-0003 | landing/1 | 819,468 | 1111,468 | cabin/0/badge, cabin/1/badge |
|
||||
| B/writer-request-0014 | landing/0 | 563,468 | 1101,468 | cabin/0/badge, cabin/1/badge |
|
||||
| B/writer-request-0014 | landing/1 | 819,468 | 1217,468 | cabin/1/badge, landing/0 |
|
||||
| B/writer-request-0021 | landing/0 | 563,468 | 755,468 | cabin/0/rect |
|
||||
| B/writer-request-0021 | landing/1 | 819,468 | 1011,468 | cabin/1/rect |
|
||||
| B/writer-request-0021 | aggregation/0 | 563,372 | 825,372 | landing/2, cabin/0/badge |
|
||||
| B/writer-request-0025 | landing/1 | 819,468 | 1111,468 | cabin/0/badge, cabin/1/badge |
|
||||
| B/writer-request-0030 | landing/1 | 819,468 | 1111,468 | cabin/0/badge, cabin/1/badge |
|
||||
|
||||
The affected queue anchor remains local [563,372], corresponding to original
|
||||
manifest screenshot [642,469]. Its five members remain hidden and its complete
|
||||
label remains unchanged. The historical reviewer index24/text26 disagreement
|
||||
and all prior negatives remain untouched; no staff defect or acceptance credit
|
||||
is inferred.
|
||||
|
||||
## Validation boundary
|
||||
|
||||
Worker runs Python syntax/local semantic tests only. GD controls are added but
|
||||
not executed here. Root still must replay currentbad, run localized/native,
|
||||
new normal OS A/B with indexed independent image review, then one full regression
|
||||
and formal review/integration/ledger in the recorded order. No formal acceptance,
|
||||
original88 credit, Windows, held-gesture, performance, or asset credit is claimed.
|
||||
|
||||
Local result: Python AST syntax PASS; 146 Python tests PASS (142 retained plus
|
||||
four focused controls). All 142 pre-existing test method ASTs match HEAD exactly.
|
||||
`git diff --check` PASS. Only the existing P23 GD marker source SHA was refreshed;
|
||||
no marker requirements or policies changed. No Godot/native/OS/full was run.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Repair37 portable recorded negative fixture
|
||||
|
||||
Candidate starts at `059c2f743be655c11627a2b19d8eab9f86a000a6` under the unchanged P23-03 contract. No Git write or Runner operation was performed.
|
||||
|
||||
`tests/fixtures/p23-03/recorded-badge-negative.json.gz` is copied byte-for-byte from Root's `.runtime/P23-03/build/audit-support/badge-repair37-portable-fixture/PROPOSED_PORTABLE_FIXTURE.json.gz` in the parent worktree directory. SHA256: `17b1596fb1dca0436cd656ac21a9dc754c75b4485335a0aebe533f70e1c1b5ac`; gzip length 83,518 bytes; decompressed length 3,490,211 bytes. Repeated decompression yields identical bytes.
|
||||
|
||||
The bundle records observed source `26f60095bff015efbf7d7cf89b5b0bbab1fec31e`, observed tested commit `dfe0dc44b0b88ace598c1b8bd05283c6084cb081`, and original PID 87103. These are preserved negative-input provenance, not acceptance evidence for 059 or this candidate. Original relative paths below are relative to the preserved `p23-normal-os-26f-r17` evidence root:
|
||||
|
||||
| Payload | Original relative path | Bytes | SHA256 |
|
||||
| --- | --- | ---: | --- |
|
||||
| request | `os-pair/B/writer-request-0021/request.json` | 655285 | `f9cd2d5ff9dd63d9cf7ae4a295ccef8eea16f75ac784696dc40906703731e1d8` |
|
||||
| state | `os-pair/B/writer-stream/objects/724d8fe362ba3e05eddd83c4b7c54da101899df6cd91ae88250feea3d4feff82.json` | 1867011 | `724d8fe362ba3e05eddd83c4b7c54da101899df6cd91ae88250feea3d4feff82` |
|
||||
| manifest | `os-pair/B/writer-request-0021/review-image-0/manifest.json` | 75726 | `a98782ec1263aba400d542862dd084d2f1dd4cf7a0851cf7f0679867e545a338` |
|
||||
|
||||
The loader verifies all three payload lengths and hashes on every call. Request uses JSON, report and state use the existing typed decoder; state retains the whole-Dictionary check. There is no runtime lookup, fallback, cache, extraction, or skip.
|
||||
|
||||
Local validation: all 146 Python tests pass; AST audit against 059c2f7 confirms 145 test methods are source-byte and AST identical, and the remaining method is AST identical after replacing only the manifest assignment value with the same fixed placeholder. Python syntax compilation passes. A fresh isolated copy of Git-tracked candidate files plus the fixture, without `.runtime`, passes Repair36LabelTests 4/4. Separate corrupt-payload and missing-fixture controls fail with fatal errors. No extra fixture is retained.
|
||||
|
||||
Frozen whole-file SHA256 values remain unchanged: renderer `4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2`; checker `e41479ae51bccdfab3f31bd348794b441c2f341179bbe33222e2aacdf88f987f`.
|
||||
|
||||
Native and OS checks are still pending. Root's final local run on the new tested commit, raw geometry/native checks, new normal OS run, every indexed-image review, mandatory full 179, formal all 7, integration and ledger remain pending. No Godot or full suite was run by this worker.
|
||||
@@ -0,0 +1,335 @@
|
||||
{
|
||||
"candidate": "336a8cd0b30db1add11869ebceab847db693f531",
|
||||
"r10_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-03-native-os-atomic-336a8cd-r10",
|
||||
"terminal_error": "AssertionError: native OS collection failed: AssertionError: ordinary native OS exit0",
|
||||
"cleanup_errors": [],
|
||||
"A_writer_pid": 56892,
|
||||
"A_reader_pid": 60176,
|
||||
"B_writer_pid": 61335,
|
||||
"B_reader": "not run; B writer failed",
|
||||
"all_conditions_accepted": false,
|
||||
"formal_credit": 0,
|
||||
"confirmed_failure": "B construction-only diagnostic budget leaves zero cash; 5 hires and raw-food purchase rejected. Do not change production values or weaken conditions.",
|
||||
"actual_command_objects": {
|
||||
"A": [
|
||||
{
|
||||
"object_sha256": "0d360907ad9289b7db5bc759a220cf274184f18cff3fe98c912941fed22fb1a9",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_akane"
|
||||
},
|
||||
"result": {
|
||||
"code": "OK",
|
||||
"command_id": 24,
|
||||
"target_id": "",
|
||||
"message": "",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"candidate_id": "staff_akane",
|
||||
"staff_id": "staff_akane",
|
||||
"role_id": "receptionist",
|
||||
"daily_wage": 120,
|
||||
"employment_kind": "FREE_HIRE",
|
||||
"signing_cost": 180,
|
||||
"shift_id": "shift_00000001_d00000",
|
||||
"player_cash": 1320
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "6dfd11522dee4b5ac9e1eefad4c2a27f01265cd46f1bb3e364665a85cd78769a",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_gina"
|
||||
},
|
||||
"result": {
|
||||
"code": "OK",
|
||||
"command_id": 26,
|
||||
"target_id": "",
|
||||
"message": "",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"candidate_id": "staff_gina",
|
||||
"staff_id": "staff_gina",
|
||||
"role_id": "porter",
|
||||
"daily_wage": 110,
|
||||
"employment_kind": "FREE_HIRE",
|
||||
"signing_cost": 150,
|
||||
"shift_id": "shift_00000003_d00000",
|
||||
"player_cash": 1170
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "e87798f0030e97a630c76ed3ad9ecdc4e032a090efc5fbb4a39ac606857828dd",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_chika"
|
||||
},
|
||||
"result": {
|
||||
"code": "OK",
|
||||
"command_id": 28,
|
||||
"target_id": "",
|
||||
"message": "",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"candidate_id": "staff_chika",
|
||||
"staff_id": "staff_chika",
|
||||
"role_id": "cook",
|
||||
"daily_wage": 145,
|
||||
"employment_kind": "FREE_HIRE",
|
||||
"signing_cost": 250,
|
||||
"shift_id": "shift_00000005_d00000",
|
||||
"player_cash": 920
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "00c167c28d271fbe133642650ab17b1240e73abd1d5fbb1bcea683f62e603c26",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_iris"
|
||||
},
|
||||
"result": {
|
||||
"code": "OK",
|
||||
"command_id": 30,
|
||||
"target_id": "",
|
||||
"message": "",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"candidate_id": "staff_iris",
|
||||
"staff_id": "staff_iris",
|
||||
"role_id": "attendant",
|
||||
"daily_wage": 130,
|
||||
"employment_kind": "FREE_HIRE",
|
||||
"signing_cost": 210,
|
||||
"shift_id": "shift_00000007_d00000",
|
||||
"player_cash": 710
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "969d0116e1bcc57e28019273e401ff1bbed54ca5ab22455d677889e2375f45f5",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_juno"
|
||||
},
|
||||
"result": {
|
||||
"code": "OK",
|
||||
"command_id": 32,
|
||||
"target_id": "",
|
||||
"message": "",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"candidate_id": "staff_juno",
|
||||
"staff_id": "staff_juno",
|
||||
"role_id": "attendant",
|
||||
"daily_wage": 140,
|
||||
"employment_kind": "FREE_HIRE",
|
||||
"signing_cost": 240,
|
||||
"shift_id": "shift_00000009_d00000",
|
||||
"player_cash": 470
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "092af99c4a2f84786641430e6c1cebce62f813b69dbac5d5ac397a3017fe6a84",
|
||||
"decoded": {
|
||||
"command": "purchase_supplies",
|
||||
"payload": {
|
||||
"warehouse_id": "facility_00000004",
|
||||
"item_id": "ingredient/raw_food",
|
||||
"quantity": 40
|
||||
},
|
||||
"result": {
|
||||
"code": "OK",
|
||||
"command_id": 34,
|
||||
"target_id": "",
|
||||
"message": "",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"ok": true,
|
||||
"reason": "",
|
||||
"total_cost": 120,
|
||||
"cash_after": 350,
|
||||
"quantity_after": 40,
|
||||
"max_quantity": 100,
|
||||
"warehouse_id": "facility_00000004",
|
||||
"item_id": "ingredient/raw_food",
|
||||
"quantity": 40,
|
||||
"transaction_id": "ledger_00000060"
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
}
|
||||
],
|
||||
"B": [
|
||||
{
|
||||
"object_sha256": "bcb27db495ae9e94a20578e1aadcb64e0fa316bb1d4a6e954e581f4bdb34b296",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_akane"
|
||||
},
|
||||
"result": {
|
||||
"code": "VALIDATION_FAILED",
|
||||
"command_id": 25,
|
||||
"target_id": "",
|
||||
"message": "An account does not permit a negative balance.",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"account_id": "PLAYER_CASH",
|
||||
"available": 0,
|
||||
"resulting_balance": -180
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "81f9484c3fcce64e084a7e2ead8bf84dd8c56e144860624845eccb8c4218417a",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_gina"
|
||||
},
|
||||
"result": {
|
||||
"code": "VALIDATION_FAILED",
|
||||
"command_id": 27,
|
||||
"target_id": "",
|
||||
"message": "An account does not permit a negative balance.",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"account_id": "PLAYER_CASH",
|
||||
"available": 0,
|
||||
"resulting_balance": -150
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "16f4a094baa4204d4e254b6d52d824f32aa33e914e6bc1698aa933a8fa51207a",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_chika"
|
||||
},
|
||||
"result": {
|
||||
"code": "VALIDATION_FAILED",
|
||||
"command_id": 29,
|
||||
"target_id": "",
|
||||
"message": "An account does not permit a negative balance.",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"account_id": "PLAYER_CASH",
|
||||
"available": 0,
|
||||
"resulting_balance": -250
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "303e545649bf1c84c736293a6e1b54491befbc376b9a051d3f3c75659e2b147e",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_iris"
|
||||
},
|
||||
"result": {
|
||||
"code": "VALIDATION_FAILED",
|
||||
"command_id": 31,
|
||||
"target_id": "",
|
||||
"message": "An account does not permit a negative balance.",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"account_id": "PLAYER_CASH",
|
||||
"available": 0,
|
||||
"resulting_balance": -210
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "de1717d64250c9ae721fc38230d185b0621a066e01890f9fb4020b4c7e36e7c0",
|
||||
"decoded": {
|
||||
"command": "hire_staff_candidate",
|
||||
"payload": {
|
||||
"candidate_id": "staff_juno"
|
||||
},
|
||||
"result": {
|
||||
"code": "VALIDATION_FAILED",
|
||||
"command_id": 33,
|
||||
"target_id": "",
|
||||
"message": "An account does not permit a negative balance.",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"account_id": "PLAYER_CASH",
|
||||
"available": 0,
|
||||
"resulting_balance": -240
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_sha256": "c257c14418959e558b88d4f47591c14726fff4763a30177275d8725cb6639b9f",
|
||||
"decoded": {
|
||||
"command": "purchase_supplies",
|
||||
"payload": {
|
||||
"warehouse_id": "facility_00000004",
|
||||
"item_id": "ingredient/raw_food",
|
||||
"quantity": 40
|
||||
},
|
||||
"result": {
|
||||
"code": "VALIDATION_FAILED",
|
||||
"command_id": 35,
|
||||
"target_id": "",
|
||||
"message": "Supply command rejected: INSUFFICIENT_FUNDS",
|
||||
"resolution_hints": [],
|
||||
"details": {
|
||||
"ok": false,
|
||||
"reason": "INSUFFICIENT_FUNDS",
|
||||
"total_cost": 120,
|
||||
"cash_after": -120,
|
||||
"quantity_after": 40,
|
||||
"max_quantity": 0
|
||||
},
|
||||
"is_replay": false
|
||||
},
|
||||
"minute": 910
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"next": "Actual Opus diagnosis and narrow same-task repair before full regression"
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
# P23-03 worker status — static-complete submission
|
||||
|
||||
Contract base: `860136f7099f5e3aee99da5798a627a77f2d8eea`.
|
||||
This finalization preserves every pre-existing modified and untracked file.
|
||||
Static-complete is supported by the wiring inspection and local checks below.
|
||||
No acceptance claim is made. No Godot, native, OS collector, Runner, full
|
||||
regression, git write, branch change, or other worker was used.
|
||||
|
||||
## Current wiring confirmed by git diff and rg only
|
||||
|
||||
Line numbers refer to the current candidate.
|
||||
|
||||
- Checker `tools/check_p23_life_observation_evidence.py`: `check` at 1189
|
||||
reaches `inspect_native_phases` (1162) at 1215 and `inspect_stream` (950)
|
||||
at 1220/1226. The stream inspection invokes `compare_rest` (531) at 968,
|
||||
`compare_motion` (738) at 974, and `compare_actor_geometry` (800) at 975,
|
||||
alongside actor, room, queue, traffic, goods, badge and cabin checks.
|
||||
- `check` reaches `inspect_os_receipt` (1038), `inspect_os_requests` (1092),
|
||||
and `inspect_resume` (1070) at 1249, 1250 and 1257.
|
||||
Request inspection calls `screenshot_pixels` (184) at 1143, which invokes
|
||||
`inspect_conversion` (158) at 191 for JPEG evidence; rectangle/pixel
|
||||
checking follows at 1154.
|
||||
- `check` builds executed check sets at 1270–1272, then calls `BLOCKERS`
|
||||
at 1273, reaching `condition_matrix` (982) through 1000. All original
|
||||
seven CONDITIONS and six CATEGORIES (room, queue, EV, staff, goods,
|
||||
business state, represented by the key `business`) remain required.
|
||||
Missing executed check names produce pending/deficits; no fixed-true matrix.
|
||||
- Collector `tools/run_p23_life_observation.py`: `collect_os` (110)
|
||||
invokes `collect_screenshots` (26) at 186. JPEG collection invokes
|
||||
`convert_screenshot` (73) at 55; conversion calls `inspect_conversion`
|
||||
at 107. Raw CUA bytes and tool-call bindings are retained separately from
|
||||
converted PNG and conversion receipts. This wiring was inspected, not run.
|
||||
- `diagnostic_stream.gd` captures hash-addressed `rest_audit` and `motion`
|
||||
at 70–78. `rest_audit` (89) records selection inputs, route/cost probes,
|
||||
locks/transit and comparison-only Runtime output. No stored-rest field was
|
||||
introduced. The source-grounded correction supersedes the old stored-state
|
||||
proposal below. MotionObserver retains two draw frames, not whole Runtime
|
||||
state per frame.
|
||||
- Native route: `tests/ui/test_p23_life_observation.gd` at 18–21 invokes
|
||||
`layout_comparison.gd`; the runner invokes that UI test at 309 for A/B.
|
||||
Layout comparison preloads diagnostics at 5 and starts watching at 65.
|
||||
- Normal OS route is separate from the native UI component test: runner
|
||||
RECORDER at 24 is copied/launched at 145/152; `passive_os_recorder.gd`
|
||||
preloads `passive_observation.gd` at 2 and calls its inherited run at 6.
|
||||
`passive_observation.gd` extends `observation.gd` at 1;
|
||||
`observation.gd` preloads diagnostics/layout comparison at 3–4 and
|
||||
watches the real preview at 26. These fixtures are reached through the
|
||||
OS runner, not directly preloaded by the native UI test.
|
||||
- Existing mutation negatives cover conversion provenance, rest operands,
|
||||
motion history, rectangle/pixel transforms, process/source/profile guards,
|
||||
formal members/distinct PID/ACK, and every condition/category matrix cell.
|
||||
No concrete missing call or negative was found; no code hardening was added.
|
||||
|
||||
## Exact local commands and real results
|
||||
|
||||
- `git status --short`, `git diff --stat`, and targeted `git diff --`
|
||||
for the collector, UI test, diagnostic fixture and marker: inspected existing
|
||||
changes without modifying git state.
|
||||
- Targeted `rg -n` / `rg -n -A...` searches for function definitions,
|
||||
calls, fixture preloads/extends, rest_audit/motion, CONDITIONS/CATEGORIES
|
||||
and unittest methods in the three P23 tools and five P23 fixtures/UI test:
|
||||
wiring results recorded above. No Runtime/source audit or Phase0 repetition.
|
||||
- `python3 -m py_compile tools/run_p23_life_observation.py tools/check_p23_life_observation_evidence.py tools/test_p23_life_observation_evidence.py`:
|
||||
initial attempt failed because the interpreter's default cache destination
|
||||
under Library/Caches was not writable; no syntax failure was reported.
|
||||
- `PYTHONPYCACHEPREFIX=docs/plans/original-completion-2026-10-02/execution/p23-03/.finalization-pycache python3 -m py_compile tools/run_p23_life_observation.py tools/check_p23_life_observation_evidence.py tools/test_p23_life_observation_evidence.py`:
|
||||
PASS, exit 0 for all three tools.
|
||||
- `python3 -B tools/test_p23_life_observation_evidence.py`:
|
||||
**27 test methods run, 27 passed**, 0 failures/errors; reported 0.044 seconds.
|
||||
These are synthetic checker tests, not native/OS evidence.
|
||||
- `git diff --check`: PASS, exit 0; repeated after this status rewrite.
|
||||
- Read-only `python3 -B -c` JSON/hash/path assertions using
|
||||
`git show 860136f7099f5e3aee99da5798a627a77f2d8eea:<path>`,
|
||||
`git diff --name-only <base>` and
|
||||
`git ls-files --others --exclude-standard`: PASS.
|
||||
All old marker metadata/rows equal base; exactly one appended P23-03
|
||||
required source-authority entry; test SHA
|
||||
`cc7049e1c3919585caeefd1747be12fbf3fa52141fcdb980929c8a16fd7e2ee1`
|
||||
matches current bytes. suite-policy.json equals base byte-for-byte.
|
||||
All 16 changed/untracked paths relative to base are allowed.
|
||||
- The immutable spec SHA matches
|
||||
`8568457df849ef587c731f072d77244ef5420fc4e1190c5069cfd6cfeea9c89f`
|
||||
using the existing checker's `read_text().strip().encode()` convention.
|
||||
An initial extra raw-byte SHA assertion failed (raw file SHA
|
||||
`1a925e0d7f5c1c9f28670d29dd0fdc5aa48f4d5415946d6de4c15e1d83b5ed66`);
|
||||
normalized verification passed and the spec has no diff against base.
|
||||
- An initial inline validation here-document could not create its shell
|
||||
temporary file; rerun via `python3 -B -c` succeeded.
|
||||
- Removed only the temporary .finalization-pycache tree created by this run.
|
||||
No pre-existing dirty/untracked material was removed.
|
||||
|
||||
## Root work still pending — expected, not worker blockers
|
||||
|
||||
Native Godot execution; actual OS collector with screenshots for both layouts
|
||||
and all six categories, FRONT/BACK and ordinary pan/zoom; formal save and
|
||||
distinct actual PID normal resume; one full exact-candidate **177-suite**
|
||||
regression; independent Opus all-condition review. These must precede any
|
||||
acceptance determination. Constructibility, visual correctness and same-demand
|
||||
improvement have not been claimed from static checks.
|
||||
|
||||
## Superseded history — retained verbatim
|
||||
|
||||
The following earlier blocked statuses (including the 19-method and
|
||||
"not yet wired" claims) describe previous candidates and are superseded by the
|
||||
current wiring and results above.
|
||||
|
||||
# Current RUN continuation — blocked on stored-rest-destination contract
|
||||
|
||||
All seven pre-existing modified files and three untracked fixtures were retained.
|
||||
No git writes, agents, Godot, collector execution, Runner or full regression.
|
||||
This is not a genuine completed submission and makes no acceptance claim.
|
||||
Root native/OS evidence remains **pending**, independently of this source blocker.
|
||||
|
||||
Changes completed in this continuation:
|
||||
- Removed mandatory left-button drag and its pan-change assertion. Camera
|
||||
screenshots now use ordinary-wheel-zoom, pan-mode-button, move-mode-wheel-pan.
|
||||
Existing non-Ctrl event observation is preserved.
|
||||
- Added stdlib PNG decoding with CRC/chunk/end checks, RGB/RGBA and filters 0–4.
|
||||
JPEG dimensions reuse the P08 helper; no JPEG decoder or PIL was added.
|
||||
- Added conversion receipt inspection and Root-only convert_screenshot helper:
|
||||
separate output, original CUA hash/call binding, sips executable SHA/argv,
|
||||
output digests, decoded PNG and magic-derived dimension equality. No actual
|
||||
sips conversion was run here. This helper is not yet wired into collect_os.
|
||||
- Added four image test methods including all filters, CRC corruption, trailing
|
||||
data, converter SHA/argv, raw CUA hash, dimensions and missing-conversion negatives.
|
||||
|
||||
Actually executed: 19 checker unittest methods passed; py_compile passed for
|
||||
the three P23 Python tools. Temporary compilation outputs were removed.
|
||||
Synthetic image tests are checker tests, never native screenshot evidence.
|
||||
|
||||
Concrete contract gap (targeted dependency inspection, not a repeated Phase0 audit):
|
||||
- autoload/simulation_runtime.gd:13109–13125 computes get_staff_rest_report by
|
||||
scanning facilities and selecting a minimum-cost route on every call. The
|
||||
selected facility and eligibility are local variables, not stored state.
|
||||
- autoload/simulation_runtime.gd:8783–8785 calls that query and passes only its
|
||||
cell to advance_staff_commute. At :12656–12690 the movement function either
|
||||
returns without movement or emits a STAFF_MOVED event with a target_cell;
|
||||
it does not persist a selected rest facility ID or eligibility. Movement
|
||||
events cannot supply a current destination for stationary/nonmoving staff.
|
||||
- scripts/staff/staff_runtime.gd:282–315 serializes current_cell, assignment,
|
||||
task and path_handle but no rest destination/eligibility. Runtime snapshot
|
||||
assembly at :511–570 has no stored rest-selection model. PathService's
|
||||
snapshot at scripts/world/path_service.gd:1606 stores path/actor projections,
|
||||
not the selected rest facility and its eligibility.
|
||||
|
||||
Thus recipe (3), "rest destination must equal the typed stored destination and
|
||||
its eligibility; do not re-simulate AI", has no authoritative stored operand.
|
||||
Calling get_staff_rest_report from a fixture would rerun selection, and copying
|
||||
its output into a purported stored-state field would manufacture authority.
|
||||
These Runtime/staff/path files are outside allowed_paths and remain unchanged.
|
||||
|
||||
Read source SHA256:
|
||||
- Runtime: 4696df4a77feb465a5a3ba8b806569f7bcbe1d03c88289acfb78a3928f307d67
|
||||
- StaffRuntime: 807a417bc7866c97cd6d8f5c1368b5480a2d9fd685a2a47a978e81482bf522b5
|
||||
- PathService: 447135729a1106639a6eee6754af3195c8a8b458ca26980ae91fa39d80dcfbd4
|
||||
|
||||
Minimal separate proposal for Root/Opus, not applied: authorize an authoritative
|
||||
rest-decision record at the existing Runtime selection point, including selected
|
||||
facility/cell, eligibility and decision time, with explicit invalidation and
|
||||
snapshot/restore semantics. Alternatively Root must identify an existing typed
|
||||
stored field that has those semantics. No worker relaxation of this gate.
|
||||
|
||||
Remaining source implementation is explicit: collector conversion integration,
|
||||
all row/rectangle bindings, motion history, distinct-PID/formal ACK checker,
|
||||
dynamic matrix and the remaining requested negatives. Existing fail-closed
|
||||
whole-unit behavior remains; no unconditional PASS or new fail stub was added.
|
||||
The 30/88 denominator, seven conditions, six categories and old suite policies
|
||||
are unchanged. Do not begin acceptance assuming only Root evidence is missing.
|
||||
|
||||
---
|
||||
Previous status retained as history:
|
||||
|
||||
# Repair from source 03100be — blocked, implementation incomplete
|
||||
|
||||
Source checkout HEAD confirmed read-only: `03100beabeb55caac03f53db3d1f399b424df517`.
|
||||
Contract base remains `860136f7099f5e3aee99da5798a627a77f2d8eea`.
|
||||
No git writes, Godot, Runner, OS input, regression, or other workers were used.
|
||||
No acceptance or partial credit is claimed. No out-of-scope defect or missing Runtime capability was established.
|
||||
|
||||
Changes in this repair:
|
||||
- Added registered `--verify-only` CLI, concrete missing-index deficits, immutable fixture/spec/contract/baseline/AGENTS pins and a normative P23 plan-section hash. Full-plan SHA is separate provenance only. Existing diagnostic/layout CLI modes retained.
|
||||
- Removed fixture-side begin_leaving/despawn_guest calls; Runtime owns continued guest behavior. Added A/B native entry to the existing component test and updated the existing single marker row SHA.
|
||||
- Added observation.gd extending P08 placement helpers unchanged, with real supply scenario commands/settings, common definition-derived construction budget, seed806042, relocated common cafeteria/kitchen at x6 to avoid declared B elevator footprint, sparse typed captures, normal save/load and whole formal-model comparisons. These construction prechecks have not been executed.
|
||||
- Added passive request/ACK endpoint and recorder for four layout/role combinations, live FRONT/BACK buttons and PanModeButton, ordinary non-Ctrl wheel zoom/pan and ordinary pan drag only. No deferred P23-02 held-gesture credit.
|
||||
- Replaced the collector stub with an adaptation of P08 collect/collect_os using run_all_tests helpers and owned gates, fresh outputs/profiles, native import/parse/layout phases, four OS roles and formal reader copies. Records the current interpreter PIL availability; raw screenshot bytes are left untouched. Collector has not been executed.
|
||||
- Checker now inspects available native/OS streams, comparisons and basic process/source/profile guards, but explicitly fails closed for outstanding whole-unit gates.
|
||||
|
||||
Local verification actually executed: 16 Python unittest cases PASS; ast.parse for the three changed Python tools PASS; one P23 marker row and exact source SHA PASS. No engine or evidence-collector execution.
|
||||
|
||||
Remaining IMPLEMENTATION, separate from missing Root evidence:
|
||||
1. Complete screenshot byte-magic dimensions, stdlib PNG pixel/rect checks, and indexed Root JPEG-to-lossless-PNG conversion binding to raw hash/converter provenance. PIL absence is not waived.
|
||||
2. Add prior-frame visual-position/frame-delta evidence and independent interpolation checks; compare rest destinations with typed stored destination/eligibility without re-simulating AI.
|
||||
3. Bind all request report rows to viewport rectangles/affine and screenshots; current endpoint retains report plus viewport-to-screen transform but does not yet produce every required viewport row rectangle.
|
||||
4. Finish independent distinct-PID/formal-member/resume-state/load-ACK gates. Fixture assertions do not replace checker proof.
|
||||
5. Replace hard failure condition-matrix deficits with exact completed check names only after all seven independent gates exist; finish all requested category and infrastructure negatives. Current check intentionally cannot pass whole-unit integrity.
|
||||
6. Statically review collector lifecycle/failure receipts and fixture category coverage; real constructibility and EV/passenger/queue/goods coverage remain unproved.
|
||||
|
||||
Root native, actual OS for A/B, formal distinct-PID resume, exact-final full regression, independent review, integration/final/ledger: all PENDING. This candidate is blocked on remaining in-scope implementation, not ready for acceptance or a claim that missing Root evidence is the only gap.
|
||||
|
||||
---
|
||||
Previous worker status retained verbatim as history (superseded by the repair status above):
|
||||
|
||||
# P23-03 worker status: submitted static work, implementation incomplete
|
||||
|
||||
Base commit: `860136f7099f5e3aee99da5798a627a77f2d8eea`.
|
||||
This submission preserves the previous dirty work and adds genuine static
|
||||
implementation. It is **not acceptance-ready**. No external Runtime capability
|
||||
gap or out-of-scope defect has been established. Remaining implementation is
|
||||
listed separately from pending Root evidence; no acceptance credit is claimed.
|
||||
|
||||
## Inspection and changes in this repair
|
||||
|
||||
- Re-read the recorded direction, Phase 0 map, previous status and renderer diff.
|
||||
The single frame builder supplies drawing and the deep-copy public report.
|
||||
No renderer redesign or additional renderer was introduced. Existing renderer
|
||||
and component test bytes were retained.
|
||||
- Added `tests/fixtures/p23-03/layout_comparison.gd`. Both layouts initialize the
|
||||
public Runtime with seed 806042, reset budget and clock, identical production
|
||||
defaults and the same recorded 48-person arrival script. Commands build the
|
||||
existing elevator and corridors; B adds only the declared second elevator.
|
||||
Preparation time derives from registered definition build times.
|
||||
- Predeclared metric: sum of destination-arrival minutes for the entire cohort.
|
||||
The checker rejects incomplete cohorts, changed actual arrival payloads,
|
||||
additional layout changes, changed initial state, and non-strict improvement.
|
||||
No observed improvement is claimed: the fixture has not run.
|
||||
- Extended diagnostic_stream with hash-addressed typed event/input records.
|
||||
The fixture records actual command results, domain movement events and
|
||||
per-minute guest/path/EV/facility projections, including service history,
|
||||
queues and density. Whole aggregate state/report pairs are sparse (30-minute
|
||||
intervals); the existing P08 typed encoder is reused without truncation.
|
||||
- Extended independent checking of room service/rescue occupants and business
|
||||
enum fields, queue allocation/overflow, shared transforms, traffic, logistics
|
||||
report selection/order and cabin geometry. Added Rect2 and vector-key decode.
|
||||
The checker still refuses whole-unit acceptance.
|
||||
|
||||
## Exact remaining implementation (not external blockers)
|
||||
|
||||
1. Finish rest destination selection from typed path costs and event/defense
|
||||
locks. Current room check validates eligibility and reported rest references;
|
||||
it does not independently prove which rest destination Runtime selected.
|
||||
2. Finish every actor/passenger mode and geometry, including cleaning,
|
||||
rest/rescue/service slots, visual track history, badge text and screenshot
|
||||
rect/pixel provenance. Cabin endpoints and rectangles are checked, but
|
||||
unpaused prior-frame interpolation origin is not independently established.
|
||||
3. Complete A/B definition/source/production pins and command/event/path
|
||||
cross-validation, invoke the fixture from the Root collector, and add
|
||||
representative real operational scenarios for all six categories and BACK.
|
||||
The added experiment covers the fixed-demand FRONT transport comparison;
|
||||
it does not establish actual service purchasing or all-six-category coverage.
|
||||
4. Replace run_p23_life_observation.py readiness stub with the actual Root
|
||||
collector using P08 owned-process/profile/source/engine/ordinary-audio and
|
||||
CUA request/ACK helpers. It remains unchanged; it launches nothing.
|
||||
Formal save, distinct-PID normal resume, both-layout FRONT/BACK/pan/zoom,
|
||||
complete CUA receipts, all-run cleanup/protection and failure history remain
|
||||
to be wired and independently checked.
|
||||
5. Add missing meaningful negatives, especially normal OS/distinct-PID resume,
|
||||
visual provenance and the remaining rest/motion branches. Existing 12 local
|
||||
tests are not a complete seven-condition test matrix.
|
||||
|
||||
Steps (2) and (3) of the repair direction are therefore still incomplete;
|
||||
step (4) is not implemented. This is a partial static submission, not a claim
|
||||
that the requested repair is finished. Do not begin acceptance on an assumption
|
||||
that all worker deliverables are complete.
|
||||
|
||||
## Checks actually run in this repair
|
||||
|
||||
- `python3 -B tools/test_p23_life_observation_evidence.py`: 12 test methods,
|
||||
zero failures. Synthetic Python examples validate rejection logic only.
|
||||
- AST syntax: all three P23 Python tools parsed.
|
||||
- Marker JSON: old rows and metadata unchanged; exactly one appended row;
|
||||
its SHA matches the unchanged GDScript component test.
|
||||
- `git diff --check`: passed.
|
||||
- No Godot parsing/runtime, native, OS, Runner or full regression execution.
|
||||
- No git writes, subagents, integration, new assets or production changes.
|
||||
suite-policy.json was not changed; no new suite was registered.
|
||||
|
||||
## Source SHA256 at local checks
|
||||
|
||||
| File | SHA256 |
|
||||
| --- | --- |
|
||||
| scenes/main/world_preview.gd | add770377c190a5ccdde95379539de9753f7dabc703c1d58452ba590c6eda2f0 |
|
||||
| scripts/ui/room_presentation.gd | 55340b7b623532e861be1d21225635fd9169e2e40928597c1cd0458b99524dd9 |
|
||||
| tests/ui/test_p23_life_observation.gd | b9afa66ec73d3a5cadf86f9a71f0d13f5b085c3cc9a534dfe29b2bed0e2e76a0 |
|
||||
| tests/fixtures/p23-03/diagnostic_stream.gd | ff178a094fc310b0640d923a15058c6f6fcf859551c14f6428a5709dcd24260e |
|
||||
| tests/fixtures/p23-03/layout_comparison.gd | ddf00268b8e32e3c5c078ebb57ada321382ecc7c5fbd87efad7e9dfbe768df6b |
|
||||
| tools/run_p23_life_observation.py | 53c2f0638d4a861cf4fcb664504c25df33ae5478207867654f1c8cdf58fa23c7 |
|
||||
| tools/check_p23_life_observation_evidence.py | 318b253a08248a753b88902e382edc5f01cbce6f1ed8092359698b69d3bb0503 |
|
||||
| tools/test_p23_life_observation_evidence.py | 0f20d3de20b44f46f8738c0d022fcc1179656014e85fe31754f00b87ec2c288a |
|
||||
|
||||
## Root evidence: pending
|
||||
|
||||
After genuine completion and freezing: both-layout native and actual normal OS
|
||||
observation, full CUA trace, formal save and distinct-PID resume, mandatory exact
|
||||
version full regression and independent Opus review. These absent Root results
|
||||
are **pending**, not blocked. The fixed 30-package / 88-unit denominator and all
|
||||
P08/P22/P25/Windows/held-gesture/performance exclusions remain unchanged.
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
@@ -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
|
||||
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -78,6 +78,9 @@ var _queue_positions: Dictionary = {}
|
||||
var _queue_hidden: Dictionary = {}
|
||||
var _landing_badges: Array[Dictionary] = []
|
||||
var _transport_report: Dictionary = {}
|
||||
var _life_observation: Dictionary = {}
|
||||
var _life_overlay: Node2D
|
||||
var _life_observation_serial := 0
|
||||
var _dragging := false
|
||||
var _drag_button := MOUSE_BUTTON_NONE
|
||||
|
||||
@@ -103,6 +106,10 @@ func _ready() -> void:
|
||||
_inactive_layer_art = InactiveLayerRooms.new()
|
||||
_inactive_layer_art.name = "InactiveLayerRooms"
|
||||
add_child(_inactive_layer_art)
|
||||
_life_overlay = Node2D.new()
|
||||
_life_overlay.name = "LifeOverlay"
|
||||
_life_overlay.draw.connect(_draw_life_overlay)
|
||||
add_child(_life_overlay)
|
||||
_layer_lens = ColorRect.new()
|
||||
_layer_lens.name = "LayerLens"
|
||||
_layer_lens.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -511,6 +518,9 @@ func _draw() -> void:
|
||||
|
||||
|
||||
func _draw_contents() -> void:
|
||||
_life_observation = {}
|
||||
if _life_overlay != null:
|
||||
_life_overlay.queue_redraw()
|
||||
if _inactive_layer_art != null:
|
||||
_inactive_layer_art.visible = false
|
||||
_draw_background()
|
||||
@@ -521,18 +531,27 @@ func _draw_contents() -> void:
|
||||
var map_rect := get_map_rect()
|
||||
if map_rect.size == Vector2.ZERO:
|
||||
return
|
||||
_life_observation = _build_life_observation()
|
||||
_life_observation_serial += 1
|
||||
_life_observation[&"observation_serial"] = _life_observation_serial
|
||||
_life_observation[&"overlay_consumed_serial"] = -1
|
||||
_room_presentations = _life_observation[&"rooms"]
|
||||
_carried_patients = _life_observation[&"carried_patients"]
|
||||
_treated_patients = _life_observation[&"treated_patients"]
|
||||
_resting_staff = _life_observation[&"resting_staff"]
|
||||
_queue_positions = _life_observation[&"queue_positions"]
|
||||
_queue_hidden = _life_observation[&"queue_hidden"]
|
||||
_landing_badges.assign(_life_observation[&"landing_badges"])
|
||||
_transport_report = _life_observation[&"elevators"]
|
||||
_boarded_actor_ids = _life_observation[&"boarded"]
|
||||
_actor_tracks = _life_observation[&"actor_tracks"]
|
||||
_elevator_tracks = _life_observation[&"elevator_tracks"]
|
||||
_draw_world_cells(bounds, map_rect)
|
||||
if show_grid:
|
||||
_draw_congestion_overlay(bounds)
|
||||
_prepare_room_presentations()
|
||||
_prepare_queues()
|
||||
_draw_world_entity_icons()
|
||||
_draw_legacy_stairs()
|
||||
_draw_elevators()
|
||||
_draw_other_layer()
|
||||
_draw_construction_labels()
|
||||
_draw_facility_labels()
|
||||
_draw_actors()
|
||||
_draw_excavation_jobs()
|
||||
_draw_build_preview()
|
||||
_draw_excavation_preview()
|
||||
@@ -540,6 +559,177 @@ func _draw_contents() -> void:
|
||||
_draw_map_caption(map_rect)
|
||||
|
||||
|
||||
## The report describes the last submitted draw, not an independently sampled world.
|
||||
## Callers cannot mutate either the frame or its interpolation tracks.
|
||||
func get_life_observation_report() -> Dictionary:
|
||||
return _life_observation.duplicate(true)
|
||||
|
||||
|
||||
## Pure with respect to Runtime and this Control: helpers mutate only fresh local
|
||||
## dictionaries (including copies of visual tracks). No whole Runtime snapshot
|
||||
## is copied per frame; whole typed snapshots belong to sparse diagnostics.
|
||||
func _build_life_observation() -> Dictionary:
|
||||
var model := _observe_rooms()
|
||||
model.merge(_observe_queues())
|
||||
model[&"layer"] = layer_mode
|
||||
model[&"zoom"] = zoom_level
|
||||
model[&"pan"] = pan_offset
|
||||
model[&"viewport"] = size
|
||||
model[&"animation_time"] = _animation_time
|
||||
model[&"actor_tracks"] = _actor_tracks.duplicate(true)
|
||||
model[&"elevator_tracks"] = _elevator_tracks.duplicate(true)
|
||||
model[&"boarded"] = {}
|
||||
var clock := get_node_or_null("/root/GameClock")
|
||||
model[&"paused"] = clock != null and bool(clock.get("paused"))
|
||||
model[&"cabins"] = _observe_elevators(model)
|
||||
model[&"actor_drawings"] = _observe_actors(model)
|
||||
var guests: Array[Dictionary] = []
|
||||
var staff: Array[Dictionary] = []
|
||||
var tasks: Array[Dictionary] = []
|
||||
var guest_service: Variant = _service(&"guest_service")
|
||||
if guest_service != null and guest_service.has_method(&"get_guests"):
|
||||
for guest: GuestRuntime in guest_service.get_guests():
|
||||
guests.append({&"id": guest.guest_id, &"state": GuestRuntime.State.keys()[guest.state],
|
||||
&"cell": guest.current_cell, &"target": guest.target_facility_id,
|
||||
&"service": guest.current_service_id, &"queue": guest.queue_request_id})
|
||||
var staff_service: Variant = _service(&"staff_service")
|
||||
if staff_service != null:
|
||||
for member: StaffRuntime in staff_service.get_staff_members():
|
||||
staff.append({&"id": member.staff_id, &"state": StaffRuntime.State.keys()[member.state],
|
||||
&"cell": member.current_cell, &"task": member.current_task_id,
|
||||
&"facility": member.assigned_facility_id, &"role": member.role_id,
|
||||
&"carried_inventory_id": member.carried_inventory_id})
|
||||
for task: StaffTask in staff_service.get_tasks():
|
||||
tasks.append({&"id": task.task_id, &"state": StaffTask.State.keys()[task.state],
|
||||
&"type": task.task_type, &"staff": task.assigned_staff_id,
|
||||
&"target": task.target_entity_id, &"rescue_facility": task.rescue_facility_id})
|
||||
model[&"guests"] = guests
|
||||
model[&"staff"] = staff
|
||||
model[&"tasks"] = tasks
|
||||
_fit_life_actors(model)
|
||||
var bridge: Variant = _service(&"supply_logistics")
|
||||
var goods: Dictionary = bridge.get_report() if bridge != null else {&"requests": [], &"deliveries": []}
|
||||
model[&"goods"] = goods
|
||||
var badges: Array[Dictionary] = []
|
||||
var facilities: Variant = _service(&"facility_service")
|
||||
var world: Variant = _service(&"world_service")
|
||||
if facilities != null and world != null:
|
||||
for instance: FacilityInstance in facilities.get_instances():
|
||||
if instance.origin.z != layer_mode:
|
||||
continue
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(instance.instance_id)
|
||||
if cells.is_empty():
|
||||
continue
|
||||
var rect := _cell_bounds_rect(cells)
|
||||
var data: Dictionary = model[&"rooms"][instance.instance_id]
|
||||
data[&"rect"] = rect
|
||||
data[&"definition_id"] = instance.definition_id
|
||||
var count := RoomPresentation.slots(rect, _cell_dimensions().x / 16.0, instance.definition_id).size()
|
||||
badges.append({&"facility": instance.instance_id, &"kind": &"business",
|
||||
&"at": rect.position + Vector2(2, 2),
|
||||
&"text": RoomPresentation.business_label(data) + " " + RoomPresentation.label(data, count)})
|
||||
var requests: Array[Dictionary] = []
|
||||
for request: Dictionary in goods.get(&"requests", []):
|
||||
if request[&"source_facility_id"] == instance.instance_id or request[&"destination_facility_id"] == instance.instance_id:
|
||||
requests.append(request)
|
||||
if not requests.is_empty():
|
||||
badges.append({&"facility": instance.instance_id, &"kind": &"supply",
|
||||
&"at": rect.position + Vector2(2, 16), &"requests": requests,
|
||||
&"text": RoomPresentation.supply_label(requests)})
|
||||
model[&"badges"] = badges
|
||||
_resolve_observation_labels(model)
|
||||
var obstacles: Array[Rect2] = []
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
obstacles.append(cabin[&"rect"])
|
||||
obstacles.append(cabin[&"badge_rect"])
|
||||
for collection: Array in [badges, model[&"landing_badges"], model.get(&"actor_aggregations", [])]:
|
||||
for badge: Dictionary in collection:
|
||||
obstacles.append(_readable_badge_rect(badge[&"at"], badge[&"text"]))
|
||||
model[&"traffic_obstacles"] = obstacles
|
||||
var traffic: Array[Dictionary] = []
|
||||
var path: Variant = _service(&"path_service")
|
||||
var bounds := _world_bounds()
|
||||
if path != null and not bounds.is_empty():
|
||||
for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1):
|
||||
for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1):
|
||||
var cell := Vector3i(x, y, layer_mode)
|
||||
var row: Dictionary = path.get_cell_traffic(cell)
|
||||
if not row.is_empty():
|
||||
var cell_rect := get_cell_rect(cell)
|
||||
var rect := cell_rect.grow(-2.0)
|
||||
var world_pixel := _cell_dimensions().x / 16.0
|
||||
var thickness := clampf(rect.size.y * 0.16, 2.0, 5.0)
|
||||
var band := Rect2(Vector2(rect.position.x + rect.size.x * 0.12,
|
||||
cell_rect.end.y - 2 * world_pixel - thickness * 0.5),
|
||||
Vector2(rect.size.x * 0.76, thickness))
|
||||
traffic.append({&"cell": cell, &"traffic": row, &"rect": rect, &"band_rect": band,
|
||||
&"segment_rects": _traffic_segments(band, rect, obstacles)})
|
||||
model[&"traffic"] = traffic
|
||||
return model
|
||||
|
||||
|
||||
## Subtract opaque observation regions, retaining a visible density mark even
|
||||
## when the ordinary floor band is completely behind a moving cabin.
|
||||
func _traffic_segments(band: Rect2, cell: Rect2, obstacles: Array[Rect2]) -> Array[Rect2]:
|
||||
for y: float in [band.position.y, cell.position.y, cell.end.y - band.size.y]:
|
||||
var pieces: Array[Rect2] = [Rect2(Vector2(band.position.x, y), band.size)]
|
||||
for obstacle: Rect2 in obstacles:
|
||||
var remaining: Array[Rect2] = []
|
||||
for piece: Rect2 in pieces:
|
||||
var cut := piece.intersection(obstacle)
|
||||
if not cut.has_area():
|
||||
remaining.append(piece)
|
||||
continue
|
||||
for part: Rect2 in [Rect2(piece.position, Vector2(piece.size.x, cut.position.y-piece.position.y)),
|
||||
Rect2(Vector2(piece.position.x, cut.end.y), Vector2(piece.size.x, piece.end.y-cut.end.y)),
|
||||
Rect2(Vector2(piece.position.x, cut.position.y), Vector2(cut.position.x-piece.position.x, cut.size.y)),
|
||||
Rect2(Vector2(cut.end.x, cut.position.y), Vector2(piece.end.x-cut.end.x, cut.size.y))]:
|
||||
if part.has_area():
|
||||
remaining.append(part)
|
||||
pieces = remaining
|
||||
if not pieces.is_empty():
|
||||
return pieces
|
||||
return []
|
||||
|
||||
|
||||
func _resolve_observation_labels(model: Dictionary) -> void:
|
||||
var placed: Array[Dictionary] = []
|
||||
for i: int in model[&"cabins"].size():
|
||||
var cabin: Dictionary = model[&"cabins"][i]
|
||||
placed.append({&"id": "cabin/%d/rect" % i, &"rect": cabin[&"rect"]})
|
||||
placed.append({&"id": "cabin/%d/badge" % i, &"rect": cabin[&"badge_rect"]})
|
||||
for kind: String in ["business", "supply", "landing", "aggregation"]:
|
||||
var collection: Array = model[&"badges"] if kind in ["business", "supply"] else model[&"landing_badges"] if kind == "landing" else model.get(&"actor_aggregations", [])
|
||||
for i: int in collection.size():
|
||||
var badge: Dictionary = collection[i]
|
||||
if kind in ["business", "supply"] and badge[&"kind"] != kind:
|
||||
continue
|
||||
var anchor: Vector2 = badge[&"at"]
|
||||
var at := anchor
|
||||
var displaced_by: Array[String] = []
|
||||
# Each collision advances to an existing right edge. Positive widths
|
||||
# guarantee termination; touching edges are allowed.
|
||||
while true:
|
||||
var blocker: Dictionary = {}
|
||||
for previous: Dictionary in placed:
|
||||
if _readable_badge_rect(at, badge[&"text"]).intersection(previous[&"rect"]).has_area():
|
||||
blocker = previous
|
||||
break
|
||||
if blocker.is_empty():
|
||||
break
|
||||
displaced_by.append(blocker[&"id"])
|
||||
at.x = (blocker[&"rect"] as Rect2).end.x
|
||||
badge[&"anchor"] = anchor
|
||||
badge[&"at"] = at
|
||||
badge[&"displaced_by"] = displaced_by.duplicate(true)
|
||||
placed.append({&"id": "%s/%d" % [kind, i], &"rect": _readable_badge_rect(at, badge[&"text"])})
|
||||
|
||||
|
||||
func _draw_life_badges(canvas: CanvasItem) -> void:
|
||||
for badge: Dictionary in _life_observation.get(&"badges", []):
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"], canvas)
|
||||
|
||||
|
||||
func _draw_background() -> void:
|
||||
var night := false
|
||||
var clock := get_node_or_null("/root/GameClock")
|
||||
@@ -650,40 +840,43 @@ func _draw_leaf_cluster(center: Vector2, p: float, variation: int) -> void:
|
||||
draw_rect(Rect2(c+offset,Vector2(3+i%2,2)*p),Color("95ad69") if i%3==0 else Color("648957"))
|
||||
|
||||
|
||||
func _draw_congestion_overlay(bounds: Dictionary) -> void:
|
||||
var path_service: Variant = _service(&"path_service")
|
||||
if path_service == null or not path_service.has_method(&"get_cell_traffic"):
|
||||
func _draw_life_overlay() -> void:
|
||||
if _life_observation.is_empty():
|
||||
return
|
||||
for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1):
|
||||
for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1):
|
||||
var cell := Vector3i(x, y, layer_mode)
|
||||
var traffic_value: Variant = path_service.call(&"get_cell_traffic", cell)
|
||||
if not traffic_value is Dictionary or traffic_value.is_empty():
|
||||
continue
|
||||
var traffic: Dictionary = traffic_value
|
||||
var density := int(traffic.get(&"current_density", 0))
|
||||
var capacity := maxi(int(traffic.get(&"base_capacity", 1)), 1)
|
||||
var blocked := bool(traffic.get(&"temporary_block", false))
|
||||
if density <= 0 and not blocked:
|
||||
continue
|
||||
var ratio := float(density) / float(capacity)
|
||||
var color := Color("55c96b")
|
||||
if blocked or ratio > 1.0:
|
||||
color = Color("e34332")
|
||||
elif ratio >= 0.65:
|
||||
color = Color("e3a62f")
|
||||
var rect := get_cell_rect(cell).grow(-2.0)
|
||||
var center := rect.get_center()
|
||||
var route_width := clampf(rect.size.y * 0.16, 2.0, 5.0)
|
||||
draw_line(
|
||||
Vector2(rect.position.x + rect.size.x * 0.12, center.y),
|
||||
Vector2(rect.end.x - rect.size.x * 0.12, center.y),
|
||||
Color(color, 0.90),
|
||||
route_width
|
||||
)
|
||||
if blocked:
|
||||
draw_line(rect.position, rect.end, color, 2.0)
|
||||
draw_line(Vector2(rect.end.x, rect.position.y), Vector2(rect.position.x, rect.end.y), color, 2.0)
|
||||
# These are actual child indices and the passes executed by this callback.
|
||||
_life_observation[&"overlay_child_indices"] = [_inactive_layer_art.get_index(), _life_overlay.get_index(), _layer_lens.get_index()]
|
||||
_life_observation[&"overlay_passes"] = []
|
||||
_life_observation[&"overlay_passes"].append(&"traffic")
|
||||
_draw_congestion_overlay(_life_overlay)
|
||||
_life_observation[&"overlay_passes"].append(&"cabins")
|
||||
_draw_elevators(_life_overlay)
|
||||
_draw_life_badges(_life_overlay)
|
||||
_life_observation[&"overlay_passes"].append(&"actors")
|
||||
for cabin: Dictionary in _life_observation.get(&"cabins", []):
|
||||
for actor: Dictionary in cabin[&"passengers"]:
|
||||
_draw_observed_actor(actor, _life_overlay)
|
||||
_draw_actors(_life_overlay)
|
||||
for badge: Dictionary in _life_observation.get(&"actor_aggregations", []):
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"], _life_overlay)
|
||||
_life_observation[&"overlay_consumed_serial"] = _life_observation[&"observation_serial"]
|
||||
|
||||
|
||||
func _draw_congestion_overlay(canvas: CanvasItem) -> void:
|
||||
for row: Dictionary in _life_observation.get(&"traffic", []):
|
||||
var traffic: Dictionary = row[&"traffic"]
|
||||
var density := int(traffic[&"current_density"])
|
||||
var blocked := bool(traffic[&"temporary_block"])
|
||||
if density <= 0 and not blocked:
|
||||
continue
|
||||
var ratio := float(density) / maxi(int(traffic[&"base_capacity"]), 1)
|
||||
var color := Color("e34332") if blocked or ratio > 1.0 else Color("e3a62f") if ratio >= 0.65 else Color("55c96b")
|
||||
# At overview zoom a whole cell can be covered by opaque badges. Still
|
||||
# submit its density band, beneath those later draws, and report no visible
|
||||
# fragments. No traffic cell or raw density row is dropped.
|
||||
if row[&"segment_rects"].is_empty():
|
||||
canvas.draw_rect(row[&"band_rect"], Color(color, 0.90))
|
||||
for segment: Rect2 in row[&"segment_rects"]:
|
||||
canvas.draw_rect(segment, Color(color, 0.90))
|
||||
|
||||
|
||||
## Visual-only level comes from the existing live FacilityInstance, not the
|
||||
@@ -794,29 +987,40 @@ func _draw_world_entity_icons() -> void:
|
||||
draw_rect(Rect2(rect.end.x-4*p,rect.position.y+2*p,2*p,2*p),_facility_color(instance_id))
|
||||
if instance_id==selected_entity_id:
|
||||
draw_rect(rect.grow(-p),Color("f5d582"),false,2*p)
|
||||
var data: Dictionary = _room_presentations.get(instance_id, {})
|
||||
if not data.is_empty() and (rect.size.x >= 80 or instance_id == selected_entity_id) and id not in [&"guest_corridor", &"service_corridor", &"guest_stairs", &"service_stairs", &"guest_elevator", &"service_elevator"]:
|
||||
var status := RoomPresentation.label(data, RoomPresentation.slots(rect, p, id).size())
|
||||
_draw_readable_badge(rect.position+Vector2(2, 2), status.replace("利用", "治療") if id == &"infirmary" else status.replace("利用", "休憩") if id in [&"staff_waiting_room", &"staff_dormitory"] else status)
|
||||
|
||||
|
||||
func _draw_readable_badge(at: Vector2, text: String) -> void:
|
||||
var width := ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x+6
|
||||
var rect := Rect2(at, Vector2(width, 14))
|
||||
func _readable_badge_rect(at: Vector2, text: String) -> Rect2:
|
||||
# A conservative em-width box makes geometry independently reproducible.
|
||||
# Text still uses the existing fallback font and size.
|
||||
return Rect2(at, Vector2(text.length() * 10 + 6, 14))
|
||||
|
||||
|
||||
func _draw_readable_badge(at: Vector2, text: String, canvas: CanvasItem = null) -> void:
|
||||
if canvas == null:
|
||||
canvas = self
|
||||
var rect := _readable_badge_rect(at, text)
|
||||
if not rect.intersects(Rect2(Vector2.ZERO, size)):
|
||||
return
|
||||
draw_rect(rect, Color("34413b", 0.94))
|
||||
draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce"))
|
||||
canvas.draw_rect(rect, Color("34413b"))
|
||||
canvas.draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce"))
|
||||
|
||||
|
||||
func _prepare_room_presentations() -> void:
|
||||
_room_presentations.clear()
|
||||
_carried_patients.clear()
|
||||
_treated_patients.clear()
|
||||
_resting_staff.clear()
|
||||
var model := _observe_rooms()
|
||||
_room_presentations = model[&"rooms"]
|
||||
_carried_patients = model[&"carried_patients"]
|
||||
_treated_patients = model[&"treated_patients"]
|
||||
_resting_staff = model[&"resting_staff"]
|
||||
|
||||
|
||||
func _observe_rooms() -> Dictionary:
|
||||
var _room_presentations: Dictionary = {}
|
||||
var _carried_patients: Dictionary = {}
|
||||
var _treated_patients: Dictionary = {}
|
||||
var _resting_staff: Dictionary = {}
|
||||
var service: Variant = _service(&"facility_service")
|
||||
if service == null:
|
||||
return
|
||||
return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff}
|
||||
for instance: Variant in service.get_instances():
|
||||
if instance.origin.z == layer_mode:
|
||||
_room_presentations[instance.instance_id] = RoomPresentation.describe(instance)
|
||||
@@ -844,12 +1048,22 @@ func _prepare_room_presentations() -> void:
|
||||
else:
|
||||
_carried_patients[task.target_entity_id] = task.assigned_staff_id
|
||||
|
||||
return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff}
|
||||
|
||||
|
||||
func _prepare_queues() -> void:
|
||||
_queue_positions.clear()
|
||||
_queue_hidden.clear()
|
||||
_landing_badges.clear()
|
||||
_transport_report.clear()
|
||||
var model := _observe_queues()
|
||||
_queue_positions = model[&"queue_positions"]
|
||||
_queue_hidden = model[&"queue_hidden"]
|
||||
_landing_badges.assign(model[&"landing_badges"])
|
||||
_transport_report = model[&"elevators"]
|
||||
|
||||
|
||||
func _observe_queues() -> Dictionary:
|
||||
var _queue_positions: Dictionary = {}
|
||||
var _queue_hidden: Dictionary = {}
|
||||
var _landing_badges: Array[Dictionary] = []
|
||||
var _transport_report: Dictionary = {}
|
||||
var groups: Dictionary = {}
|
||||
var facilities: Variant = _service(&"facility_service")
|
||||
var guests: Variant = _service(&"guest_service")
|
||||
@@ -877,7 +1091,8 @@ func _prepare_queues() -> void:
|
||||
var lift_cells: Dictionary = {}
|
||||
for shaft: Dictionary in _transport_report.get(&"shafts", []):
|
||||
for stop: Vector3i in _cells_from_serialized(shaft.get(&"stops", [])):
|
||||
lift_cells[stop] = true
|
||||
for offset: int in range(maxi(int(shaft.get(&"width", 2)), 1)):
|
||||
lift_cells[stop + Vector3i(offset, 0, 0)] = true
|
||||
var reserved: Dictionary = {}
|
||||
var cells: Array = groups.keys()
|
||||
cells.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return a.y < b.y or (a.y == b.y and a.x < b.x))
|
||||
@@ -892,6 +1107,8 @@ func _prepare_queues() -> void:
|
||||
var next := cell + Vector3i(direction*distance, 0, 0)
|
||||
if world == null or not world.is_walkable(next):
|
||||
break
|
||||
if lift_cells.has(next):
|
||||
break
|
||||
var occupant: StringName = world.get_occupant(next)
|
||||
var facility: Variant = facilities.get_instance(occupant) if facilities != null else null
|
||||
if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]:
|
||||
@@ -908,10 +1125,10 @@ func _prepare_queues() -> void:
|
||||
index += 1
|
||||
for hidden: int in range(index, members.size()):
|
||||
_queue_hidden[members[hidden]] = true
|
||||
var text := "待ち%d人" % members.size()
|
||||
if index < members.size():
|
||||
text += "(他%d人)" % (members.size()-index)
|
||||
_landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text})
|
||||
var text := RoomPresentation.queue_label(members.size(), members.size()-index)
|
||||
_landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text, &"cell": cell, &"members": members.duplicate(), &"visible_count": index, &"overflow": members.size()-index})
|
||||
|
||||
return {&"queue_positions": _queue_positions, &"queue_hidden": _queue_hidden, &"landing_badges": _landing_badges, &"elevators": _transport_report, &"shaft_footprint": lift_cells.keys()}
|
||||
|
||||
|
||||
func _add_queue_member(groups: Dictionary, key: String, cell: Vector3i) -> void:
|
||||
@@ -1038,15 +1255,20 @@ func _draw_facility_labels() -> void:
|
||||
draw_string(ThemeDB.fallback_font,at+Vector2(4,12),label,HORIZONTAL_ALIGNMENT_CENTER,label_width-8,font_size,Color("f0e2b9"))
|
||||
|
||||
|
||||
func _draw_elevators() -> void:
|
||||
_boarded_actor_ids.clear()
|
||||
var transport: Variant = _service(&"elevators")
|
||||
if transport == null or not transport.has_method(&"get_report"):
|
||||
_elevator_tracks.clear()
|
||||
return
|
||||
var report_value: Variant = _transport_report
|
||||
if not report_value is Dictionary:
|
||||
return
|
||||
func _draw_elevators(canvas: CanvasItem) -> void:
|
||||
for row: Dictionary in _life_observation.get(&"cabins", []):
|
||||
PixelArt.elevator_cabin(canvas, row[&"rect"], row[&"pixel"], layer_mode == LayerMode.BACK, row[&"operational"])
|
||||
PixelArt.elevator_gate(canvas, row[&"rect"], row[&"pixel"], row[&"doors_open"])
|
||||
_draw_readable_badge(row[&"badge_at"], row[&"badge"], canvas)
|
||||
for badge: Dictionary in _life_observation.get(&"landing_badges", []):
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"], canvas)
|
||||
|
||||
|
||||
func _observe_elevators(observation: Dictionary) -> Array[Dictionary]:
|
||||
var cabins: Array[Dictionary] = []
|
||||
var _boarded_actor_ids: Dictionary = observation[&"boarded"]
|
||||
var _elevator_tracks: Dictionary = observation[&"elevator_tracks"]
|
||||
var report_value: Dictionary = observation[&"elevators"]
|
||||
var alive: Dictionary = {}
|
||||
var p := _cell_dimensions().x/16.0
|
||||
for value: Variant in report_value.get(&"shafts", []):
|
||||
@@ -1062,7 +1284,7 @@ func _draw_elevators() -> void:
|
||||
for actor_id: Variant in passengers:
|
||||
_boarded_actor_ids[_elevator_actor_key(String(actor_id))] = true
|
||||
var floor_value := float(shaft.get(&"visual_floor", shaft.get(&"current_floor", stops[0].y)))
|
||||
var visual_floor := _tracked_elevator_floor(shaft_id, floor_value)
|
||||
var visual_floor := _tracked_elevator_floor(shaft_id, floor_value, observation)
|
||||
if stops[0].z != layer_mode:
|
||||
continue
|
||||
var origin := get_cell_rect(stops[0])
|
||||
@@ -1074,10 +1296,9 @@ func _draw_elevators() -> void:
|
||||
var operational := bool(shaft.get(&"operational", true))
|
||||
var phase := StringName(shaft.get(&"phase", &"IDLE"))
|
||||
var doors_open := operational and phase in [&"IDLE", &"BOARDING", &"ALIGHTING"]
|
||||
PixelArt.elevator_cabin(self,cabin,p,layer_mode==LayerMode.BACK,operational)
|
||||
var passengers_drawn: Array[Dictionary] = []
|
||||
var seat_columns := maxi(int((cabin.size.x / p - 4.0) / 8.0), 1)
|
||||
var visible_count := mini(passengers.size(), seat_columns*2)
|
||||
for index: int in range(visible_count):
|
||||
for index: int in range(passengers.size()):
|
||||
var actor_key := _elevator_actor_key(String(passengers[index]))
|
||||
var is_staff := actor_key.begins_with("staff/")
|
||||
var person: Variant = null
|
||||
@@ -1088,24 +1309,23 @@ func _draw_elevators() -> void:
|
||||
var carrying := is_staff and person != null and not String(person.carried_inventory_id).is_empty()
|
||||
var state := int(person.state) if person != null else 0
|
||||
var x := cabin.position.x + (2.0 + (index % seat_columns + 0.5)*8.0)*p
|
||||
var feet := cabin.end.y - (2.0 + int(index / seat_columns)*9.0)*p
|
||||
var feet := cabin.end.y - (2.0 + (int(index / seat_columns) % 2)*9.0)*p
|
||||
var center := Vector2(x, feet+3*p-_cell_dimensions().y*0.5)
|
||||
_draw_pixel_actor(center,actor_key,is_staff,state,false,carrying,&"",true)
|
||||
PixelArt.elevator_gate(self,cabin,p,doors_open)
|
||||
var capacity := int(shaft.get(&"capacity", 0))
|
||||
var load_text := "%d/%d人" % [passengers.size(), capacity]
|
||||
if capacity > 0 and passengers.size() >= capacity:
|
||||
load_text = "満員 " + load_text
|
||||
if not operational:
|
||||
load_text = "停止 " + load_text
|
||||
_draw_readable_badge(Vector2(cabin.position.x, cabin.position.y-14), load_text)
|
||||
for badge: Dictionary in _landing_badges:
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"])
|
||||
passengers_drawn.append(_actor_drawing(center,actor_key,is_staff,state,false,carrying,&"",true,observation))
|
||||
cabins.append({&"shaft_id": shaft_id, &"rect": cabin, &"pixel": p,
|
||||
&"operational": operational, &"doors_open": doors_open,
|
||||
&"phase": phase, &"passenger_count": passengers.size(),
|
||||
&"passengers": passengers_drawn,
|
||||
&"badge_at": Vector2(cabin.position.x, cabin.position.y-14),
|
||||
&"badge_rect": _readable_badge_rect(Vector2(cabin.position.x, cabin.position.y-14), RoomPresentation.elevator_label(shaft)),
|
||||
&"badge": RoomPresentation.elevator_label(shaft)})
|
||||
|
||||
for shaft_id: Variant in _elevator_tracks.keys():
|
||||
if not alive.has(shaft_id):
|
||||
_elevator_tracks.erase(shaft_id)
|
||||
|
||||
return cabins
|
||||
|
||||
|
||||
func _elevator_actor_key(actor_id: String) -> String:
|
||||
if actor_id.begins_with("guest:"):
|
||||
@@ -1115,7 +1335,8 @@ func _elevator_actor_key(actor_id: String) -> String:
|
||||
return actor_id
|
||||
|
||||
|
||||
func _tracked_elevator_floor(shaft_id: StringName, current_floor: float) -> float:
|
||||
func _tracked_elevator_floor(shaft_id: StringName, current_floor: float, observation: Dictionary = {}) -> float:
|
||||
var _elevator_tracks: Dictionary = observation.get(&"elevator_tracks", self._elevator_tracks)
|
||||
if not _elevator_tracks.has(shaft_id):
|
||||
_elevator_tracks[shaft_id] = {&"floor":current_floor,&"from":current_floor,&"at":_animation_time}
|
||||
var track: Dictionary = _elevator_tracks[shaft_id]
|
||||
@@ -1130,7 +1351,20 @@ func _tracked_elevator_floor(shaft_id: StringName, current_floor: float) -> floa
|
||||
return lerpf(float(track[&"from"]),current_floor,t)
|
||||
|
||||
|
||||
func _draw_actors() -> void:
|
||||
func _draw_actors(canvas: CanvasItem = null) -> void:
|
||||
for row: Dictionary in _life_observation.get(&"actor_drawings", []):
|
||||
_draw_observed_actor(row, canvas)
|
||||
|
||||
|
||||
func _observe_actors(observation: Dictionary) -> Array[Dictionary]:
|
||||
var rows: Array[Dictionary] = []
|
||||
var _actor_tracks: Dictionary = observation[&"actor_tracks"]
|
||||
var _boarded_actor_ids: Dictionary = observation[&"boarded"]
|
||||
var _queue_hidden: Dictionary = observation[&"queue_hidden"]
|
||||
var _queue_positions: Dictionary = observation[&"queue_positions"]
|
||||
var _carried_patients: Dictionary = observation[&"carried_patients"]
|
||||
var _treated_patients: Dictionary = observation[&"treated_patients"]
|
||||
var _resting_staff: Dictionary = observation[&"resting_staff"]
|
||||
var counts: Dictionary = {}
|
||||
var alive: Dictionary = {}
|
||||
var service_slots: Dictionary = {}
|
||||
@@ -1144,18 +1378,18 @@ func _draw_actors() -> void:
|
||||
if _boarded_actor_ids.has(key) or _queue_hidden.has(key):
|
||||
_actor_tracks.erase(key)
|
||||
continue
|
||||
var position := _tracked_actor_position(key,guest.current_cell)
|
||||
var position := _tracked_actor_position(key,guest.current_cell,observation)
|
||||
if guest.current_cell.z==layer_mode:
|
||||
var receiving := not String(guest.current_service_id).is_empty()
|
||||
if _queue_positions.has(key):
|
||||
position = _queue_positions[key]
|
||||
elif receiving:
|
||||
position = _service_use_position(guest,position,service_slots)
|
||||
position = _service_use_position(guest,position,service_slots,observation)
|
||||
if not position.is_finite():
|
||||
continue
|
||||
else:
|
||||
position += _actor_offset(guest.current_cell,counts)
|
||||
_draw_pixel_actor(position,key,false,int(guest.state),receiving,false,_guest_activity(guest))
|
||||
rows.append(_actor_drawing(position,key,false,int(guest.state),receiving,false,_guest_activity(guest),false,observation))
|
||||
var staff_service: Variant = _service(&"staff_service")
|
||||
if staff_service != null and staff_service.has_method(&"get_staff_members"):
|
||||
for staff: Variant in staff_service.call(&"get_staff_members"):
|
||||
@@ -1166,27 +1400,32 @@ func _draw_actors() -> void:
|
||||
if _boarded_actor_ids.has(key) or _queue_hidden.has(key):
|
||||
_actor_tracks.erase(key)
|
||||
continue
|
||||
var position := _tracked_actor_position(key,staff.current_cell)
|
||||
var position := _tracked_actor_position(key,staff.current_cell,observation)
|
||||
if staff.current_cell.z==layer_mode:
|
||||
if _queue_positions.has(key):
|
||||
rows.append(_actor_drawing(_queue_positions[key],key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation))
|
||||
continue
|
||||
if _carried_patients.has(staff.staff_id):
|
||||
var carrier: Variant = staff_service.get_staff(_carried_patients[staff.staff_id])
|
||||
if carrier != null:
|
||||
position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell) - Vector2(0, 12*_cell_dimensions().x/16.0)
|
||||
_draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
||||
position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell,observation) - Vector2(0, 12*_cell_dimensions().x/16.0)
|
||||
rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||
continue
|
||||
if _resting_staff.has(staff.staff_id):
|
||||
position = _resting_staff_position(staff.staff_id, position)
|
||||
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
||||
position = _resting_staff_position(staff.staff_id, position,observation)
|
||||
if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||
continue
|
||||
if _treated_patients.has(staff.staff_id):
|
||||
position = _rescue_treatment_position(staff.staff_id, position)
|
||||
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
||||
position = _rescue_treatment_position(staff.staff_id, position,observation)
|
||||
if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||
continue
|
||||
_draw_pixel_actor(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts))),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff))
|
||||
rows.append(_actor_drawing(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts),observation)),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation))
|
||||
for key: Variant in _actor_tracks.keys():
|
||||
if not alive.has(key):
|
||||
_actor_tracks.erase(key)
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
func _staff_work_animation(staff: StaffRuntime) -> StringName:
|
||||
if staff.role_id != &"cleaner":
|
||||
@@ -1201,7 +1440,8 @@ func _staff_work_animation(staff: StaffRuntime) -> StringName:
|
||||
return &"idle"
|
||||
|
||||
|
||||
func _staff_work_position(staff: Variant, fallback: Vector2) -> Vector2:
|
||||
func _staff_work_position(staff: Variant, fallback: Vector2, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
if staff.state not in [StaffRuntime.State.WORKING, StaffRuntime.State.AVAILABLE]:
|
||||
return fallback
|
||||
var service: Variant = _service(&"facility_service")
|
||||
@@ -1227,7 +1467,8 @@ func _staff_work_position(staff: Variant, fallback: Vector2) -> Vector2:
|
||||
return Vector2(rect.position.x+(slot+0.5)*rect.size.x/columns, rect.position.y+26*p-_cell_dimensions().y*0.5)
|
||||
|
||||
|
||||
func _tracked_actor_position(key: String, cell: Vector3i) -> Vector2:
|
||||
func _tracked_actor_position(key: String, cell: Vector3i, observation: Dictionary = {}) -> Vector2:
|
||||
var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks)
|
||||
if not _actor_tracks.has(key):
|
||||
_actor_tracks[key] = {&"cell":cell,&"from":cell,&"at":_animation_time,&"direction":1.0}
|
||||
var track: Dictionary = _actor_tracks[key]
|
||||
@@ -1246,7 +1487,8 @@ func _tracked_actor_position(key: String, cell: Vector3i) -> Vector2:
|
||||
return cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(cell),t)
|
||||
|
||||
|
||||
func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary) -> Vector2:
|
||||
func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
# Guest paths stop at an access cell. While a service is active, project the
|
||||
# occupant into that facility's use position; authoritative paths stay intact.
|
||||
var id := StringName(guest.target_facility_id)
|
||||
@@ -1281,6 +1523,21 @@ func _guest_activity(guest: Variant) -> StringName:
|
||||
|
||||
|
||||
func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false) -> void:
|
||||
_draw_observed_actor(_actor_drawing(center, key, staff, state, receiving, carrying, activity, riding))
|
||||
|
||||
|
||||
func _draw_observed_actor(row: Dictionary, canvas: CanvasItem = null) -> void:
|
||||
if canvas == null:
|
||||
canvas = self
|
||||
var at: Vector2 = row[&"at"]
|
||||
var p: float = row[&"scale"]
|
||||
canvas.draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2))
|
||||
People.draw(canvas, at, p, row[&"mode"], row[&"frame"], row[&"variant"], row[&"staff"], row[&"flip"])
|
||||
|
||||
|
||||
func _actor_drawing(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false, observation: Dictionary = {}) -> Dictionary:
|
||||
var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks)
|
||||
var _queue_positions: Dictionary = observation.get(&"queue_positions", self._queue_positions)
|
||||
var world_pixel := _cell_dimensions().x / 16.0
|
||||
var scale := 0.5 if riding else 0.75
|
||||
var p := maxf(floorf(world_pixel * scale), 1.0) if world_pixel >= 2.0 else world_pixel * scale
|
||||
@@ -1307,8 +1564,274 @@ func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, re
|
||||
elif _queue_positions.has(key) or (not staff and state == GuestRuntime.State.QUEUING):
|
||||
mode = &"wait"
|
||||
var at := (center + Vector2(-8 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 17 * p)).round()
|
||||
draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2))
|
||||
People.draw(self, at, p, mode, frame, variant, staff, float(track.get(&"direction", 1.0)) < 0.0)
|
||||
return {&"key": key, &"center": center, &"at": at, &"scale": p, &"mode": mode,
|
||||
&"frame": frame, &"variant": variant, &"staff": staff, &"state": state,
|
||||
&"receiving": receiving, &"carrying": carrying, &"activity": activity,
|
||||
&"riding": riding, &"flip": float(track.get(&"direction", 1.0)) < 0.0}
|
||||
|
||||
|
||||
## Identity cores are tied to the unchanged PixelPeople poses, not sprite boxes.
|
||||
func _life_core(row: Dictionary) -> Rect2:
|
||||
var phase := int(row[&"frame"]) % 8
|
||||
var core := Rect2(6, 1, 6, 12)
|
||||
if row[&"mode"] in [&"sleep", &"collapsed", &"standard_room_sleep"]:
|
||||
core = Rect2(2, 11, 13, 5)
|
||||
if row[&"mode"] == &"standard_room_sleep" and phase in [2, 3, 4]:
|
||||
core.position.y -= 1
|
||||
core.size.y += 1
|
||||
elif row[&"mode"] in [&"walk", &"carry_walk"] and phase in [2, 6]:
|
||||
core.position.y -= 1
|
||||
if row[&"flip"]:
|
||||
core.position.x = 16-core.end.x
|
||||
return Rect2(row[&"at"]+core.position*row[&"scale"], core.size*row[&"scale"])
|
||||
|
||||
|
||||
func _life_track_region(key: String, cell: Vector3i, model: Dictionary) -> Dictionary:
|
||||
var track: Dictionary = model[&"actor_tracks"].get(key, {})
|
||||
var transit: bool = not model[&"paused"] and track.get(&"from", cell) != cell and _animation_time-float(track.get(&"at", 0.0)) < 0.42
|
||||
var rect := get_cell_rect(cell)
|
||||
if transit:
|
||||
rect = rect.merge(get_cell_rect(track[&"from"]))
|
||||
return {&"bounds": rect, &"transit": transit}
|
||||
|
||||
|
||||
## Keep non-passengers on a real corridor landing when a cabin crosses their pose.
|
||||
func _life_landing_region(region: Dictionary, cell: Vector3i, model: Dictionary) -> Dictionary:
|
||||
var blocked := false
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
if (region[&"bounds"] as Rect2).intersects(cabin[&"rect"]):
|
||||
blocked = true
|
||||
break
|
||||
if not blocked:
|
||||
return region
|
||||
var world: Variant = _service(&"world_service")
|
||||
var facilities: Variant = _service(&"facility_service")
|
||||
var footprint: Array = model[&"shaft_footprint"]
|
||||
var origins: Array = footprint.filter(func(c: Vector3i) -> bool: return c.y == cell.y and c.z == cell.z)
|
||||
origins.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x))
|
||||
var lane: Array[Vector3i] = []
|
||||
for origin: Vector3i in origins:
|
||||
for direction: int in [-1, 1]:
|
||||
for distance: int in range(1, 9):
|
||||
var target := origin + Vector3i(direction*distance, 0, 0)
|
||||
if world == null or footprint.has(target) or not world.is_walkable(target):
|
||||
break
|
||||
var facility: Variant = facilities.get_instance(world.get_occupant(target)) if facilities != null else null
|
||||
if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]:
|
||||
break
|
||||
if not lane.has(target):
|
||||
lane.append(target)
|
||||
lane.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x))
|
||||
if lane.is_empty():
|
||||
var anchor := get_cell_rect(cell).position+Vector2(-80, -16)
|
||||
var nearest := INF
|
||||
for badge: Dictionary in model[&"landing_badges"]:
|
||||
var landing: Vector3i = badge[&"cell"]
|
||||
if landing.y == cell.y and landing.z == cell.z and absf(landing.x-cell.x) < nearest:
|
||||
nearest = absf(landing.x-cell.x)
|
||||
anchor = badge[&"at"]
|
||||
return {&"bounds": region[&"bounds"], &"transit": false, &"no_lane": true, &"badge_at": anchor}
|
||||
var selected: Vector3i = lane[0]
|
||||
for target: Vector3i in lane:
|
||||
if not model[&"queue_positions"].values().has(cell_to_canvas(target)):
|
||||
selected = target
|
||||
break
|
||||
return {&"bounds": get_cell_rect(selected), &"transit": false, &"center": cell_to_canvas(selected)}
|
||||
|
||||
|
||||
func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary) -> Dictionary:
|
||||
var key: String = row[&"key"]
|
||||
var cell: Vector3i = person[&"cell"]
|
||||
var result := _life_track_region(key, cell, model)
|
||||
var p := _cell_dimensions().x/16.0
|
||||
var world: Variant = _service(&"world_service")
|
||||
var facility_id: StringName = &""
|
||||
if model[&"queue_positions"].has(key):
|
||||
result = {&"bounds": Rect2(model[&"queue_positions"][key]-_cell_dimensions()*0.5, _cell_dimensions()), &"transit": false}
|
||||
elif not row[&"staff"]:
|
||||
if not String(person[&"service"]).is_empty():
|
||||
var room: Dictionary = model[&"rooms"].get(person[&"target"], {})
|
||||
if (room.get(&"people", []) as Array).has(person[&"id"]):
|
||||
facility_id = person[&"target"]
|
||||
else:
|
||||
var id: StringName = person[&"id"]
|
||||
if model[&"carried_patients"].has(id):
|
||||
for carrier: Dictionary in model[&"staff"]:
|
||||
if carrier[&"id"] == model[&"carried_patients"][id]:
|
||||
result = _life_actor_region({&"key": "staff/"+String(carrier[&"id"]), &"staff": true}, carrier, model)
|
||||
var rect: Rect2 = result[&"bounds"]
|
||||
rect = rect.grow(p)
|
||||
rect.position.y -= 12*p
|
||||
result[&"bounds"] = rect
|
||||
if result.has(&"center"):
|
||||
result[&"center"] -= Vector2(0, 12*p)
|
||||
elif model[&"resting_staff"].has(id):
|
||||
facility_id = model[&"resting_staff"][id]
|
||||
elif model[&"treated_patients"].has(id):
|
||||
facility_id = model[&"treated_patients"][id]
|
||||
elif person[&"state"] in ["WORKING", "AVAILABLE"] and world != null:
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(person[&"facility"])
|
||||
var room: Dictionary = model[&"rooms"].get(person[&"facility"], {})
|
||||
var slot := (room.get(&"staff", []) as Array).find(id)
|
||||
var rect := _cell_bounds_rect(cells)
|
||||
var columns := maxi(int(rect.size.x/(16*p)), 1)
|
||||
if cells.has(cell) and slot >= 0 and slot < columns and rect.size.y >= 40*p:
|
||||
result = {&"bounds": Rect2(rect.position+Vector2(slot*rect.size.x/columns, 0), Vector2(rect.size.x/columns, rect.size.y)), &"transit": false}
|
||||
if not facility_id.is_empty() and world != null:
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id)
|
||||
if not cells.is_empty():
|
||||
result = {&"bounds": _cell_bounds_rect(cells), &"transit": false}
|
||||
if not model[&"queue_positions"].has(key) and not result.has(&"center") and not result.get(&"no_lane", false):
|
||||
result = _life_landing_region(result, cell, model)
|
||||
result[&"bounds"] = (result[&"bounds"] as Rect2).grow(-p)
|
||||
return result
|
||||
|
||||
|
||||
func _life_scaled(row: Dictionary, scale: float) -> Dictionary:
|
||||
var result := row.duplicate(true)
|
||||
result[&"scale"] = scale
|
||||
result[&"at"] = (row[&"center"]+Vector2(-8*scale, _cell_dimensions().y*0.5-3*_cell_dimensions().x/16.0-17*scale)).round()
|
||||
return result
|
||||
|
||||
|
||||
## Forward/backward packing keeps each member's own interval. No relocation.
|
||||
func _life_pack(group: Array, scale: float) -> Array:
|
||||
var trial: Array = []
|
||||
var right := -INF
|
||||
for member: Dictionary in group:
|
||||
var row := _life_scaled(member[&"row"], minf(scale, member[&"row"][&"scale"]))
|
||||
var core := _life_core(row)
|
||||
var bounds: Rect2 = member[&"bounds"]
|
||||
var x := maxf(maxf(core.position.x, bounds.position.x), right)
|
||||
row[&"at"].x += x-core.position.x
|
||||
right = x+core.size.x
|
||||
trial.append(row)
|
||||
var left := INF
|
||||
for i: int in range(trial.size()-1, -1, -1):
|
||||
var row: Dictionary = trial[i]
|
||||
var core := _life_core(row)
|
||||
var bounds: Rect2 = group[i][&"bounds"]
|
||||
var x := minf(core.position.x, minf(bounds.end.x, left)-core.size.x)
|
||||
row[&"at"].x += x-core.position.x
|
||||
left = x
|
||||
if x < bounds.position.x or core.position.y < bounds.position.y or core.end.y > bounds.end.y:
|
||||
return []
|
||||
return trial
|
||||
|
||||
|
||||
## Rebuild vertical components at every fitted scale. Ceilings only decrease;
|
||||
## once none decreases, the feet/cores and therefore the partition are fixed.
|
||||
func _life_fitted_groups(members: Array) -> Array:
|
||||
var groups: Array = []
|
||||
while true:
|
||||
for member: Dictionary in members:
|
||||
member[&"row"].merge(_life_scaled(member[&"row"], member[&"row"][&"scale"]), true)
|
||||
members.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
var ay := _life_core(a[&"row"]).position.y
|
||||
var by := _life_core(b[&"row"]).position.y
|
||||
return ay < by or (ay == by and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
||||
groups = []
|
||||
var bottom := -INF
|
||||
for member: Dictionary in members:
|
||||
var core := _life_core(member[&"row"])
|
||||
if groups.is_empty() or core.position.y >= bottom:
|
||||
groups.append([])
|
||||
bottom = core.end.y
|
||||
else:
|
||||
bottom = maxf(bottom, core.end.y)
|
||||
groups.back().append(member)
|
||||
var reduced := false
|
||||
for group: Array in groups:
|
||||
group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
||||
var ceiling := 1.0
|
||||
for member: Dictionary in group:
|
||||
ceiling = maxf(ceiling, member[&"row"][&"scale"])
|
||||
if _life_pack(group, ceiling).is_empty() and ceiling > 1:
|
||||
for member: Dictionary in group:
|
||||
var scale := minf(member[&"row"][&"scale"], maxf(1, int(ceiling)-1))
|
||||
member[&"row"].merge(_life_scaled(member[&"row"], scale), true)
|
||||
reduced = true
|
||||
if not reduced:
|
||||
return groups
|
||||
return groups
|
||||
|
||||
|
||||
func _fit_life_actors(model: Dictionary) -> void:
|
||||
var people: Dictionary = {}
|
||||
for person: Dictionary in model[&"guests"]:
|
||||
people["guest/"+String(person[&"id"])] = person
|
||||
for person: Dictionary in model[&"staff"]:
|
||||
people["staff/"+String(person[&"id"])] = person
|
||||
var members: Array = []
|
||||
var stranded: Array = []
|
||||
var all_rows: Array = model[&"actor_drawings"].duplicate()
|
||||
var passenger_bounds: Dictionary = {}
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
for row: Dictionary in cabin[&"passengers"]:
|
||||
all_rows.append(row)
|
||||
passenger_bounds[row[&"key"]] = (cabin[&"rect"] as Rect2).grow(-_cell_dimensions().x/16.0)
|
||||
for row: Dictionary in all_rows:
|
||||
var person: Dictionary = people[row[&"key"]]
|
||||
row[&"cell"] = person[&"cell"]
|
||||
var region := _life_actor_region(row, person, model) if not row[&"riding"] else {&"bounds": passenger_bounds[row[&"key"]], &"transit": false}
|
||||
if region.has(&"center"):
|
||||
var jitter := Vector2.ZERO
|
||||
if not model[&"carried_patients"].has(person[&"id"]):
|
||||
var track: Dictionary = model[&"actor_tracks"][row[&"key"]]
|
||||
var t := 1.0 if model[&"paused"] else clampf((_animation_time-float(track[&"at"]))/0.42, 0, 1)
|
||||
jitter = row[&"center"]-cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(person[&"cell"]), t)
|
||||
row[&"center"] = region[&"center"]+jitter
|
||||
row.merge(_life_scaled(row, row[&"scale"]), true)
|
||||
region[&"row"] = row
|
||||
if region.get(&"no_lane", false):
|
||||
stranded.append(region)
|
||||
elif not region[&"transit"]:
|
||||
members.append(region)
|
||||
var groups := _life_fitted_groups(members)
|
||||
model[&"actor_aggregations"] = []
|
||||
var hidden: Dictionary = {}
|
||||
for member: Dictionary in stranded:
|
||||
var row: Dictionary = member[&"row"]
|
||||
hidden[row[&"key"]] = true
|
||||
var aggregate: Dictionary = {}
|
||||
for existing: Dictionary in model[&"actor_aggregations"]:
|
||||
if existing[&"at"] == member[&"badge_at"]:
|
||||
aggregate = existing
|
||||
break
|
||||
if aggregate.is_empty():
|
||||
aggregate = {&"members": [], &"drawn_keys": [], &"at": member[&"badge_at"], &"text": ""}
|
||||
model[&"actor_aggregations"].append(aggregate)
|
||||
aggregate[&"members"].append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]})
|
||||
aggregate[&"text"] = "+%d" % aggregate[&"members"].size()
|
||||
for group: Array in groups:
|
||||
group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
||||
var base := 1.0
|
||||
for member: Dictionary in group:
|
||||
base = maxf(base, member[&"row"][&"scale"])
|
||||
var fitted := _life_pack(group, base)
|
||||
var retained: Array = group
|
||||
if fitted.is_empty():
|
||||
retained = []
|
||||
var identities: Array = []
|
||||
var drawn: Array = []
|
||||
for member: Dictionary in group:
|
||||
var row: Dictionary = member[&"row"]
|
||||
identities.append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]})
|
||||
var attempt := retained.duplicate()
|
||||
attempt.append(member)
|
||||
var packed := _life_pack(attempt, 1)
|
||||
if not packed.is_empty():
|
||||
retained = attempt
|
||||
fitted = packed
|
||||
drawn.append(row[&"key"])
|
||||
else:
|
||||
hidden[row[&"key"]] = true
|
||||
model[&"actor_aggregations"].append({&"members": identities, &"drawn_keys": drawn, &"at": (group[0][&"bounds"] as Rect2).position, &"text": "+%d" % (group.size()-retained.size())})
|
||||
for i: int in fitted.size():
|
||||
retained[i][&"row"].merge(fitted[i], true)
|
||||
model[&"actor_drawings"] = model[&"actor_drawings"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"]))
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
cabin[&"passengers"] = cabin[&"passengers"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"]))
|
||||
|
||||
|
||||
func _draw_excavation_jobs() -> void:
|
||||
@@ -1421,14 +1944,14 @@ func _facility_color(instance_id: StringName) -> Color:
|
||||
var instance: Variant = facility_service.call(&"get_instance", instance_id)
|
||||
if instance == null:
|
||||
return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582")
|
||||
match int(instance.state):
|
||||
0:
|
||||
match int(_room_presentations.get(instance_id, {}).get(&"state", instance.state)):
|
||||
FacilityInstance.State.BROKEN:
|
||||
return Color("a24b45")
|
||||
1, 2, 3:
|
||||
FacilityInstance.State.NO_ACCESS, FacilityInstance.State.NO_POWER, FacilityInstance.State.NO_STAFF:
|
||||
return Color("a77d3f")
|
||||
4:
|
||||
FacilityInstance.State.CLOSED:
|
||||
return Color("6b6258")
|
||||
5:
|
||||
FacilityInstance.State.DEGRADED:
|
||||
return Color("89914d")
|
||||
_:
|
||||
return Color("6f9a55") if layer_mode == LayerMode.FRONT else Color("67518e")
|
||||
@@ -1579,7 +2102,9 @@ func _normalized_rotation(value: int) -> int:
|
||||
return posmod(value, 360) / 90 * 90
|
||||
|
||||
|
||||
func _rescue_treatment_position(patient_id: StringName, fallback: Vector2) -> Vector2:
|
||||
func _rescue_treatment_position(patient_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
var _treated_patients: Dictionary = observation.get(&"treated_patients", self._treated_patients)
|
||||
var facility_id: StringName = _treated_patients[patient_id]
|
||||
var world: Variant = _service(&"world_service")
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else []
|
||||
@@ -1592,7 +2117,9 @@ func _rescue_treatment_position(patient_id: StringName, fallback: Vector2) -> Ve
|
||||
return anchors[slot]+Vector2(0,-2*p-_cell_dimensions().y*0.5)
|
||||
|
||||
|
||||
func _resting_staff_position(staff_id: StringName, fallback: Vector2) -> Vector2:
|
||||
func _resting_staff_position(staff_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
var _resting_staff: Dictionary = observation.get(&"resting_staff", self._resting_staff)
|
||||
var facility_id: StringName = _resting_staff[staff_id]
|
||||
var world: Variant = _service(&"world_service")
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else []
|
||||
|
||||
@@ -10,7 +10,9 @@ static func describe(instance: Variant) -> Dictionary:
|
||||
if not ids.has(id):
|
||||
ids.append(id)
|
||||
ids.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b))
|
||||
return {&"operation": int(instance.operation_state), &"people": ids,
|
||||
return {&"state": int(instance.state), &"state_name": FacilityInstance.State.keys()[instance.state],
|
||||
&"reasons": instance.state_reasons.duplicate(),
|
||||
&"operation": int(instance.operation_state), &"people": ids,
|
||||
&"count": ids.size(), &"limit": int(instance.customer_capacity),
|
||||
&"staff": instance.active_staff_ids.duplicate(), &"suspension": StringName(instance.suspension_reason)}
|
||||
|
||||
@@ -50,3 +52,52 @@ static func label(data: Dictionary, visible_count: int) -> String:
|
||||
if count > visible_count:
|
||||
value += "(他%d人)" % (count - visible_count)
|
||||
return value
|
||||
|
||||
|
||||
static func business_label(data: Dictionary) -> String:
|
||||
if data.is_empty():
|
||||
return ""
|
||||
if int(data[&"operation"]) == FacilityInstance.OperationState.SUSPENDED:
|
||||
return "休止・摘発" if data[&"suspension"] == &"RAIDED" else "休止・破壊"
|
||||
match int(data[&"state"]):
|
||||
FacilityInstance.State.BROKEN: return "破損"
|
||||
FacilityInstance.State.NO_ACCESS: return "通路なし"
|
||||
FacilityInstance.State.NO_POWER: return "魔力なし"
|
||||
FacilityInstance.State.NO_STAFF: return "職員不足"
|
||||
FacilityInstance.State.CLOSED: return "営業停止"
|
||||
FacilityInstance.State.DEGRADED: return "低下運転"
|
||||
FacilityInstance.State.OPEN: return "営業中"
|
||||
return String(data[&"state_name"])
|
||||
|
||||
|
||||
static func queue_label(count: int, overflow: int) -> String:
|
||||
return "待ち%d人" % count + ("(他%d人)" % overflow if overflow > 0 else "")
|
||||
|
||||
|
||||
static func elevator_label(shaft: Dictionary) -> String:
|
||||
var count := (shaft[&"boarded"] as Array).size()
|
||||
var capacity := int(shaft[&"capacity"])
|
||||
var value := "%d/%d人" % [count, capacity]
|
||||
if capacity > 0 and count >= capacity:
|
||||
value = "満員 " + value
|
||||
if not bool(shaft[&"operational"]):
|
||||
value = "停止 " + value
|
||||
else:
|
||||
value = "運転 " + value
|
||||
var phase := String(shaft.get(&"phase", &"IDLE"))
|
||||
var doors_open := bool(shaft[&"operational"]) and phase in ["IDLE", "BOARDING", "ALIGHTING"]
|
||||
return value + (" 開 " if doors_open else " 閉 ") + phase
|
||||
|
||||
|
||||
static func supply_label(requests: Array[Dictionary]) -> String:
|
||||
# These are requests, never invented stock quantities. Preserve actual states.
|
||||
var states: Dictionary = {}
|
||||
for request: Dictionary in requests:
|
||||
var state := String(request[&"state"])
|
||||
states[state] = int(states.get(state, 0)) + 1
|
||||
var names: Array = states.keys()
|
||||
names.sort()
|
||||
var parts: PackedStringArray = []
|
||||
for state: String in names:
|
||||
parts.append("%s:%d" % [state, states[state]])
|
||||
return "物資依頼 " + " / ".join(parts)
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
extends RefCounted
|
||||
## Sparse whole typed captures. No acceptance flags and no truncation.
|
||||
const Codec := preload("res://tests/fixtures/p08-02/mixed_world.gd")
|
||||
var directory: String
|
||||
var manifest: FileAccess
|
||||
var observer: MotionObserver
|
||||
|
||||
class MotionObserver extends Node:
|
||||
var view: WorldPreview
|
||||
var frames: Array = []
|
||||
var elapsed := 0.0
|
||||
var scaled_elapsed := 0.0
|
||||
func _ready() -> void:
|
||||
RenderingServer.frame_post_draw.connect(after_draw)
|
||||
view.draw.connect(on_view_draw)
|
||||
func _process(delta: float) -> void:
|
||||
elapsed += delta
|
||||
var clock := get_node_or_null("/root/GameClock")
|
||||
if clock == null or not bool(clock.get("paused")):
|
||||
scaled_elapsed += delta * maxf(float(clock.get("speed")) if clock != null else 1.0, 0.0)
|
||||
func on_view_draw() -> void:
|
||||
# Read the draw-consumed report after the real _draw completes.
|
||||
after_draw.call_deferred()
|
||||
func display_sample(report: Dictionary) -> Dictionary:
|
||||
var sample := {}
|
||||
for key: String in ["animation_time", "paused", "layer", "zoom", "pan", "viewport", "actor_tracks", "elevator_tracks", "actor_drawings", "cabins"]:
|
||||
sample[key] = report[key]
|
||||
return sample
|
||||
func after_draw() -> void:
|
||||
if not is_instance_valid(view): return
|
||||
var frame := Engine.get_process_frames()
|
||||
if not frames.is_empty() and frame <= int(frames[-1]["frame"]): return
|
||||
var report := view.get_life_observation_report()
|
||||
if report.is_empty(): return
|
||||
var sample := display_sample(report)
|
||||
if not frames.is_empty() and sample == frames[-1]["display"] and not sample["paused"]: return
|
||||
# Keep only two draw frames, never whole Runtime copies per frame.
|
||||
frames.append({"frame": frame, "delta": elapsed, "scaled_delta": scaled_elapsed, "display": sample})
|
||||
if frames.size() > 2: frames.pop_front()
|
||||
elapsed = 0.0
|
||||
scaled_elapsed = 0.0
|
||||
func _exit_tree() -> void:
|
||||
if is_instance_valid(view) and view.draw.is_connected(on_view_draw): view.draw.disconnect(on_view_draw)
|
||||
if RenderingServer.frame_post_draw.is_connected(after_draw): RenderingServer.frame_post_draw.disconnect(after_draw)
|
||||
|
||||
func watch(view: WorldPreview) -> void:
|
||||
observer = MotionObserver.new()
|
||||
observer.view = view
|
||||
view.add_child(observer)
|
||||
|
||||
func await_draws(tree: SceneTree, view: WorldPreview, n: int = 2, bound: int = 120) -> bool:
|
||||
var start := Engine.get_process_frames()
|
||||
for unused: int in range(bound):
|
||||
view.queue_redraw()
|
||||
await tree.process_frame
|
||||
if not is_instance_valid(observer): break
|
||||
var report := view.get_life_observation_report()
|
||||
if observer.frames.size() == n and not report.is_empty() and int(observer.frames[0]["frame"]) > start and observer.frames[-1]["display"] == observer.display_sample(report):
|
||||
return true
|
||||
# Persist timeout as a failure, never a skip.
|
||||
record("draw-history-timeout", {"start_frame": start, "bound": bound, "required_frames": n})
|
||||
return false
|
||||
|
||||
func open(path: String) -> bool:
|
||||
directory = path
|
||||
if DirAccess.dir_exists_absolute(path):
|
||||
return false
|
||||
if DirAccess.make_dir_recursive_absolute(path.path_join("objects")) != OK:
|
||||
return false
|
||||
manifest = FileAccess.open(path.path_join("manifest.jsonl"), FileAccess.WRITE)
|
||||
return manifest != null
|
||||
|
||||
func object(value: Variant) -> String:
|
||||
var text := JSON.stringify(Codec.new().typed(value)) + "\n"
|
||||
var digest := text.sha256_text()
|
||||
var path := directory.path_join("objects").path_join(digest + ".json")
|
||||
if FileAccess.file_exists(path):
|
||||
return digest if FileAccess.get_sha256(path) == digest else ""
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return ""
|
||||
file.store_string(text)
|
||||
file.close()
|
||||
return digest if FileAccess.get_sha256(path) == digest else ""
|
||||
|
||||
func capture(label: String, runtime: Variant, view: WorldPreview) -> bool:
|
||||
if manifest == null:
|
||||
return false
|
||||
var state: Dictionary = runtime.create_snapshot()
|
||||
var report := view.get_life_observation_report()
|
||||
var state_hash := object(state)
|
||||
var report_hash := object(report)
|
||||
var audit_hash := object(rest_audit(runtime))
|
||||
var motion_hash := object({"frames": observer.frames.duplicate(true) if is_instance_valid(observer) else []})
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
var after_hash := object(after)
|
||||
if state_hash.is_empty() or report_hash.is_empty() or audit_hash.is_empty():
|
||||
return false
|
||||
manifest.store_line(JSON.stringify({"label": label, "pid": OS.get_process_id(),
|
||||
"ticks_usec": Time.get_ticks_usec(), "state": state_hash, "report": report_hash,
|
||||
"rest_audit": audit_hash, "motion": motion_hash, "state_after": after_hash,
|
||||
"input_method": "diagnostic", "acceptance": "pending"}))
|
||||
manifest.flush()
|
||||
return var_to_bytes(state) == var_to_bytes(after)
|
||||
|
||||
func close() -> void:
|
||||
if manifest != null:
|
||||
manifest.close()
|
||||
manifest = null
|
||||
|
||||
# Public selection inputs; Runtime output is only a comparison operand.
|
||||
func rest_audit(runtime: Variant) -> Dictionary:
|
||||
var rows: Array = []
|
||||
for member: StaffRuntime in runtime.staff_service.get_staff_members():
|
||||
var memberships: Array = []
|
||||
for raid_id: StringName in runtime.defense_service.get_raid_ids():
|
||||
var raid: Dictionary = runtime.defense_service.get_raid(raid_id)
|
||||
if raid.get(&"combatants", {}).has(member.staff_id):
|
||||
memberships.append({"raid_id": raid_id, "state": DefenseService.RaidState.keys()[raid[&"state"]],
|
||||
"side": raid[&"combatants"][member.staff_id].get(&"side", &"")})
|
||||
var facilities: Array = []
|
||||
for room: FacilityInstance in runtime.facility_service.get_instances():
|
||||
var probes: Array = []
|
||||
var candidates: Dictionary = {}
|
||||
var occupied := room.occupied_cells.duplicate()
|
||||
if occupied.is_empty(): occupied.append(room.origin)
|
||||
for cell: Vector3i in occupied:
|
||||
for delta: Vector3i in [Vector3i.ZERO, Vector3i.LEFT, Vector3i.RIGHT]:
|
||||
var present: bool = runtime.path_service.has_cell(cell + delta)
|
||||
probes.append({"cell": cell + delta, "has_cell": present})
|
||||
if present: candidates[cell + delta] = true
|
||||
var ordered: Array = candidates.keys()
|
||||
ordered.sort_custom(func(a: Vector3i, b: Vector3i) -> bool:
|
||||
if a.z != b.z: return a.z < b.z
|
||||
if a.y != b.y: return a.y < b.y
|
||||
return a.x < b.x)
|
||||
var routes: Array = []
|
||||
for cell: Vector3i in ordered:
|
||||
var path: Array = runtime.path_service.find_path(member.current_cell, cell,
|
||||
[WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"])
|
||||
var edges: Array = []
|
||||
for index: int in range(1, path.size()):
|
||||
# Empty reports remain explicit mismatch evidence.
|
||||
edges.append(runtime.path_service.get_edge_report(path[index - 1], path[index], [&"staff"]))
|
||||
routes.append({"cell": cell, "path": path, "edges": edges,
|
||||
"calculate_path_cost": runtime.path_service.calculate_path_cost(path, [&"staff"]) if not path.is_empty() else -1})
|
||||
facilities.append({"instance_id": room.instance_id, "definition_id": room.definition_id,
|
||||
"operation_state": FacilityInstance.OperationState.keys()[room.operation_state],
|
||||
"origin": room.origin, "occupied_cells": room.occupied_cells.duplicate(),
|
||||
"probes": probes, "ordered": ordered, "routes": routes})
|
||||
rows.append({"staff_id": member.staff_id, "state": StaffRuntime.State.keys()[member.state],
|
||||
"health": member.health, "current_task_id": member.current_task_id, "current_cell": member.current_cell,
|
||||
"defense_memberships": memberships, "actor_lock": runtime.event_service.get_actor_locks().has(member.staff_id),
|
||||
"in_transit": runtime.is_staff_in_transit(member.staff_id), "facilities": facilities,
|
||||
"comparison_only": runtime.get_staff_rest_report(member.staff_id)})
|
||||
return {"staff": rows, "path_service_sha256": FileAccess.get_sha256("res://scripts/world/path_service.gd")}
|
||||
|
||||
func record(label: String, value: Dictionary) -> bool:
|
||||
if manifest == null: return false
|
||||
var digest := object(value)
|
||||
if digest.is_empty(): return false
|
||||
manifest.store_line(JSON.stringify({"label": label, "pid": OS.get_process_id(),
|
||||
"ticks_usec": Time.get_ticks_usec(), "object": digest}))
|
||||
manifest.flush()
|
||||
return true
|
||||
@@ -0,0 +1 @@
|
||||
uid://d23lifestr03
|
||||
@@ -0,0 +1,102 @@
|
||||
extends RefCounted
|
||||
## Public-command experiment; no synthetic state, costs or capacities.
|
||||
## Predeclared metric: total destination-arrival minutes of the entire cohort.
|
||||
## B must be strictly smaller. Incomplete cohorts cannot establish improvement.
|
||||
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
||||
const SEED := 806042
|
||||
const WINDOW := 480
|
||||
const START := Vector3i(2, 0, 0)
|
||||
const GOAL := Vector3i(2, 1, 0)
|
||||
var stream: RefCounted
|
||||
|
||||
func declaration() -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = SEED
|
||||
var arrivals: Array[Dictionary] = []
|
||||
for index: int in range(48):
|
||||
arrivals.append({&"id": StringName("p23-%03d" % index),
|
||||
&"offset": (index / 4) * 15 + rng.randi_range(0, 2)})
|
||||
arrivals.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
return a[&"offset"] < b[&"offset"] or (a[&"offset"] == b[&"offset"] and String(a[&"id"]) < String(b[&"id"])))
|
||||
return {&"seed": SEED, &"initial_clock": [1, 6, 0], &"budget": &"GameState.reset",
|
||||
&"production": &"Runtime.initialize defaults; production operations not enabled",
|
||||
&"arrivals": arrivals, &"from": START, &"to": GOAL, &"window": WINDOW,
|
||||
&"metric": &"sum_destination_arrival_minutes_entire_cohort",
|
||||
&"improvement": {&"definition_id": &"guest_elevator", &"origin": Vector3i(4, 0, 0)}}
|
||||
|
||||
func run(tree: SceneTree, directory: String, layout: String) -> bool:
|
||||
if layout not in ["A", "B"]: return false
|
||||
var global_clock := tree.root.get_node_or_null("GameClock")
|
||||
if global_clock != null: global_clock.set_paused(true)
|
||||
stream = Stream.new()
|
||||
if not stream.open(directory): return false
|
||||
var input := declaration()
|
||||
if not stream.record("declaration", input):
|
||||
stream.close()
|
||||
return false
|
||||
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
|
||||
var clock: Variant = load("res://autoload/game_clock.gd").new()
|
||||
var state: Variant = load("res://autoload/game_state.gd").new()
|
||||
clock.reset(1, 6, 0)
|
||||
state.reset()
|
||||
var ok: bool = runtime.initialize(clock, state, SEED)[&"valid"]
|
||||
clock.set_paused(true)
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
ok = stream.record("initial", runtime.create_snapshot()) and ok
|
||||
var builds: Array = [[&"guest_elevator", Vector3i.ZERO],
|
||||
[&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)]]
|
||||
if layout == "B": builds.append([input[&"improvement"][&"definition_id"], input[&"improvement"][&"origin"]])
|
||||
var construction_minutes := 0
|
||||
# Identical preparation time derived from the same registered definitions.
|
||||
for id: StringName in [&"guest_elevator", &"guest_corridor"]:
|
||||
construction_minutes = maxi(construction_minutes, runtime.definition_registry.get_facility(id).build_minutes)
|
||||
for index: int in range(builds.size()):
|
||||
var payload := {&"definition_id": builds[index][0], &"origin": builds[index][1]}
|
||||
var result: CommandResult = runtime.submit_command(&"build_facility", payload,
|
||||
&"player", &"", 0, clock.absolute_minute, StringName("p23.build.%d" % index))
|
||||
ok = stream.record("build-%d" % index, {&"payload": payload, &"result": result.to_dictionary()}) and result.is_success() and ok
|
||||
clock.advance_minutes(construction_minutes + 1)
|
||||
ok = stream.record("prepared", runtime.create_snapshot()) and ok
|
||||
runtime.domain_events.subscribe(&"*", &"p23.capture", Callable(self, "_event"))
|
||||
var view := WorldPreview.new()
|
||||
view.size = Vector2(1440, 810)
|
||||
view.set_runtime(runtime)
|
||||
tree.root.add_child(view)
|
||||
stream.watch(view)
|
||||
var finished: Dictionary = {}
|
||||
for offset: int in range(WINDOW + 1):
|
||||
if not ok: break
|
||||
if offset > 0: clock.advance_minutes(1)
|
||||
for arrival: Dictionary in input[&"arrivals"]:
|
||||
if arrival[&"offset"] != offset: continue
|
||||
var payload := {&"guest_id": arrival[&"id"], &"guest_type_id": &"traveler", &"gold": 100,
|
||||
&"options": {&"current_cell": START, &"state": GuestRuntime.State.MOVING,
|
||||
&"state_entered_at": clock.absolute_minute - 1, &"planned_exit_at": clock.absolute_minute + 2000,
|
||||
&"path_handle": {&"target_cell": GOAL}}}
|
||||
var result: CommandResult = runtime.submit_command(&"spawn_guest", payload,
|
||||
&"world", arrival[&"id"], 10, -1, StringName("p23.spawn.%s" % arrival[&"id"]))
|
||||
ok = stream.record("arrival-%s" % arrival[&"id"], {&"offset": offset, &"payload": payload, &"result": result.to_dictionary()}) and result.is_success() and ok
|
||||
# Typed per-minute projections retain paths, queues, density and actual
|
||||
# completed services. Whole aggregate snapshots are only sampled sparsely.
|
||||
var guest_records: Dictionary = runtime.guest_service.create_snapshot()
|
||||
ok = stream.record("tick-%03d" % offset, {&"minute": clock.absolute_minute,
|
||||
&"guests": guest_records, &"path": runtime.path_service.create_snapshot(),
|
||||
&"elevators": runtime.elevators.create_snapshot(), &"facilities": runtime.facility_service.create_snapshot()}) and ok
|
||||
for guest: Dictionary in guest_records[&"guests"]:
|
||||
if guest[&"current_cell"] != [GOAL.x, GOAL.y, GOAL.z] or finished.has(guest[&"guest_id"]): continue
|
||||
finished[guest[&"guest_id"]] = offset
|
||||
if offset % 30 == 0:
|
||||
var drawn: bool = await stream.await_draws(tree, view)
|
||||
ok = drawn and stream.capture("frame-%03d" % offset, runtime, view) and ok
|
||||
ok = stream.record("first-arrivals", {&"offsets": finished}) and ok
|
||||
ok = stream.record("final", runtime.create_snapshot()) and ok
|
||||
stream.close()
|
||||
view.free()
|
||||
runtime.free()
|
||||
clock.free()
|
||||
state.free()
|
||||
return ok
|
||||
|
||||
func _event(event: SimulationDomainEvent) -> void:
|
||||
stream.record("event", {&"type": event.event_type, &"minute": event.absolute_minute,
|
||||
&"entities": event.entity_ids, &"payload": event.payload.duplicate(true)})
|
||||
@@ -0,0 +1 @@
|
||||
uid://dtxpm0n6cfom
|
||||
@@ -0,0 +1,193 @@
|
||||
extends "res://tests/fixtures/p08-03/placement.gd"
|
||||
## Real Runtime commands; inherited formal codec/save-operation helpers unchanged.
|
||||
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
||||
const Comparison := preload("res://tests/fixtures/p23-03/layout_comparison.gd")
|
||||
const PurchaseQuoteV2 := preload("res://scripts/supply/supply_purchase_quote_v2.gd")
|
||||
var stream: RefCounted
|
||||
var layout := "A"
|
||||
var facility_ids: Dictionary = {}
|
||||
|
||||
func setup() -> void:
|
||||
clock.reset(1, 6, 0)
|
||||
var state: Variant = tree.root.get_node("GameState")
|
||||
state.reset()
|
||||
expect(runtime.initialize(clock, state, 806042).get(&"valid", false), "initialize observation")
|
||||
clock.set_paused(true)
|
||||
tree.root.size = Vector2i(1440, 810)
|
||||
layout = OS.get_environment("P23_LAYOUT")
|
||||
expect(layout in ["A", "B"], "declared layout A or B")
|
||||
main = load("res://scenes/main/main.tscn").instantiate()
|
||||
main.manual_save_path = "user://saves/manual_ui"
|
||||
if role == "reader":
|
||||
import_package(directory.path_join("formal"), String(main.manual_save_path))
|
||||
tree.root.add_child(main)
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
stream = Stream.new()
|
||||
expect(stream.open(directory.path_join(role + "-stream")), "fresh observation stream")
|
||||
stream.watch(main.world_preview)
|
||||
await frames()
|
||||
|
||||
func command(id: StringName, payload: Dictionary) -> bool:
|
||||
var result: CommandResult = runtime.submit_command(id, payload)
|
||||
expect(result.is_success(), String(id) + " " + str(payload) + ": " + str(result.code))
|
||||
expect(stream.record("command", {&"command": id, &"payload": payload,
|
||||
&"result": result.to_dictionary(), &"minute": clock.absolute_minute}), "record public command")
|
||||
return result.is_success()
|
||||
|
||||
func advance(minutes: int) -> void:
|
||||
for unused: int in range(minutes):
|
||||
clock.advance_minutes(1)
|
||||
await frames()
|
||||
await observe_progress()
|
||||
if clock.absolute_minute % 30 == 0:
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture("minute-%d" % clock.absolute_minute, runtime, main.world_preview), "sparse minute observation")
|
||||
|
||||
func observe_progress() -> void:
|
||||
pass
|
||||
|
||||
func prepare() -> void:
|
||||
# Both layouts receive the same runtime-derived budget for all setup spends.
|
||||
# Cafeteria/kitchen move together to x=6, leaving x=4..5 for the declared B EV.
|
||||
var builds: Array = [[&"reception", Vector3i(-8, 0, 0)],
|
||||
[&"cafeteria", Vector3i(6, 0, 0)], [&"warehouse", Vector3i(-8, 0, 1)],
|
||||
[&"kitchen", Vector3i(6, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)],
|
||||
[&"standard_room", Vector3i(-8, 2, 0)], [&"guest_stairs", Vector3i(-4, 1, 0)],
|
||||
[&"guest_stairs", Vector3i(-4, 2, 0)], [&"guest_corridor", Vector3i(-5, 2, 0)],
|
||||
[&"guest_corridor", Vector3i(-6, 2, 0)], [&"guest_elevator", Vector3i.ZERO],
|
||||
[&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)],
|
||||
[&"staff_waiting_room", Vector3i(-4, 0, 1)]]
|
||||
var improvement: Dictionary = Comparison.new().declaration()[&"improvement"]
|
||||
var budget_builds := builds.duplicate(true)
|
||||
budget_builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
||||
var assignments: Array = [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"],
|
||||
[&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]
|
||||
var purchase_quantity := 40
|
||||
var needed := {&"gold": 0, &"materials": 0}
|
||||
var costs: Array = []
|
||||
for row: Array in budget_builds:
|
||||
var definition: FacilityDefinition = runtime.definition_registry.get_facility(row[0])
|
||||
costs.append({&"definition": row[0], &"cost": definition.construction_cost.duplicate(true)})
|
||||
for key: StringName in needed:
|
||||
needed[key] += int(definition.construction_cost.get(key, 0))
|
||||
var construction_total := needed.duplicate(true)
|
||||
var signing_costs: Array = []
|
||||
var signing_total := 0
|
||||
for assignment: Array in assignments:
|
||||
var candidate: Dictionary = runtime.mvp_content_catalog.get_recruitment_entry(assignment[0])
|
||||
expect(candidate.has(&"signing_cost"), "registered signing cost " + String(assignment[0]))
|
||||
var signing_cost := int(candidate[&"signing_cost"])
|
||||
signing_costs.append({&"candidate_id": assignment[0], &"signing_cost": signing_cost,
|
||||
&"source": "MvpContentCatalog.get_recruitment_entry.signing_cost"})
|
||||
signing_total += signing_cost
|
||||
needed[&"gold"] += signing_total
|
||||
# Before warehouse construction, use the purchase service's catalog/quote
|
||||
# branch. Check this total against its live preview immediately before buying.
|
||||
var item := SupplyCatalog.get_item(SupplyCatalog.RAW_FOOD)
|
||||
var purchase_quote := PurchaseQuoteV2.make(SupplyCatalog.RAW_FOOD, purchase_quantity, int(item[&"unit_price"]))
|
||||
expect(not purchase_quote.is_empty(), "registered raw-food purchase quote")
|
||||
var purchase_total := int(purchase_quote[&"effective_total"] if PurchaseQuoteV2.eligible(runtime) else purchase_quote[&"base_total"])
|
||||
needed[&"gold"] += purchase_total
|
||||
var purchase_cost := {&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": purchase_quantity,
|
||||
&"unit_price": item[&"unit_price"], &"quote": purchase_quote, &"total_cost": purchase_total,
|
||||
&"source": "SupplyCatalog.unit_price / PurchaseQuoteV2.make; preview_purchase eligibility"}
|
||||
var state: Variant = tree.root.get_node("GameState")
|
||||
var deltas := {}
|
||||
for key: StringName in needed:
|
||||
if needed[key] > state.get_resource(key):
|
||||
deltas[key] = needed[key] - state.get_resource(key)
|
||||
budget = {"setup": "diagnostic", "costs": costs, "needed": needed,
|
||||
"initial": state.resources.duplicate(true), "deltas": deltas,
|
||||
"construction_total": construction_total, "signing_costs": signing_costs,
|
||||
"signing_total": signing_total, "purchase_cost": purchase_cost}
|
||||
if not deltas.is_empty(): command(&"change_resources", {&"deltas": deltas})
|
||||
expect(stream.record("budget", budget), "common diagnostic budget")
|
||||
if layout == "B": builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
||||
# Preserve the supply scenario's staged support/construction sequence.
|
||||
for index: int in range(builds.size()):
|
||||
if index == 5: await advance(180)
|
||||
if index == 7: await advance(60)
|
||||
if index == 8: await advance(10)
|
||||
command(&"build_facility", {&"definition_id": builds[index][0], &"origin": builds[index][1]})
|
||||
var transport_minutes := 0
|
||||
for id: StringName in [&"guest_elevator", &"guest_corridor"]:
|
||||
transport_minutes = maxi(transport_minutes, runtime.definition_registry.get_facility(id).build_minutes)
|
||||
await advance(maxi(10, transport_minutes))
|
||||
for facility: FacilityInstance in runtime.facility_service.get_instances():
|
||||
facility_ids[facility.definition_id] = facility.instance_id
|
||||
command(&"set_facility_conditions", {&"facility_id": facility_ids[&"cafeteria"],
|
||||
&"conditions": {&"related_facility_id": facility_ids[&"kitchen"]}})
|
||||
for assignment: Array in assignments:
|
||||
command(&"hire_staff_candidate", {&"candidate_id": assignment[0]})
|
||||
command(&"set_staff_work_assignment", {&"staff_id": assignment[0],
|
||||
&"facility_id": facility_ids[assignment[1]], &"template_id": &"DAY"})
|
||||
var actual_quote: Dictionary = runtime.preview_supply_purchase(facility_ids[&"warehouse"], SupplyCatalog.RAW_FOOD, purchase_quantity)
|
||||
expect(int(actual_quote[&"total_cost"]) == purchase_total, "budget matches actual purchase quote")
|
||||
expect(stream.record("setup-purchase-quote", actual_quote), "record actual setup purchase quote")
|
||||
command(&"purchase_supplies", {&"warehouse_id": facility_ids[&"warehouse"],
|
||||
&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": purchase_quantity})
|
||||
command(&"set_kitchen_production", {&"facility_id": facility_ids[&"kitchen"],
|
||||
&"enabled": true, &"target_stock": 8})
|
||||
# The OS observation uses the already declared transport cohort, plus
|
||||
# ordinary public arrivals whose service choice remains Runtime-owned.
|
||||
# These commands are recorded and identical across A/B; no actor is moved,
|
||||
# despawned, or inserted into a room/queue by the fixture.
|
||||
var demand: Dictionary = Comparison.new().declaration()
|
||||
expect(stream.record("observation-demand", {"cohort": demand[&"arrivals"],
|
||||
"schedule": "all observation actors at the recorded current minute; offsets apply only to native layout comparison",
|
||||
"minute": clock.absolute_minute}), "declared observation demand")
|
||||
for arrival: Dictionary in demand[&"arrivals"]:
|
||||
command(&"spawn_guest", {&"guest_id": StringName("os-" + String(arrival[&"id"])),
|
||||
&"guest_type_id": &"traveler", &"gold": 100,
|
||||
&"options": {&"current_cell": Comparison.START, &"state": GuestRuntime.State.MOVING,
|
||||
&"state_entered_at": clock.absolute_minute - 1, &"planned_exit_at": clock.absolute_minute + 2000,
|
||||
&"path_handle": {&"target_cell": Comparison.GOAL}}})
|
||||
command(&"spawn_guest", {&"guest_id": StringName("service-" + String(arrival[&"id"])),
|
||||
&"guest_type_id": &"traveler", &"gold": 100})
|
||||
await advance(120)
|
||||
|
||||
func layer_observations(prefix: String) -> void:
|
||||
await button(main.front_button)
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture(prefix + "-FRONT", runtime, main.world_preview), "FRONT state/report")
|
||||
await button(main.back_button)
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture(prefix + "-BACK", runtime, main.world_preview), "BACK state/report")
|
||||
|
||||
func writer() -> void:
|
||||
await prepare()
|
||||
await layer_observations("writer")
|
||||
capture("presave")
|
||||
await button(main.find_child("ManualSaveButton", true, false))
|
||||
expect(main.build_status_label.text == "手動セーブを保存しました。", "normal save")
|
||||
capture("checkpoint")
|
||||
export_package(String(main.manual_save_path), "formal")
|
||||
var formal := inspect_formal()
|
||||
var presave := FileAccess.get_file_as_bytes(directory.path_join("writer-presave.var"))
|
||||
expect(presave == var_to_bytes(formal), "whole presave formal model")
|
||||
expect(not operation_results.is_empty(), "save result recorded")
|
||||
if not operation_results.is_empty():
|
||||
verify_operation(formal, operation_results.back(), &"SAVE_CHECKPOINT_SUCCEEDED")
|
||||
var members := {}
|
||||
for member: String in MEMBERS:
|
||||
members[member] = FileAccess.get_sha256(directory.path_join("formal/" + member))
|
||||
json_file("save.json", {"pid": OS.get_process_id(), "members": members})
|
||||
|
||||
func reader(distinct: bool) -> void:
|
||||
var saved: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(directory.path_join("save.json")))
|
||||
expect(distinct and saved.pid != OS.get_process_id(), "distinct actual reader PID")
|
||||
await load_menu(main.find_child("SaveLoadMenu", true, false))
|
||||
await frames()
|
||||
expect(main.build_status_label.text == "手動セーブを読み込みました。", "normal resume")
|
||||
capture("checkpoint")
|
||||
var formal := inspect_formal()
|
||||
expect(FileAccess.get_file_as_bytes(directory.path_join("writer-formal-model.var")) == var_to_bytes(formal), "whole formal resume model")
|
||||
for member: String in MEMBERS:
|
||||
expect(FileAccess.get_sha256(directory.path_join("formal/" + member)) == saved.members[member], "formal member " + member)
|
||||
expect(not operation_results.is_empty(), "load result recorded")
|
||||
if not operation_results.is_empty():
|
||||
verify_operation(formal, operation_results.back(), &"SAVE_RECOVERY_LOADED")
|
||||
await layer_observations("reader")
|
||||
|
||||
func finish_observations() -> void:
|
||||
stream.close()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmoi8je5hse38
|
||||
@@ -0,0 +1,266 @@
|
||||
extends "res://tests/fixtures/p23-03/observation.gd"
|
||||
## Passive endpoint implementation. All gameplay/input is performed by Root CUA.
|
||||
## Shared parent contains the handler endpoints, but every such endpoint below
|
||||
## is overridden; this class never emits UI signals or invokes product handlers.
|
||||
var receipts: Array = []
|
||||
var terminated := false
|
||||
var terminal_record: Dictionary = {}
|
||||
var terminal_cause_recorded := false
|
||||
var reveal_rows: Array = []
|
||||
var all_requests: Array = []
|
||||
var input_observer: Node
|
||||
var last_dispatch: Dictionary = {}
|
||||
var observed_categories: Dictionary = {}
|
||||
|
||||
func observe_progress() -> void:
|
||||
if role != "writer" or terminated: return
|
||||
var wanted: Dictionary = {}
|
||||
var report: Dictionary = main.world_preview.get_life_observation_report()
|
||||
for room: Dictionary in report.get(&"rooms", {}).values():
|
||||
if int(room.get(&"count", 0)) > 0: wanted["room-users"] = true
|
||||
for badge: Dictionary in report.get(&"landing_badges", []):
|
||||
if int(badge.get(&"overflow", 0)) > 0: wanted["queue-overflow"] = true
|
||||
for cabin: Dictionary in report.get(&"cabins", []):
|
||||
if not cabin[&"passengers"].is_empty(): wanted["EV-passengers"] = true
|
||||
var elevators: Dictionary = runtime.elevators.get_report()
|
||||
for shaft: Dictionary in elevators.get(&"shafts", []):
|
||||
if not shaft.get(&"boarded", []).is_empty(): wanted["EV-passengers"] = true
|
||||
for trip: Dictionary in elevators.get(&"trips", []):
|
||||
if trip.get(&"state", &"") == &"QUEUED": wanted["queue-present"] = true
|
||||
for member: StaffRuntime in runtime.staff_service.get_staff_members():
|
||||
if not member.carried_inventory_id.is_empty(): wanted["carried-goods"] = true
|
||||
if not report.get(&"resting_staff", {}).is_empty(): wanted["rest-position"] = true
|
||||
for category: String in wanted:
|
||||
if observed_categories.has(category): continue
|
||||
observed_categories[category] = true
|
||||
await layer_observations("actual-" + category)
|
||||
if category == "EV-passengers": await motion_observations()
|
||||
|
||||
func motion_observations() -> void:
|
||||
await button(main.front_button)
|
||||
await button(main.pause_button)
|
||||
expect(not clock.paused, "ordinary resume for motion observation")
|
||||
var minute: int = clock.absolute_minute
|
||||
while clock.absolute_minute == minute and not terminated:
|
||||
await tree.process_frame
|
||||
await frames()
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture("active-motion", runtime, main.world_preview), "active interpolation history")
|
||||
await button(main.pause_button)
|
||||
expect(clock.paused, "ordinary pause for stable observation")
|
||||
await frames()
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture("paused-motion", runtime, main.world_preview), "paused interpolation history")
|
||||
|
||||
class InputObserver extends Node:
|
||||
var events: Array = []
|
||||
var endpoint: RefCounted = null
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton or event is InputEventKey or event is InputEventMouseMotion:
|
||||
var row := {"ticks_usec": Time.get_ticks_usec(), "text": event.as_text(), "pressed": event.is_pressed()}
|
||||
if event is InputEventMouseButton:
|
||||
row["button"] = event.button_index
|
||||
row["position"] = [event.position.x, event.position.y]
|
||||
row["ctrl"] = event.ctrl_pressed
|
||||
if endpoint != null and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
row["dispatch"] = endpoint.dispatch_position(event.position)
|
||||
events.append(row)
|
||||
|
||||
func expect(ok: bool, message: String) -> void:
|
||||
if terminated and ok:
|
||||
return
|
||||
if terminated and terminal_cause_recorded:
|
||||
if not ok:
|
||||
terminal_record["suppressed_after_terminal"] += 1
|
||||
json_file(role + "-terminal.json", terminal_record)
|
||||
return
|
||||
if terminated and not ok:
|
||||
terminal_cause_recorded = true
|
||||
super.expect(ok, message)
|
||||
|
||||
func terminate(stem: String, reason: String) -> void:
|
||||
terminated = true
|
||||
terminal_record = {"pid": OS.get_process_id(), "role": role, "sequence": serial,
|
||||
"stem": stem, "reason": reason, "suppressed_after_terminal": 0}
|
||||
json_file(role + "-terminal.json", terminal_record)
|
||||
expect(false, reason)
|
||||
tree.quit(1)
|
||||
|
||||
func request(action: Dictionary) -> void:
|
||||
if terminated:
|
||||
return
|
||||
serial += 1
|
||||
var stem := "%s-request-%04d" % [role, serial]
|
||||
var folder := directory.path_join(stem)
|
||||
DirAccess.make_dir_recursive_absolute(folder)
|
||||
var window: Window = main.get_window()
|
||||
var view: WorldPreview = main.world_preview
|
||||
expect(await stream.await_draws(tree, view), "real draw history before capture")
|
||||
action["report"] = Codec.new().typed(view.get_life_observation_report())
|
||||
action["viewport_to_screen"] = affine(view.get_screen_transform() * view.get_global_transform_with_canvas().affine_inverse())
|
||||
action["local_to_viewport"] = affine(view.get_global_transform_with_canvas())
|
||||
action["local_to_screen"] = affine(view.get_screen_transform())
|
||||
action["rectangles"] = Codec.new().typed(draw_rectangles(view.get_life_observation_report(), view))
|
||||
action["requested_ticks_usec"] = Time.get_ticks_usec()
|
||||
action["requested_unix_ns"] = int(Time.get_unix_time_from_system() * 1000000000.0)
|
||||
action["screenshot_receipt_schema"] = {"screenshots": [{"path": "evidence-root-relative raw image path", "raw_sha256": "original byte SHA256", "tool_call_id": "actual CUA screenshot call ID"}]}
|
||||
expect(stream.capture(stem, runtime, view), "request state/report")
|
||||
action.merge({"sequence": serial, "target_pid": OS.get_process_id(), "role": role,
|
||||
"state": "waiting_for_root_cua", "window_title": window.title,
|
||||
"window_position": [window.position.x, window.position.y],
|
||||
"window_size": [window.size.x, window.size.y], "screen_scale": DisplayServer.screen_get_scale(window.current_screen),
|
||||
"content_scale_factor": window.content_scale_factor, "setup": "diagnostic"})
|
||||
json_file(stem + "/request.json", action)
|
||||
var path := folder.path_join("ack.json")
|
||||
var first_event: int = input_observer.events.size()
|
||||
var deadline := Time.get_ticks_msec() + 300000
|
||||
print("P23_ROOT_CUA_WAIT " + folder.path_join("request.json"))
|
||||
while not FileAccess.file_exists(path) and Time.get_ticks_msec() < deadline:
|
||||
await tree.process_frame
|
||||
if not FileAccess.file_exists(path):
|
||||
terminate(stem, "Root CUA acknowledgement timeout")
|
||||
return
|
||||
var ack: Variant = JSON.parse_string(FileAccess.get_file_as_string(path))
|
||||
if not ack is Dictionary or ack.get("target_pid") != OS.get_process_id() or ack.get("sequence") != serial or ack.get("request_sha256") != FileAccess.get_sha256(folder.path_join("request.json")):
|
||||
terminate(stem, "Root CUA acknowledgement identity/hash")
|
||||
return
|
||||
await frames()
|
||||
for event: Dictionary in input_observer.events.slice(first_event):
|
||||
if event.has("dispatch"):
|
||||
last_dispatch = event["dispatch"]
|
||||
json_file(stem + "/observed.json", {"pid": OS.get_process_id(), "sequence": serial,
|
||||
"ack_observed_ticks_usec": Time.get_ticks_usec(), "request_sha256": FileAccess.get_sha256(folder.path_join("request.json")),
|
||||
"ack_observed_unix_ns": int(Time.get_unix_time_from_system() * 1000000000.0),
|
||||
"observer_endpoint": "null_modal_receipts_only" if input_observer.endpoint == null else "passive_read_only_dispatch",
|
||||
"events": input_observer.events.slice(first_event), "status": main.build_status_label.text})
|
||||
receipts.append("os-pair/" + layout + "/" + stem + "/ack.json")
|
||||
all_requests.append("os-pair/" + layout + "/" + stem + "/ack.json")
|
||||
|
||||
func annotate(row: Dictionary) -> void:
|
||||
var view: WorldPreview = main.world_preview
|
||||
row["rectangles"] = Codec.new().typed(draw_rectangles(view.get_life_observation_report(), view))
|
||||
row["viewport_to_screen"] = affine(view.get_screen_transform() * view.get_global_transform_with_canvas().affine_inverse())
|
||||
row["reveal"] = reveal_rows.duplicate(true)
|
||||
reveal_rows.clear()
|
||||
row["input_method"] = "native_os"
|
||||
row["requests"] = receipts.duplicate()
|
||||
receipts.clear()
|
||||
|
||||
# Rectangles describe existing draw commands; no second renderer is invoked.
|
||||
func draw_rectangles(report: Dictionary, view: WorldPreview) -> Dictionary:
|
||||
var rows: Array = []
|
||||
for id: Variant in report.get(&"rooms", {}):
|
||||
var room: Dictionary = report[&"rooms"][id]
|
||||
if room.has(&"rect"):
|
||||
rows.append({"category": "room", "id": id, "rect": room[&"rect"], "style_source": "scripts/ui/pixel_tower_art.gd"})
|
||||
for index: int in range(report.get(&"landing_badges", []).size()):
|
||||
var badge: Dictionary = report[&"landing_badges"][index]
|
||||
rows.append(badge_rectangle("queue", str(index), badge[&"at"], badge[&"text"]))
|
||||
for cabin: Dictionary in report.get(&"cabins", []):
|
||||
rows.append({"category": "EV", "id": cabin[&"shaft_id"], "rect": cabin[&"rect"], "style_source": "scripts/ui/pixel_tower_art.gd"})
|
||||
for actor: Dictionary in cabin[&"passengers"]:
|
||||
rows.append(actor_rectangle(actor))
|
||||
for actor: Dictionary in report.get(&"actor_drawings", []):
|
||||
rows.append(actor_rectangle(actor))
|
||||
if actor[&"staff"] and report[&"resting_staff"].has(StringName(String(actor[&"key"]).trim_prefix("staff/"))):
|
||||
var rest := actor_rectangle(actor)
|
||||
rest["category"] = "rest"
|
||||
rows.append(rest)
|
||||
for index: int in range(report.get(&"actor_aggregations", []).size()):
|
||||
var badge: Dictionary = report[&"actor_aggregations"][index]
|
||||
rows.append(badge_rectangle("aggregation", str(index), badge[&"at"], badge[&"text"]))
|
||||
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)
|
||||
row["screen_rect"] = view.get_screen_transform() * (row["rect"] as Rect2)
|
||||
var source: String = FileAccess.get_file_as_string("res://" + row["style_source"])
|
||||
var regex := RegEx.new()
|
||||
regex.compile('Color\\("([0-9a-fA-F]{6})"')
|
||||
var colors: Array = []
|
||||
for found: RegExMatch in regex.search_all(source):
|
||||
if not colors.has(found.get_string(1)): colors.append(found.get_string(1))
|
||||
row["style_colors"] = colors
|
||||
return {"rows": rows}
|
||||
|
||||
func badge_rectangle(category: String, id: String, at: Vector2, text: String) -> Dictionary:
|
||||
var width := ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x + 6
|
||||
return {"category": category, "id": id, "rect": Rect2(at, Vector2(text.length() * 10 + 6, 14)),
|
||||
"text": text, "font_width": width - 6, "style_source": "scenes/main/world_preview.gd"}
|
||||
|
||||
func actor_rectangle(actor: Dictionary) -> Dictionary:
|
||||
return {"category": "staff" if actor[&"staff"] else "guest", "id": actor[&"key"],
|
||||
"rect": Rect2(actor[&"at"], Vector2(16, 18) * float(actor[&"scale"])), "style_source": "scripts/ui/pixel_people.gd"}
|
||||
|
||||
func point(node: Control) -> Dictionary:
|
||||
var viewport: Vector2 = node.get_global_transform_with_canvas() * (node.size * 0.5)
|
||||
var screen: Vector2 = node.get_screen_transform() * (node.size * 0.5)
|
||||
return {"node": String(node.get_path()), "viewport": [viewport.x, viewport.y], "screen": [screen.x, screen.y], "window_id": node.get_window().get_window_id(),
|
||||
"viewport_to_screen": affine(node.get_screen_transform() * node.get_global_transform_with_canvas().affine_inverse())}
|
||||
|
||||
func setup() -> void:
|
||||
await super.setup()
|
||||
input_method = "native_os"
|
||||
expect(DisplayServer.get_name() != "headless", "real OS display")
|
||||
tree.root.title = "The Tree P23-03 " + layout + " " + role + " PID " + str(OS.get_process_id())
|
||||
input_observer = InputObserver.new()
|
||||
# No handler dispatch: this observer only records incoming OS events.
|
||||
tree.root.add_child(input_observer)
|
||||
json_file(role + "-profile.json", {"pid": OS.get_process_id(),
|
||||
"user_data_dir": OS.get_user_data_dir(), "engine": Engine.get_version_info(),
|
||||
"display": DisplayServer.get_name()})
|
||||
|
||||
func button(node: BaseButton) -> void:
|
||||
expect(node != null and not node.disabled, "reachable ordinary button")
|
||||
if node == null or node.disabled: return
|
||||
await request({"action": "click", "point": point(node), "button_text": str(node.get("text"))})
|
||||
|
||||
func load_menu(menu: MenuButton) -> void:
|
||||
var action := point(menu)
|
||||
action.merge({"action": "load_menu", "item_id": 0, "instructions": "Open this normal menu and choose manual load (item 0)."})
|
||||
await request(action)
|
||||
|
||||
func screenshot(label: String) -> void:
|
||||
await frames()
|
||||
await request({"action": "screenshot", "id": label, "layout": layout})
|
||||
|
||||
func layer_observations(prefix: String) -> void:
|
||||
if role == "writer" and prefix == "writer": await screenshot("calibration")
|
||||
await button(main.front_button)
|
||||
await screenshot(prefix + "-FRONT")
|
||||
await button(main.back_button)
|
||||
await screenshot(prefix + "-BACK")
|
||||
if role == "writer" and prefix == "writer": await ordinary_camera()
|
||||
|
||||
func ordinary_camera() -> void:
|
||||
var view: WorldPreview = main.world_preview
|
||||
var at := point(view)
|
||||
expect(not view.pan_mode, "ordinary zoom starts with move mode off")
|
||||
var zoom: float = view.zoom_level
|
||||
await request({"action": "wheel", "point": at, "direction": "down",
|
||||
"ctrl": false, "move_mode": false})
|
||||
expect(view.zoom_level != zoom, "ordinary wheel changes zoom")
|
||||
await screenshot("ordinary-wheel-zoom")
|
||||
await button(main.pan_button)
|
||||
await screenshot("pan-mode-button")
|
||||
expect(view.pan_mode, "PanModeButton enables move mode")
|
||||
zoom = view.zoom_level
|
||||
var pan: Vector2 = view.pan_offset
|
||||
await request({"action": "wheel", "point": point(view), "direction": "up",
|
||||
"ctrl": false, "move_mode": true})
|
||||
expect(view.pan_offset != pan and view.zoom_level == zoom, "ordinary move-mode wheel pans")
|
||||
await screenshot("move-mode-wheel-pan")
|
||||
|
||||
func finish_observations() -> void:
|
||||
json_file(role + "-requests.json", {"requests": all_requests, "pid": OS.get_process_id()})
|
||||
if is_instance_valid(input_observer): input_observer.queue_free()
|
||||
super.finish_observations()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://d3oqtdp6dodu1
|
||||
@@ -0,0 +1,8 @@
|
||||
extends SceneTree
|
||||
const Passive := preload("res://tests/fixtures/p23-03/passive_observation.gd")
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
func run() -> void:
|
||||
var result: Dictionary = await Passive.new().run(self, OS.get_environment("P23_OBSERVATION_ROOT"), OS.get_environment("P23_OBSERVATION_ROLE"))
|
||||
print("P23 passive OS observation: %d checks, %d failures" % [result.checks, result.failures])
|
||||
quit(0 if result.failures == 0 else 1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://mn33trympqu6
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"provenance": "Distilled layout-A frame-030 state ac3ed64a6e6162918e4d44c45b2f78d09e50d473b607f3469350a06074a7efb3; retain first real queued trip, no cabin seat. Unit fixture only.",
|
||||
"saved": {
|
||||
"version": 1,
|
||||
"current_minute": 691,
|
||||
"request_sequence": 8,
|
||||
"shafts": [{
|
||||
"config": {
|
||||
"shaft_id": "transport.shaft.0.0.0.2",
|
||||
"module_ids": ["facility_00000003"],
|
||||
"stops": [[0, 0, 0], [0, 1, 0]],
|
||||
"width": 2, "capacity": 4, "travel_minutes_per_floor": 10, "access_id": ""
|
||||
},
|
||||
"operational": true, "operator_open": true, "powered": true,
|
||||
"current_floor": 1, "target_floor": 1, "boarding_floor": 0,
|
||||
"phase": "PICKUP", "remaining_minutes": 9, "phase_total_minutes": 10,
|
||||
"phase_started_at": 690, "phase_origin_floor": 1, "phase_passenger_count": 0,
|
||||
"boarded": []
|
||||
}],
|
||||
"trips": [{
|
||||
"actor_id": "guest:p23-000", "request_id": "transport.trip.000000000002",
|
||||
"shaft_id": "transport.shaft.0.0.0.2",
|
||||
"connector_id": "transport.shaft.0.0.0.2.link.0.1",
|
||||
"from": [0, 0, 0], "to": [0, 1, 0], "enqueued_at": 674, "state": "QUEUED"
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"purpose": "Historical negative only; not fresh acceptance evidence",
|
||||
"source_request": "os-pair/A/reader-request-0003/request.json",
|
||||
"source_request_sha256": "7dc3ee0bcc428e9d103726ee2448c767f1dd4107f9490833dc860c0b5140068c",
|
||||
"manifest_sha256": "53aa8e2f12e5d0a089529457fcd55ee1ba0600e8dffff2225bb8b057d007fe1a",
|
||||
"independent_judgment": [11, false, false, false],
|
||||
"source_rows": [11, 29],
|
||||
"report_inputs": {
|
||||
"actor_drawings": [
|
||||
{"key": "guest/guest-auto-00000041", "at": [651.0, 517.0], "scale": 3.0, "mode": "wait", "frame": 12, "flip": false},
|
||||
{"key": "staff/staff_gina", "at": [651.0, 517.0], "scale": 3.0, "mode": "idle", "frame": 3, "flip": false}
|
||||
],
|
||||
"cabins": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"purpose": "Historical diagnostic input for placement-rule clarification; not fresh acceptance evidence",
|
||||
"source_request": "os-pair/A/reader-request-0003/request.json",
|
||||
"source_request_sha256": "7dc3ee0bcc428e9d103726ee2448c767f1dd4107f9490833dc860c0b5140068c",
|
||||
"guests": [
|
||||
{"id": "guest-auto-00000044", "state": "RECEIVING_SERVICE", "cell": [5, 0, 0], "target": "facility_00000003", "service": "service_00000009", "queue": ""},
|
||||
{"id": "guest-auto-00000019", "state": "RECEIVING_SERVICE", "cell": [-6, 2, 0], "target": "facility_00000007", "service": "service_00000002", "queue": ""}
|
||||
],
|
||||
"room_rects": {
|
||||
"facility_00000003": [1027.0, 388.0, 256.0, 192.0],
|
||||
"facility_00000007": [131.0, 292.0, 128.0, 96.0]
|
||||
},
|
||||
"traffic": [
|
||||
{"cell": [5, 0, 0], "traffic": {"base_capacity": 4, "current_density": 6, "dynamic_cost": 0, "temporary_block": false}, "rect": [965.0, 486.0, 60.0, 92.0]},
|
||||
{"cell": [-6, 2, 0], "traffic": {"base_capacity": 4, "current_density": 3, "dynamic_cost": 0, "temporary_block": false}, "rect": [261.0, 294.0, 60.0, 92.0]}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"applicability": "Historical negative only. Distilled local-coordinate fields; never S/T execution proof.",
|
||||
"source_commit": "9a26f9ab8c6691ba3cbe32ac4bc4525f4a9c7106",
|
||||
"manifest_sha256": "a25e15f00d09acb16b05e180a6263c3771c84449bd6e0c99b6968fd739727bd2",
|
||||
"request_sha256": "7099ca03da3cef5819e7965f6fdea8777b9995a9e12806e870e844ec2b02b9a5",
|
||||
"request_relative_to_R15": "os-pair/A/reader-request-0003/request.json",
|
||||
"actual_negative_rows": [6, 17],
|
||||
"cabin_rect": [659, 463, 96, 76],
|
||||
"passenger_count": 0,
|
||||
"actor": {"key": "guest/os-p23-024", "center": [739, 532], "at": [715, 517], "scale": 3, "mode": "wait", "frame": 6, "flip": false},
|
||||
"traffic": {"cell": [0, 1, 0], "rect": [645, 390, 60, 92], "band_rect": [652.2, 473.5, 45.6, 5], "traffic": {"base_capacity": 4, "current_density": 16, "dynamic_cost": 0, "temporary_block": false}},
|
||||
"queue_badge": {"at": [563, 468], "text": "待ち27人(他18人)"},
|
||||
"note": "Raw R15 maps/receipts remain unchanged. New geometry tests calculate a counterfactual projection in memory only."
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
extends SceneTree
|
||||
## Component and read-only rendering checks; never OS or same-demand proof.
|
||||
const Presentation := preload("res://scripts/ui/room_presentation.gd")
|
||||
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
|
||||
class QueueWorld extends RefCounted:
|
||||
var revision := 0
|
||||
func get_bounds() -> Dictionary:
|
||||
return {&"min_x": -1, &"max_x": 2, &"min_y": 0, &"max_y": 1}
|
||||
func is_walkable(cell: Vector3i) -> bool:
|
||||
return cell.y >= 0 and cell.y <= 1 and cell.x >= -1 and cell.x <= 2
|
||||
func get_occupant(_cell: Vector3i) -> StringName:
|
||||
return &""
|
||||
|
||||
class QueueLift extends RefCounted:
|
||||
var layer := 0
|
||||
var visual_floor := 0.3
|
||||
func get_report() -> Dictionary:
|
||||
var trips: Array = []
|
||||
for i: int in 3:
|
||||
trips.append({&"state": PathService.ELEVATOR_REQUEST_QUEUED, &"from": Vector3i(0,0,layer),
|
||||
&"actor_id": "guest:q%d" % i, &"enqueued_at": i})
|
||||
return {&"shafts": [{&"shaft_id": &"wide", &"stops": [Vector3i(0,0,layer), Vector3i(0,1,layer)],
|
||||
&"width": 2, &"capacity": 4, &"boarded": [], &"visual_floor": visual_floor,
|
||||
&"operational": true, &"phase": &"PICKUP"}], &"trips": trips}
|
||||
|
||||
class QueueRuntime extends RefCounted:
|
||||
var world_service := QueueWorld.new()
|
||||
var elevators := QueueLift.new()
|
||||
|
||||
func repair28_geometry() -> void:
|
||||
var view := WorldPreview.new()
|
||||
view.size = Vector2(408,332)
|
||||
var fixture := QueueRuntime.new()
|
||||
view._runtime = fixture
|
||||
root.add_child(view)
|
||||
for layer: int in [0,1]:
|
||||
view.layer_mode = layer as WorldPreview.LayerMode
|
||||
fixture.elevators.layer = layer
|
||||
var model: Dictionary = view._observe_queues()
|
||||
expect(model[&"shaft_footprint"].has(Vector3i(1,0,layer)), "width-2 interior belongs to shaft")
|
||||
expect(model[&"queue_positions"].size() == 1 and model[&"queue_hidden"].size() == 2, "shaft stops outward lane and retains overflow")
|
||||
var badge: Dictionary = model[&"landing_badges"][0]
|
||||
expect(badge[&"members"].size() == badge[&"visible_count"] + badge[&"overflow"], "no queue member lost")
|
||||
model.merge({&"boarded": {}, &"elevator_tracks": {}, &"paused": true})
|
||||
var cabins: Array[Dictionary] = view._observe_elevators(model)
|
||||
expect(cabins.size() == 1, "active-layer cabin remains visible")
|
||||
for at: Vector2 in model[&"queue_positions"].values():
|
||||
for cell: Vector3i in model[&"shaft_footprint"]:
|
||||
expect(not view.get_cell_rect(cell).has_point(at), "queue anchor outside full footprint")
|
||||
expect(not cabins[0][&"rect"].has_point(at), "queue anchor outside empty cabin")
|
||||
expect(cabins[0][&"passenger_count"] == 0 and cabins[0][&"passengers"].is_empty(), "empty cabin has no passenger")
|
||||
expect(cabins[0][&"operational"] and not cabins[0][&"doors_open"] and cabins[0][&"phase"] == &"PICKUP", "typed moving cabin state")
|
||||
for floor: float in [0.0, 0.8]:
|
||||
fixture.elevators.visual_floor = floor
|
||||
var pose_model: Dictionary = view._observe_queues()
|
||||
var cell := Vector3i(0, 0 if floor == 0 else 1, layer)
|
||||
var key := "guest/landing"
|
||||
var person := {&"id": &"landing", &"cell": cell, &"service": &"", &"target": &""}
|
||||
pose_model.merge({&"boarded": {}, &"elevator_tracks": {}, &"paused": true,
|
||||
&"actor_tracks": {key: {&"cell": cell, &"from": cell, &"at": 0.0, &"direction": 1.0}},
|
||||
&"rooms": {}, &"guests": [person], &"staff": [], &"carried_patients": {},
|
||||
&"resting_staff": {}, &"treated_patients": {}})
|
||||
pose_model[&"cabins"] = view._observe_elevators(pose_model)
|
||||
var row: Dictionary = view._actor_drawing(view.cell_to_canvas(cell), key, false,
|
||||
GuestRuntime.State.MOVING, false, false, &"", false, pose_model)
|
||||
var region: Dictionary = view._life_actor_region(row, person, pose_model)
|
||||
expect(region.has(&"center") and not region[&"transit"], "same-floor and fractional cabin use landing region")
|
||||
expect(not (region[&"bounds"] as Rect2).intersects(pose_model[&"cabins"][0][&"rect"]), "landing bounds outside cabin")
|
||||
pose_model[&"actor_drawings"] = [row]
|
||||
view._fit_life_actors(pose_model)
|
||||
expect(pose_model[&"actor_drawings"].size() == 1 and pose_model[&"actor_aggregations"].is_empty(), "landing actor retained")
|
||||
expect(not view._life_core(row).intersects(pose_model[&"cabins"][0][&"rect"]), "fitted non-passenger core outside cabin")
|
||||
expect(row[&"cell"] == cell and not row[&"riding"] and row[&"state"] == GuestRuntime.State.MOVING, "landing placement preserves actual state")
|
||||
fixture.elevators.visual_floor = 0.3
|
||||
for operational: bool in [true,false]:
|
||||
for phase: StringName in [&"IDLE", &"BOARDING", &"PICKUP"]:
|
||||
var shaft: Dictionary = fixture.elevators.get_report()[&"shafts"][0]
|
||||
shaft[&"operational"] = operational
|
||||
shaft[&"phase"] = phase
|
||||
var text := Presentation.elevator_label(shaft)
|
||||
expect(text.contains("0/4人") and text.contains(String(phase)), "count and phase visible")
|
||||
expect(text.begins_with("運転" if operational else "停止"), "operation visible beyond lamp")
|
||||
expect(text.contains(" 開 " if operational and phase != &"PICKUP" else " 閉 "), "doors visible beyond lamp")
|
||||
var obstacles: Array[Rect2] = [Rect2(20,0,20,90), Rect2(45,80,20,14)]
|
||||
var segments := view._traffic_segments(Rect2(10,85,60,5), Rect2(0,0,80,96), obstacles)
|
||||
var area := 0.0
|
||||
for segment: Rect2 in segments:
|
||||
area += segment.get_area()
|
||||
for obstacle: Rect2 in obstacles:
|
||||
expect(not segment.intersects(obstacle), "traffic never crosses cabin or badge")
|
||||
expect(is_equal_approx(area, 100.0), "traffic preserves all uncovered area")
|
||||
var covering: Array[Rect2] = [Rect2(0,80,80,16)]
|
||||
var fallback := view._traffic_segments(Rect2(10,85,60,5), Rect2(0,0,80,96), covering)
|
||||
expect(fallback.size() == 1 and fallback[0] == Rect2(10,0,60,5), "shaft-column density remains visible")
|
||||
view.free()
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func repair36_labels() -> void:
|
||||
var view := WorldPreview.new()
|
||||
var members: Array[String] = ["guest/a", "guest/b", "guest/c"]
|
||||
var identities: Array[Dictionary] = []
|
||||
for key: String in members:
|
||||
identities.append({&"key": key, &"id": key.trim_prefix("guest/"), &"state": 0, &"cell": Vector3i.ZERO, &"mode": &"idle"})
|
||||
var queue := {&"at": Vector2(40,40), &"text": "待ち3人(他3人)", &"cell": Vector3i.ZERO, &"members": members.duplicate(), &"visible_count": 0, &"overflow": members.size()}
|
||||
var aggregate := {&"at": queue[&"at"], &"text": "+3", &"members": identities.duplicate(true), &"drawn_keys": []}
|
||||
var model := {&"cabins": [], &"badges": [], &"landing_badges": [queue], &"actor_aggregations": [aggregate]}
|
||||
var blocked := Rect2(100,100,20,20)
|
||||
model[&"cabins"] = [{&"rect": blocked, &"badge_at": Vector2(200,200), &"badge": "EV", &"badge_rect": view._readable_badge_rect(Vector2(200,200), "EV")}]
|
||||
model[&"shaft_footprint"] = []
|
||||
var region: Dictionary = view._life_landing_region({&"bounds": blocked, &"transit": false}, Vector3i.ZERO, model)
|
||||
expect(region.get(&"no_lane", false) and region[&"badge_at"] == queue[&"at"], "lane-less stranded actor uses hidden queue anchor")
|
||||
view._resolve_observation_labels(model)
|
||||
expect(not view._readable_badge_rect(queue[&"at"], queue[&"text"]).intersection(view._readable_badge_rect(aggregate[&"at"], aggregate[&"text"])).has_area(), "hidden queue and stranded aggregate labels do not overlap")
|
||||
expect(queue[&"text"] == "待ち3人(他3人)" and aggregate[&"text"] == "+3", "complete counts remain readable")
|
||||
expect(queue[&"members"] == members and aggregate[&"members"] == identities and aggregate[&"drawn_keys"].is_empty(), "label placement preserves member identities and visibility")
|
||||
expect(queue[&"at"] == queue[&"anchor"] and aggregate[&"anchor"] == queue[&"anchor"], "uncontested queue keeps anchor and stranded anchor remains recorded")
|
||||
var solitary := {&"at": Vector2(400,400), &"text": "+1"}
|
||||
model[&"actor_aggregations"] = [solitary]
|
||||
view._resolve_observation_labels(model)
|
||||
expect(solitary[&"at"] == solitary[&"anchor"] and solitary[&"displaced_by"].is_empty(), "uncontested label stays at anchor")
|
||||
view.free()
|
||||
|
||||
func expect(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error("P23 life observation: " + message)
|
||||
|
||||
func run() -> void:
|
||||
var layout := OS.get_environment("P23_LAYOUT")
|
||||
var output_directory := OS.get_environment("P23_OUTPUT")
|
||||
if layout in ["A", "B"] and not output_directory.is_empty():
|
||||
var comparison := preload("res://tests/fixtures/p23-03/layout_comparison.gd").new()
|
||||
expect(await comparison.run(self, output_directory, layout), "native layout " + layout)
|
||||
print("P23 life observation: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
return
|
||||
var clock: Variant = root.get_node("GameClock")
|
||||
clock.set_paused(true)
|
||||
repair28_geometry()
|
||||
repair36_labels()
|
||||
# Isolated real typed components test state labels, without claiming gameplay.
|
||||
var labels: Dictionary = {}
|
||||
for state: int in FacilityInstance.State.values():
|
||||
var room := FacilityInstance.new().configure(&"component", &"reception")
|
||||
room.state = state as FacilityInstance.State
|
||||
var description := Presentation.describe(room)
|
||||
expect(description[&"state_name"] == FacilityInstance.State.keys()[state], "enum name preserved")
|
||||
var text := Presentation.business_label(description)
|
||||
expect(not text.is_empty() and not labels.has(text), "distinct visible business reason")
|
||||
labels[text] = true
|
||||
for reason: StringName in [&"RAIDED", &"DESTROYED"]:
|
||||
var room := FacilityInstance.new().configure(&"component", &"reception", {&"suspension_reason": reason})
|
||||
var description := Presentation.describe(room)
|
||||
expect(description[&"operation"] == FacilityInstance.OperationState.SUSPENDED, "actual suspension operation")
|
||||
var text := Presentation.business_label(description)
|
||||
expect(not labels.has(text), "raid and damage have separate visible labels")
|
||||
labels[text] = true
|
||||
var room := FacilityInstance.new().configure(&"component", &"reception", {&"customer_capacity": 3})
|
||||
var service := ServiceInstance.new()
|
||||
service.guest_ids.assign([&"b", &"a"])
|
||||
room.active_services[&"service"] = service
|
||||
room.queue_entries.assign([&"queue-only"])
|
||||
var description := Presentation.describe(room)
|
||||
expect(description[&"people"] == [&"a", &"b"] and description[&"count"] == 2, "queue entries are not room occupants")
|
||||
description[&"people"].clear()
|
||||
expect(service.guest_ids.size() == 2, "presentation owns its arrays")
|
||||
var runtime: Variant = root.get_node("SimulationRuntime")
|
||||
var view := WorldPreview.new()
|
||||
view.size = Vector2(1440, 810)
|
||||
view.set_runtime(runtime)
|
||||
root.add_child(view)
|
||||
# R13's four same-cell MOVING guests. Component fixture only, no Runtime edit.
|
||||
var crowd: Array = []
|
||||
var offsets := [0, 16, -16, 8]
|
||||
for i: int in 4:
|
||||
crowd.append({&"row": {&"key": "guest/os-p23-%03d" % (12+i),
|
||||
&"center": Vector2(108+offsets[i], 118-4*(i%2)),
|
||||
&"at": Vector2(84+offsets[i], 103-4*(i%2)), &"scale": 3.0,
|
||||
&"mode": &"idle", &"frame": 0, &"flip": false,
|
||||
&"state": GuestRuntime.State.MOVING, &"cell": Vector3i(1,1,0)},
|
||||
&"bounds": Rect2(80,74,56,88), &"transit": false})
|
||||
crowd.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x)
|
||||
# _life_pack uses the view's cell dimensions for the unchanged feet formula.
|
||||
# The x-capacity assertion is independent of the current default world size.
|
||||
expect(view._life_pack(crowd, 3).is_empty(), "four cores cannot fit 72px in 56px")
|
||||
var saved_size := view.size
|
||||
var saved_zoom := view.zoom_level
|
||||
var dim: Vector2 = view._cell_dimensions()
|
||||
view.zoom_level *= 64.0/dim.x
|
||||
var fitted: Array = view._life_pack(crowd, 2)
|
||||
expect(fitted.size() == 4, "all four R13 occupants fit at p=2")
|
||||
for row: Dictionary in fitted:
|
||||
expect(row[&"scale"] == 2.0 and row[&"cell"] == Vector3i(1,1,0), "fitting preserves cell and uses p=2")
|
||||
var regroup: Array = crowd.slice(0, 2).duplicate(true)
|
||||
for i: int in 2:
|
||||
regroup[i][&"row"][&"center"] = Vector2(108, 118+26*i)
|
||||
regroup[i][&"bounds"] = Rect2(80,74,20,130)
|
||||
var regrouped: Array = view._life_fitted_groups(regroup)
|
||||
expect(regrouped.size() == 2, "scale change splits vertical component")
|
||||
for group: Array in regrouped:
|
||||
expect(group.size() == 1 and group[0][&"row"][&"scale"] == 2.0, "regroup avoids stale p=1 fit")
|
||||
var saturated: Array = []
|
||||
for i: int in 10:
|
||||
var member: Dictionary = crowd[0].duplicate(true)
|
||||
member[&"row"][&"key"] = "guest/saturation-%02d" % i
|
||||
member[&"row"][&"center"] = Vector2(108,118)
|
||||
saturated.append(member)
|
||||
var saturated_groups: Array = view._life_fitted_groups(saturated)
|
||||
expect(saturated_groups.size() == 1, "saturation keeps one vertical component")
|
||||
expect(view._life_pack(saturated_groups[0], 1).is_empty(), "ten cores cannot fit 60px in 56px")
|
||||
expect(view._life_pack(saturated_groups[0].slice(0,9), 1).size() == 9, "nine cores fit without losing identities")
|
||||
var aggregate_model := {&"guests": [], &"staff": [], &"actor_drawings": [], &"cabins": []}
|
||||
var aggregate_rows: Array = []
|
||||
for member: Dictionary in saturated:
|
||||
var row: Dictionary = member[&"row"].duplicate(true)
|
||||
row[&"riding"] = true
|
||||
aggregate_rows.append(row)
|
||||
aggregate_model[&"guests"].append({&"id": String(row[&"key"]).trim_prefix("guest/"), &"cell": Vector3i(1,1,0)})
|
||||
aggregate_model[&"cabins"].append({&"rect": Rect2(76,70,64,96), &"passengers": aggregate_rows})
|
||||
view._fit_life_actors(aggregate_model)
|
||||
expect(aggregate_model[&"cabins"][0][&"passengers"].size() == 9, "aggregation draws all nine fitting members")
|
||||
var aggregate: Dictionary = aggregate_model[&"actor_aggregations"][0]
|
||||
expect(aggregate[&"members"].size() == 10 and aggregate[&"drawn_keys"].size() == 9, "aggregation retains every identity")
|
||||
expect(aggregate[&"text"] == "+1" and aggregate[&"at"] == Vector2(80,74), "actual aggregation badge position and text")
|
||||
view.zoom_level = saved_zoom
|
||||
view.size = saved_size
|
||||
var before: PackedByteArray = var_to_bytes(runtime.create_snapshot())
|
||||
var tracks: PackedByteArray = var_to_bytes([view._actor_tracks, view._elevator_tracks])
|
||||
var model := view._build_life_observation()
|
||||
expect(var_to_bytes(runtime.create_snapshot()) == before, "builder does not mutate Runtime")
|
||||
expect(var_to_bytes([view._actor_tracks, view._elevator_tracks]) == tracks, "builder does not advance visual tracks")
|
||||
expect(model.has(&"rooms") and model.has(&"landing_badges") and model.has(&"elevators") and model.has(&"staff") and model.has(&"goods") and model.has(&"badges"), "all six category fields exist")
|
||||
expect(model[&"traffic"].size() > 0 and not view.show_grid, "normal view observes real walkable traffic")
|
||||
await process_frame
|
||||
await process_frame
|
||||
var report := view.get_life_observation_report()
|
||||
expect(not report.is_empty(), "actual drawing published an observation")
|
||||
expect(report.get(&"observation_serial", -1) == report.get(&"overlay_consumed_serial", -2), "LifeOverlay consumed this observation")
|
||||
expect(report.get(&"overlay_passes", []) == [&"traffic", &"cabins", &"actors"], "traffic drawn before cabins and actors")
|
||||
var indices: Array = report.get(&"overlay_child_indices", [])
|
||||
expect(indices.size() == 3 and indices[0] < indices[1] and indices[1] < indices[2], "LifeOverlay above inactive art and below lens")
|
||||
# The getter exposes the last actual draw and must not create another model.
|
||||
var original: PackedByteArray = var_to_bytes(report)
|
||||
if report.has(&"traffic"):
|
||||
report[&"traffic"].clear()
|
||||
expect(var_to_bytes(view.get_life_observation_report()) == original, "nested report arrays are isolated")
|
||||
report.clear()
|
||||
expect(var_to_bytes(view.get_life_observation_report()) == original, "public report is a deep copy")
|
||||
var output := OS.get_environment("P23_LIFE_DIAGNOSTIC_ROOT")
|
||||
if not output.is_empty():
|
||||
var stream := Stream.new()
|
||||
expect(stream.open(output), "fresh sparse diagnostic stream")
|
||||
expect(stream.capture("initial", runtime, view), "whole typed state and draw report capture")
|
||||
stream.close()
|
||||
view.free()
|
||||
print("P23 life observation: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://d23lifeobs03
|
||||
@@ -0,0 +1,362 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only native collection. Does not run regression or claim acceptance."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
import re
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'
|
||||
CONDITIONS = ('room_occupancy_and_operation', 'queues_and_overflow',
|
||||
'elevator_state_and_passengers', 'staff_state_and_jobs',
|
||||
'supplies_and_carried_goods', 'same_demand_route_improvement',
|
||||
'normal_os_and_distinct_pid_resume')
|
||||
OS_CHILD_TIMEOUT_SECONDS = 3600
|
||||
RECORDER = 'tests/fixtures/p23-03/passive_os_recorder.gd'
|
||||
|
||||
def collect_screenshots(out, pair, role):
|
||||
"""Bind every external CUA screenshot to its request and decoded PNG.
|
||||
|
||||
The external receipt supplies screenshots[{path,raw_sha256,tool_call_id}].
|
||||
Paths are relative to the evidence root; raw receipt bytes remain unchanged.
|
||||
"""
|
||||
checker = module('p23_screenshot_binding', HERE/'check_p23_life_observation_evidence.py')
|
||||
bindings = []
|
||||
requests = json.loads((pair/(role+'-requests.json')).read_text())['requests']
|
||||
for ack_name in requests:
|
||||
ack_path = out/ack_name
|
||||
checker.require(ack_path.resolve().is_relative_to(out.resolve()), 'ACK path escape')
|
||||
ack = json.loads(ack_path.read_text())
|
||||
request_path = ack_path.with_name('request.json')
|
||||
request = json.loads(request_path.read_text())
|
||||
receipt_path = out/ack['input_receipt']
|
||||
checker.require(receipt_path.resolve().is_relative_to(out.resolve()), 'CUA receipt path escape')
|
||||
cua = json.loads(receipt_path.read_text())
|
||||
checker.require(ack['request_sha256'] == checker.sha(request_path), 'CUA request hash')
|
||||
checker.require(cua['screenshots'], 'CUA screenshots missing')
|
||||
for number, shot in enumerate(cua['screenshots']):
|
||||
raw = out/shot['path']
|
||||
checker.require(raw.resolve().is_relative_to(out.resolve()) and not raw.is_symlink(), 'screenshot path escape')
|
||||
checker.require(checker.sha(raw) == shot['raw_sha256'] and shot['tool_call_id'], 'raw screenshot CUA binding')
|
||||
kind, _, _ = checker.image_kind(raw.read_bytes())
|
||||
conversion = None
|
||||
if kind == 'jpeg':
|
||||
png = ack_path.with_name('decoded-%02d.png' % number)
|
||||
receipt = ack_path.with_name('conversion-%02d.json' % number)
|
||||
convert_screenshot(raw.resolve(), png.resolve(), shot, receipt.resolve())
|
||||
conversion = str(receipt.relative_to(out))
|
||||
else:
|
||||
png = raw
|
||||
width, height, _ = checker.png_pixels(png.read_bytes())
|
||||
bindings.append(dict(request=str(request_path.relative_to(out)), request_sha256=checker.sha(request_path),
|
||||
sequence=request['sequence'], target_pid=request['target_pid'],
|
||||
input_receipt=ack['input_receipt'], input_receipt_sha256=checker.sha(receipt_path),
|
||||
screenshot_index=number, tool_call_id=shot['tool_call_id'],
|
||||
raw_path=shot['path'], raw_sha256=shot['raw_sha256'], conversion=conversion,
|
||||
png_path=str(png.relative_to(out)), png_sha256=checker.sha(png), width=width, height=height))
|
||||
checker.require(bindings, 'empty screenshot collection')
|
||||
destination = pair/(role+'-screenshots.json')
|
||||
with destination.open('x') as output:
|
||||
json.dump(bindings, output, indent=2)
|
||||
output.write('\n')
|
||||
return str(destination.relative_to(out))
|
||||
|
||||
def convert_screenshot(raw_path, png_path, cua_receipt, receipt_path):
|
||||
"""Root-only conversion; raw CUA bytes are never renamed or rewritten."""
|
||||
checker = module('p23_conversion_checker', HERE/'check_p23_life_observation_evidence.py')
|
||||
raw, png, receipt = map(Path, (raw_path, png_path, receipt_path))
|
||||
checker.require(raw.is_absolute() and png.is_absolute() and receipt.is_absolute(),
|
||||
'absolute conversion paths required')
|
||||
checker.require(raw.is_file() and not raw.is_symlink() and not png.exists()
|
||||
and not receipt.exists() and raw.resolve() != png.resolve(), 'fresh separate conversion output')
|
||||
raw_hash = checker.sha(raw)
|
||||
checker.require(raw_hash == cua_receipt['raw_sha256'] and cua_receipt['tool_call_id'],
|
||||
'CUA conversion input hash/call mismatch')
|
||||
kind, width, height = checker.image_kind(raw.read_bytes())
|
||||
checker.require(kind == 'jpeg', 'conversion requires JPEG input')
|
||||
converter = '/usr/bin/sips'
|
||||
converter_hash = checker.sha(converter)
|
||||
argv = [converter, '-s', 'format', 'png', str(raw), '--out', str(png)]
|
||||
process = subprocess.run(argv, capture_output=True, timeout=900, check=False)
|
||||
rec = dict(raw_path=str(raw), raw_sha256=raw_hash, raw_kind=kind,
|
||||
tool_call_id=cua_receipt['tool_call_id'], converter=converter,
|
||||
converter_sha256=converter_hash, argv=argv, exit_code=process.returncode,
|
||||
stdout_sha256=hashlib.sha256(process.stdout).hexdigest(),
|
||||
stderr_sha256=hashlib.sha256(process.stderr).hexdigest(),
|
||||
png_path=str(png), png_sha256=checker.sha(png) if png.is_file() else None,
|
||||
width=width, height=height)
|
||||
# Preserve failure receipts too. These logs allow independent hash checking.
|
||||
with receipt.open('x') as output:
|
||||
json.dump(rec, output, indent=2)
|
||||
output.write('\n')
|
||||
with Path(str(receipt) + '.stdout').open('xb') as output:
|
||||
output.write(process.stdout)
|
||||
with Path(str(receipt) + '.stderr').open('xb') as output:
|
||||
output.write(process.stderr)
|
||||
checker.require(checker.sha(raw) == raw_hash, 'raw CUA bytes changed during conversion')
|
||||
checker.require(checker.sha(converter) == converter_hash, 'converter changed during conversion')
|
||||
checker.inspect_conversion(rec, cua_receipt, converter_hash)
|
||||
return rec
|
||||
|
||||
def collect_os(r, root, out, stage, engine, commit, refs, protected, outer):
|
||||
"""Adapted P08-03 collect_os protocol, retaining owned-process guards.
|
||||
|
||||
Root supplies CUA ack/receipt files in the requested directories. Neither
|
||||
controller nor passive endpoints generate input or change permissions.
|
||||
"""
|
||||
r.require(not outer.native.helper['live_engines'](), 'external engine present')
|
||||
checker = module('p23_os_pins', HERE/'check_p23_life_observation_evidence.py')
|
||||
before = r.inventory(stage, stage=True)
|
||||
public = (stage/'project.godot').read_bytes()
|
||||
(out/'os-pair').mkdir()
|
||||
phases = []
|
||||
for label in ('A-writer', 'A-reader', 'B-writer', 'B-reader'):
|
||||
layout, role = label.split('-')
|
||||
pair = out/'os-pair'/layout
|
||||
pair.mkdir(exist_ok=role == 'reader')
|
||||
r.require(not outer.native.helper['live_engines'](), 'external engine present')
|
||||
r.require(r.source_refs(root) == refs and r.default_player(root) == protected, 'OS before guards')
|
||||
r.require(r.inventory(stage, stage=True) == before, 'OS stage before guard')
|
||||
r.require(shutil.disk_usage(out).free >= 500*1024*1024, 'OS disk admission')
|
||||
folder = out/'os-runs'/label
|
||||
folder.mkdir(parents=True)
|
||||
name = 'TheTree-PCK-tests-'+uuid.uuid4().hex+'-p23-observation-'+label.lower()
|
||||
profile = r.profile_path(name)
|
||||
r.require(not profile.exists(), 'fresh OS private profile')
|
||||
formal_source = pair/'formal' if role == 'reader' else None
|
||||
formal_before = r.inventory(formal_source) if formal_source else {}
|
||||
if role == 'reader':
|
||||
r.require(set(formal_before) == {'world.bin','manifest.json','thumbnail.png','journal.log'}, 'all formal inputs')
|
||||
formal = profile/'saves/manual_ui'
|
||||
formal.mkdir(parents=True)
|
||||
for member, pin in formal_before.items():
|
||||
r.copy_source(formal_source/member, formal/member)
|
||||
r.require(r.sha(formal/member) == pin['sha256'], 'formal fixture copy SHA')
|
||||
recorder = folder/'passive_os.gd'
|
||||
r.copy_source(root/RECORDER, recorder)
|
||||
proof = folder/'teardown'
|
||||
(proof/'owned-audio').mkdir(parents=True)
|
||||
env = r.fresh_environment(dict(os.environ))
|
||||
env.update(P23_OBSERVATION_ROOT=str(pair), P23_OBSERVATION_ROLE=role, P23_LAYOUT=layout,
|
||||
THE_TREE_AUDIO_TEARDOWN_PROOF_DIR=str(proof/'owned-audio'),
|
||||
THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF=str(proof/'control.json'))
|
||||
command = [engine, '--path', str(stage), '--script', str(recorder)]
|
||||
base = {'protocol':'p23-observation-native-os-v1', 'role':role, 'layout':layout, 'commit':commit,
|
||||
'source_map':{n:v['sha256'] for n,v in refs.items()}, 'stage':str(stage),
|
||||
'stage_before':before, 'profile':str(profile), 'profile_name':name,
|
||||
'profile_before':{'existed':profile.exists(), 'files':r.inventory(profile) if profile.exists() else {}}, 'formal_input':formal_before,
|
||||
'formal_source':str(formal_source) if formal_source else None, 'owner_pid':os.getpid(),
|
||||
'default_before':protected, 'command':command, 'engine_sha256':r.sha(engine),
|
||||
'controller_sha256':r.sha(root/'tools/run_p23_life_observation.py'),
|
||||
'recorder_sha256':r.sha(recorder), 'formal_acceptance':False,
|
||||
'timeout_seconds':OS_CHILD_TIMEOUT_SECONDS,
|
||||
'inputs':checker.input_map(root), 'spec_sha256':checker.SPEC_SHA,
|
||||
'settings_sha256':hashlib.sha256(public).hexdigest(),
|
||||
'environment':{k:v for k,v in env.items() if k.startswith(('P23_', 'THE_TREE_'))},
|
||||
'environment_sha256':hashlib.sha256(json.dumps(env,sort_keys=True).encode()).hexdigest()}
|
||||
r.write(folder/'started.json', base)
|
||||
error = None; children = []; guards = []; exit_code = None; owner_proof = None
|
||||
try:
|
||||
(stage/'project.godot').write_bytes(r.private_project(public, name))
|
||||
with (folder/'engine-native.log').open('x') as log:
|
||||
process = subprocess.Popen(command, cwd=stage, env=env, stdout=log, stderr=subprocess.STDOUT)
|
||||
children.append(process)
|
||||
r.write(folder/'process-started.json', dict(base, actual_pid=process.pid,
|
||||
monotonic_ns=time.monotonic_ns(), state='waiting_for_root_cua_requests'))
|
||||
print(json.dumps({'actual_pid':process.pid, 'request_directory':str(pair),
|
||||
'state':'waiting_for_root_cua', 'role':role}), flush=True)
|
||||
exit_code = process.wait(timeout=base['timeout_seconds'])
|
||||
r.require(exit_code == 0, 'ordinary native OS exit0')
|
||||
raw = (folder/'engine-native.log').read_text()
|
||||
r.require('ERROR:' not in raw and 'SCRIPT ERROR:' not in raw, 'OS native diagnostics')
|
||||
matches = re.findall(r'(?m)^P23 passive OS observation: ([0-9]+) checks, 0 failures$', raw)
|
||||
r.require(len(matches) == 1 and int(matches[0]) > 0, 'passive observer completion')
|
||||
observed = r.read(pair/(role+'-profile.json'))
|
||||
r.require(observed['pid'] == process.pid and Path(observed['user_data_dir']).resolve() == profile.resolve()
|
||||
and observed['display'] != 'headless', 'actual OS private profile/display')
|
||||
collect_screenshots(out, pair, role)
|
||||
owners = r.module('p23_os_owner_gate', r.SUPPORT/'ordinary_proof_gate.py')
|
||||
owner_proof = owners.ordinary_gate(proof, process.pid, folder, ['single'])
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
try:
|
||||
cleanup, cleanup_errors = outer.finish_captured(children)
|
||||
finally:
|
||||
(stage/'project.godot').write_bytes(public)
|
||||
try:
|
||||
r.require(r.source_refs(root) == refs, 'OS source unchanged')
|
||||
r.require(r.inventory(stage, stage=True) == before, 'OS stage unchanged')
|
||||
r.require(r.default_player(root) == protected, 'OS default player unchanged')
|
||||
if formal_source:
|
||||
r.require(r.inventory(formal_source) == formal_before, 'formal input unchanged')
|
||||
r.require(r.sha(recorder) == base['recorder_sha256'], 'recorder unchanged')
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__+': '+str(caught))
|
||||
passed = not (error or guards or cleanup_errors) and len(children) == 1 and exit_code == 0
|
||||
r.write(folder/'ended.json', dict(base, actual_pid=children[0].pid if children else None,
|
||||
exit_code=exit_code, engine_ended=bool(cleanup) and all(v['engine_ended'] for v in cleanup),
|
||||
error=error, guard_errors=guards, cleanup_errors=cleanup_errors, owned_cleanup=cleanup,
|
||||
ordinary_owner_proof=owner_proof, stage_after=r.inventory(stage,stage=True),
|
||||
engine_sha256_after=r.sha(engine), inputs_after=checker.input_map(root),
|
||||
settings_sha256_after=hashlib.sha256((stage/'project.godot').read_bytes()).hexdigest(),
|
||||
environment_after={k:v for k,v in env.items() if k.startswith(('P23_', 'THE_TREE_'))},
|
||||
environment_sha256_after=hashlib.sha256(json.dumps(env,sort_keys=True).encode()).hexdigest(),
|
||||
default_after=r.default_player(root), source_after=r.source_refs(root),
|
||||
profile_after=r.inventory(profile), passed=passed))
|
||||
phases.append('os-runs/'+label+'/ended.json')
|
||||
r.require(passed, 'native OS collection failed: '+str(error or guards or cleanup_errors))
|
||||
return phases
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
obj = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(obj)
|
||||
return obj
|
||||
|
||||
def absolute(value):
|
||||
path = Path(value)
|
||||
if not path.is_absolute():
|
||||
raise argparse.ArgumentTypeError('absolute path required')
|
||||
return path.resolve()
|
||||
|
||||
def collect(args):
|
||||
root, out = args.project_root, args.output
|
||||
if out.is_relative_to(root):
|
||||
raise ValueError('evidence output must be outside the frozen source checkout')
|
||||
r = module('p23_standard_runner', root/'tools/run_all_tests.py')
|
||||
commit = subprocess.check_output(['git', '-C', str(root), 'rev-parse', 'HEAD'], text=True).strip()
|
||||
r.require(commit == args.commit, 'actual tested commit Q required')
|
||||
r.require(not subprocess.check_output(['git','-C',str(root),'status','--porcelain'], text=True), 'frozen clean worktree required')
|
||||
r.require(not out.exists(), 'fresh output; never overwrite earlier proof')
|
||||
refs = r.source_refs(root)
|
||||
checker = module('p23_initial_inputs', root/'tools/check_p23_life_observation_evidence.py')
|
||||
inputs_before = checker.input_map(root)
|
||||
root_roles = checker.strict_json(args.root_composition.read_bytes())
|
||||
composition = checker.verify_composition(root, root_roles,
|
||||
{name: pin['sha256'] for name, pin in refs.items()}, inputs_before)
|
||||
outer = r.module('p23_owned_outer', r.SUPPORT/'owned_outer.py')
|
||||
r.require(not outer.native.helper['live_engines'](), 'one native engine')
|
||||
engine = shutil.which(args.godot)
|
||||
r.require(engine is not None, 'native engine exists')
|
||||
engine = str(Path(engine).resolve())
|
||||
out.mkdir(parents=True)
|
||||
stage = out/'stage'; stage.mkdir()
|
||||
(out/'starters').mkdir(); (out/'bootstrap').mkdir()
|
||||
for name, pin in r.read(r.SUPPORT/'bootstrap-scripts.json').items():
|
||||
data = pin['text'].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == pin['sha256'], 'bootstrap pin')
|
||||
(out/'bootstrap'/name).write_bytes(data)
|
||||
for name, pin in refs.items():
|
||||
target = stage/name; target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(pin['path']), target)
|
||||
r.require(r.sha(target) == pin['sha256'], 'whole copied source '+name)
|
||||
expected = {n: {'bytes':v['bytes'], 'sha256':v['sha256']} for n,v in refs.items()}
|
||||
selected = {'root_before':refs, 'map':{n:v['sha256'] for n,v in refs.items()}, 'timeout':180}
|
||||
r.require(r.inventory(stage, stage=True) == expected, 'complete private source')
|
||||
protected = r.default_player(root); public = (stage/'project.godot').read_bytes()
|
||||
paths = ['res://'+n for n in sorted(refs) if n.endswith('.gd')]
|
||||
r.write(out/'parse-paths.json', paths)
|
||||
markers = r.module('p23_strict_markers', root/'tools/strict_test_markers.py')
|
||||
contracts = markers.load_contracts(root/CONTRACT)
|
||||
policy = r.read(r.SUPPORT/'suite-policy.json')
|
||||
diagnostics = r.module('p23_diagnostics', r.SUPPORT/'diagnostic_gate.py')
|
||||
owners = r.module('p23_owners', r.SUPPORT/'ordinary_proof_gate.py')
|
||||
audio = r.module('p23_audio', r.SUPPORT/'audio_evidence_gate.py')
|
||||
r.write(out/'runner-input.json', {'protocol':'p23-normal-observation-v1', 'commit':commit,
|
||||
'selected_source_map':selected['map'], 'engine':engine, 'engine_sha256':r.sha(engine),
|
||||
'owner_pid':os.getpid(), 'default_player':protected, 'diagnostic_seed':806042,
|
||||
'inputs':inputs_before, 'provenance':checker.input_provenance(root),
|
||||
'pixel_decoder':{'interpreter':sys.executable,
|
||||
'PIL_available':importlib.util.find_spec('PIL') is not None,
|
||||
'raw_screenshots':'preserve bytes; Root lossless conversion required for JPEG without PIL'}})
|
||||
phases = []; previous = {}; cases = []; error = None; os_proof = None
|
||||
deadline = time.monotonic()+900
|
||||
saved_env = {k:os.environ.get(k) for k in ('P23_LAYOUT','P23_OUTPUT')}
|
||||
def phase(label, test):
|
||||
remaining = int(deadline-time.monotonic())
|
||||
r.require(remaining >= (180 if label == 'import' else 1), '900s collection deadline')
|
||||
selected['timeout'] = min(180, remaining)
|
||||
phase_out = out/'runs'/label
|
||||
phase_out.mkdir(parents=True)
|
||||
(phase_out/'starters').mkdir(); (phase_out/'bootstrap').mkdir()
|
||||
for original in (out/'bootstrap').iterdir():
|
||||
r.copy_source(original,phase_out/'bootstrap'/original.name)
|
||||
r.require(r.sha(original)==r.sha(phase_out/'bootstrap'/original.name),'bootstrap full copy')
|
||||
r.write(phase_out/'parse-paths.json',paths)
|
||||
row = r.execute_phase(engine, stage, phase_out, label, test, selected, expected, previous,
|
||||
policy, contracts, paths, public, protected, root, outer, markers, diagnostics, owners, audio)
|
||||
# Supplement the existing runner receipt; preserve it verbatim.
|
||||
r.write(phase_out/'p23-inputs.json', dict(source_map=selected['map'], source_after=r.source_refs(root),
|
||||
engine_sha256=r.sha(engine), inputs=inputs_before, inputs_after=checker.input_map(root),
|
||||
spec_sha256=checker.SPEC_SHA, settings_sha256=hashlib.sha256(public).hexdigest(),
|
||||
stage_before=expected, stage_after=r.inventory(stage,stage=True),
|
||||
default_before=protected, default_after=r.default_player(root),
|
||||
receipt_sha256=r.sha(phase_out/(label+'-receipt.json'))))
|
||||
phases.append('runs/'+label+'/'+label+'-receipt.json')
|
||||
r.require(not row['failed'], 'strict phase failed: '+label)
|
||||
try:
|
||||
phase('import', None); phase('parse', None)
|
||||
for layout in ('A', 'B'):
|
||||
directory = out/('layout-'+layout)
|
||||
os.environ.update(P23_LAYOUT=layout, P23_OUTPUT=str(directory))
|
||||
phase('layout-'+layout.lower(), 'tests/ui/test_p23_life_observation.gd')
|
||||
cases.append({'directory':directory.name, 'phase':phases[-1], 'files':r.inventory(directory)})
|
||||
for layout in ('A', 'B'):
|
||||
checker.inspect_stream(out/('layout-'+layout), root)
|
||||
if args.input_mode == 'native_os':
|
||||
os_phases = collect_os(r, root, out, stage, engine, commit, refs, protected, outer)
|
||||
checker = module('p23_os_input_map', root/'tools/check_p23_life_observation_evidence.py')
|
||||
os_proof = 'os-proof.json'
|
||||
r.write(out/os_proof, {'source_map':selected['map'], 'inputs':checker.input_map(root),
|
||||
'input_method':'native_os', 'observer_mutations':[], 'phases':os_phases, 'directory':'os-pair'})
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
for key,value in saved_env.items():
|
||||
if value is None: os.environ.pop(key, None)
|
||||
else: os.environ[key] = value
|
||||
if r.source_refs(root) != refs or r.default_player(root) != protected:
|
||||
error = (error+'; ' if error else '')+'protected root/profile changed'
|
||||
if checker.input_map(root) != inputs_before:
|
||||
error = (error+'; ' if error else '')+'spec/fixture/controller inputs changed'
|
||||
r.write(out/'runner-ended.json', {'error':error, 'cleanup':cleanup, 'cleanup_errors':cleanup_errors,
|
||||
'phases':phases, 'formal_acceptance':False, 'native_Windows':'pending'})
|
||||
checker = module('p23_observation_checker', root/'tools/check_p23_life_observation_evidence.py')
|
||||
r.write(out/'index.json', {'schema':'p23-life-observation-v2', 'commit':commit,
|
||||
'composition':composition,
|
||||
'source_map':selected['map'], 'input_map':inputs_before,
|
||||
'provenance':checker.input_provenance(root),
|
||||
'marker_contract_sha256':r.sha(root/CONTRACT),
|
||||
'spec_sha256':hashlib.sha256((root/'docs/spec/acceptance/P23-03.json').read_text().strip().encode()).hexdigest(),
|
||||
'cases':cases, 'phases':phases, 'conditions':list(CONDITIONS),
|
||||
'artifacts':{str(p.relative_to(out)):r.sha(p)
|
||||
for p in out.rglob('*') if p.is_file() and 'stage' not in p.relative_to(out).parts},
|
||||
'os_proof':os_proof, 'regression':None,
|
||||
'acceptance':'pending', 'collection_error':error})
|
||||
return 1 if error or cleanup_errors else 0
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--project-root', type=absolute, required=True)
|
||||
parser.add_argument('--output', type=absolute, required=True)
|
||||
parser.add_argument('--commit', required=True)
|
||||
parser.add_argument('--root-composition', type=absolute, required=True,
|
||||
help='Root external S/M/T/Q and complete tested-tree blob bindings')
|
||||
parser.add_argument('--godot', default='godot')
|
||||
parser.add_argument('--input-mode', choices=('handler', 'native_os'), default='handler')
|
||||
parser.add_argument('--collect', action='store_true', required=True)
|
||||
return collect(parser.parse_args(argv))
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||