Merge commit 'd2581afd26d1976cdfe4f44c89df49a772524d1e' into HEAD
@@ -3724,6 +3724,17 @@
|
|||||||
"scope": "normal placement; native and OS pending"
|
"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",
|
"unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting",
|
||||||
"roster_count_is_not_hardcoded": true,
|
"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,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,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 _queue_hidden: Dictionary = {}
|
||||||
var _landing_badges: Array[Dictionary] = []
|
var _landing_badges: Array[Dictionary] = []
|
||||||
var _transport_report: Dictionary = {}
|
var _transport_report: Dictionary = {}
|
||||||
|
var _life_observation: Dictionary = {}
|
||||||
|
var _life_overlay: Node2D
|
||||||
|
var _life_observation_serial := 0
|
||||||
var _dragging := false
|
var _dragging := false
|
||||||
var _drag_button := MOUSE_BUTTON_NONE
|
var _drag_button := MOUSE_BUTTON_NONE
|
||||||
|
|
||||||
@@ -103,6 +106,10 @@ func _ready() -> void:
|
|||||||
_inactive_layer_art = InactiveLayerRooms.new()
|
_inactive_layer_art = InactiveLayerRooms.new()
|
||||||
_inactive_layer_art.name = "InactiveLayerRooms"
|
_inactive_layer_art.name = "InactiveLayerRooms"
|
||||||
add_child(_inactive_layer_art)
|
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 = ColorRect.new()
|
||||||
_layer_lens.name = "LayerLens"
|
_layer_lens.name = "LayerLens"
|
||||||
_layer_lens.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
_layer_lens.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
@@ -511,6 +518,9 @@ func _draw() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _draw_contents() -> void:
|
func _draw_contents() -> void:
|
||||||
|
_life_observation = {}
|
||||||
|
if _life_overlay != null:
|
||||||
|
_life_overlay.queue_redraw()
|
||||||
if _inactive_layer_art != null:
|
if _inactive_layer_art != null:
|
||||||
_inactive_layer_art.visible = false
|
_inactive_layer_art.visible = false
|
||||||
_draw_background()
|
_draw_background()
|
||||||
@@ -521,18 +531,27 @@ func _draw_contents() -> void:
|
|||||||
var map_rect := get_map_rect()
|
var map_rect := get_map_rect()
|
||||||
if map_rect.size == Vector2.ZERO:
|
if map_rect.size == Vector2.ZERO:
|
||||||
return
|
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)
|
_draw_world_cells(bounds, map_rect)
|
||||||
if show_grid:
|
|
||||||
_draw_congestion_overlay(bounds)
|
|
||||||
_prepare_room_presentations()
|
|
||||||
_prepare_queues()
|
|
||||||
_draw_world_entity_icons()
|
_draw_world_entity_icons()
|
||||||
_draw_legacy_stairs()
|
_draw_legacy_stairs()
|
||||||
_draw_elevators()
|
|
||||||
_draw_other_layer()
|
_draw_other_layer()
|
||||||
_draw_construction_labels()
|
_draw_construction_labels()
|
||||||
_draw_facility_labels()
|
_draw_facility_labels()
|
||||||
_draw_actors()
|
|
||||||
_draw_excavation_jobs()
|
_draw_excavation_jobs()
|
||||||
_draw_build_preview()
|
_draw_build_preview()
|
||||||
_draw_excavation_preview()
|
_draw_excavation_preview()
|
||||||
@@ -540,6 +559,177 @@ func _draw_contents() -> void:
|
|||||||
_draw_map_caption(map_rect)
|
_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:
|
func _draw_background() -> void:
|
||||||
var night := false
|
var night := false
|
||||||
var clock := get_node_or_null("/root/GameClock")
|
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"))
|
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:
|
func _draw_life_overlay() -> void:
|
||||||
var path_service: Variant = _service(&"path_service")
|
if _life_observation.is_empty():
|
||||||
if path_service == null or not path_service.has_method(&"get_cell_traffic"):
|
|
||||||
return
|
return
|
||||||
for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1):
|
# These are actual child indices and the passes executed by this callback.
|
||||||
for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1):
|
_life_observation[&"overlay_child_indices"] = [_inactive_layer_art.get_index(), _life_overlay.get_index(), _layer_lens.get_index()]
|
||||||
var cell := Vector3i(x, y, layer_mode)
|
_life_observation[&"overlay_passes"] = []
|
||||||
var traffic_value: Variant = path_service.call(&"get_cell_traffic", cell)
|
_life_observation[&"overlay_passes"].append(&"traffic")
|
||||||
if not traffic_value is Dictionary or traffic_value.is_empty():
|
_draw_congestion_overlay(_life_overlay)
|
||||||
continue
|
_life_observation[&"overlay_passes"].append(&"cabins")
|
||||||
var traffic: Dictionary = traffic_value
|
_draw_elevators(_life_overlay)
|
||||||
var density := int(traffic.get(&"current_density", 0))
|
_draw_life_badges(_life_overlay)
|
||||||
var capacity := maxi(int(traffic.get(&"base_capacity", 1)), 1)
|
_life_observation[&"overlay_passes"].append(&"actors")
|
||||||
var blocked := bool(traffic.get(&"temporary_block", false))
|
for cabin: Dictionary in _life_observation.get(&"cabins", []):
|
||||||
if density <= 0 and not blocked:
|
for actor: Dictionary in cabin[&"passengers"]:
|
||||||
continue
|
_draw_observed_actor(actor, _life_overlay)
|
||||||
var ratio := float(density) / float(capacity)
|
_draw_actors(_life_overlay)
|
||||||
var color := Color("55c96b")
|
for badge: Dictionary in _life_observation.get(&"actor_aggregations", []):
|
||||||
if blocked or ratio > 1.0:
|
_draw_readable_badge(badge[&"at"], badge[&"text"], _life_overlay)
|
||||||
color = Color("e34332")
|
_life_observation[&"overlay_consumed_serial"] = _life_observation[&"observation_serial"]
|
||||||
elif ratio >= 0.65:
|
|
||||||
color = Color("e3a62f")
|
|
||||||
var rect := get_cell_rect(cell).grow(-2.0)
|
func _draw_congestion_overlay(canvas: CanvasItem) -> void:
|
||||||
var center := rect.get_center()
|
for row: Dictionary in _life_observation.get(&"traffic", []):
|
||||||
var route_width := clampf(rect.size.y * 0.16, 2.0, 5.0)
|
var traffic: Dictionary = row[&"traffic"]
|
||||||
draw_line(
|
var density := int(traffic[&"current_density"])
|
||||||
Vector2(rect.position.x + rect.size.x * 0.12, center.y),
|
var blocked := bool(traffic[&"temporary_block"])
|
||||||
Vector2(rect.end.x - rect.size.x * 0.12, center.y),
|
if density <= 0 and not blocked:
|
||||||
Color(color, 0.90),
|
continue
|
||||||
route_width
|
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")
|
||||||
if blocked:
|
# At overview zoom a whole cell can be covered by opaque badges. Still
|
||||||
draw_line(rect.position, rect.end, color, 2.0)
|
# submit its density band, beneath those later draws, and report no visible
|
||||||
draw_line(Vector2(rect.end.x, rect.position.y), Vector2(rect.position.x, rect.end.y), color, 2.0)
|
# 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
|
## 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))
|
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:
|
if instance_id==selected_entity_id:
|
||||||
draw_rect(rect.grow(-p),Color("f5d582"),false,2*p)
|
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:
|
func _readable_badge_rect(at: Vector2, text: String) -> Rect2:
|
||||||
var width := ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x+6
|
# A conservative em-width box makes geometry independently reproducible.
|
||||||
var rect := Rect2(at, Vector2(width, 14))
|
# 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)):
|
if not rect.intersects(Rect2(Vector2.ZERO, size)):
|
||||||
return
|
return
|
||||||
draw_rect(rect, Color("34413b", 0.94))
|
canvas.draw_rect(rect, Color("34413b"))
|
||||||
draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce"))
|
canvas.draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce"))
|
||||||
|
|
||||||
|
|
||||||
func _prepare_room_presentations() -> void:
|
func _prepare_room_presentations() -> void:
|
||||||
_room_presentations.clear()
|
var model := _observe_rooms()
|
||||||
_carried_patients.clear()
|
_room_presentations = model[&"rooms"]
|
||||||
_treated_patients.clear()
|
_carried_patients = model[&"carried_patients"]
|
||||||
_resting_staff.clear()
|
_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")
|
var service: Variant = _service(&"facility_service")
|
||||||
if service == null:
|
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():
|
for instance: Variant in service.get_instances():
|
||||||
if instance.origin.z == layer_mode:
|
if instance.origin.z == layer_mode:
|
||||||
_room_presentations[instance.instance_id] = RoomPresentation.describe(instance)
|
_room_presentations[instance.instance_id] = RoomPresentation.describe(instance)
|
||||||
@@ -844,12 +1048,22 @@ func _prepare_room_presentations() -> void:
|
|||||||
else:
|
else:
|
||||||
_carried_patients[task.target_entity_id] = task.assigned_staff_id
|
_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:
|
func _prepare_queues() -> void:
|
||||||
_queue_positions.clear()
|
var model := _observe_queues()
|
||||||
_queue_hidden.clear()
|
_queue_positions = model[&"queue_positions"]
|
||||||
_landing_badges.clear()
|
_queue_hidden = model[&"queue_hidden"]
|
||||||
_transport_report.clear()
|
_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 groups: Dictionary = {}
|
||||||
var facilities: Variant = _service(&"facility_service")
|
var facilities: Variant = _service(&"facility_service")
|
||||||
var guests: Variant = _service(&"guest_service")
|
var guests: Variant = _service(&"guest_service")
|
||||||
@@ -877,7 +1091,8 @@ func _prepare_queues() -> void:
|
|||||||
var lift_cells: Dictionary = {}
|
var lift_cells: Dictionary = {}
|
||||||
for shaft: Dictionary in _transport_report.get(&"shafts", []):
|
for shaft: Dictionary in _transport_report.get(&"shafts", []):
|
||||||
for stop: Vector3i in _cells_from_serialized(shaft.get(&"stops", [])):
|
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 reserved: Dictionary = {}
|
||||||
var cells: Array = groups.keys()
|
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))
|
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)
|
var next := cell + Vector3i(direction*distance, 0, 0)
|
||||||
if world == null or not world.is_walkable(next):
|
if world == null or not world.is_walkable(next):
|
||||||
break
|
break
|
||||||
|
if lift_cells.has(next):
|
||||||
|
break
|
||||||
var occupant: StringName = world.get_occupant(next)
|
var occupant: StringName = world.get_occupant(next)
|
||||||
var facility: Variant = facilities.get_instance(occupant) if facilities != null else null
|
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"]:
|
if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]:
|
||||||
@@ -908,10 +1125,10 @@ func _prepare_queues() -> void:
|
|||||||
index += 1
|
index += 1
|
||||||
for hidden: int in range(index, members.size()):
|
for hidden: int in range(index, members.size()):
|
||||||
_queue_hidden[members[hidden]] = true
|
_queue_hidden[members[hidden]] = true
|
||||||
var text := "待ち%d人" % members.size()
|
var text := RoomPresentation.queue_label(members.size(), members.size()-index)
|
||||||
if index < members.size():
|
_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})
|
||||||
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})
|
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:
|
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"))
|
draw_string(ThemeDB.fallback_font,at+Vector2(4,12),label,HORIZONTAL_ALIGNMENT_CENTER,label_width-8,font_size,Color("f0e2b9"))
|
||||||
|
|
||||||
|
|
||||||
func _draw_elevators() -> void:
|
func _draw_elevators(canvas: CanvasItem) -> void:
|
||||||
_boarded_actor_ids.clear()
|
for row: Dictionary in _life_observation.get(&"cabins", []):
|
||||||
var transport: Variant = _service(&"elevators")
|
PixelArt.elevator_cabin(canvas, row[&"rect"], row[&"pixel"], layer_mode == LayerMode.BACK, row[&"operational"])
|
||||||
if transport == null or not transport.has_method(&"get_report"):
|
PixelArt.elevator_gate(canvas, row[&"rect"], row[&"pixel"], row[&"doors_open"])
|
||||||
_elevator_tracks.clear()
|
_draw_readable_badge(row[&"badge_at"], row[&"badge"], canvas)
|
||||||
return
|
for badge: Dictionary in _life_observation.get(&"landing_badges", []):
|
||||||
var report_value: Variant = _transport_report
|
_draw_readable_badge(badge[&"at"], badge[&"text"], canvas)
|
||||||
if not report_value is Dictionary:
|
|
||||||
return
|
|
||||||
|
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 alive: Dictionary = {}
|
||||||
var p := _cell_dimensions().x/16.0
|
var p := _cell_dimensions().x/16.0
|
||||||
for value: Variant in report_value.get(&"shafts", []):
|
for value: Variant in report_value.get(&"shafts", []):
|
||||||
@@ -1062,7 +1284,7 @@ func _draw_elevators() -> void:
|
|||||||
for actor_id: Variant in passengers:
|
for actor_id: Variant in passengers:
|
||||||
_boarded_actor_ids[_elevator_actor_key(String(actor_id))] = true
|
_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 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:
|
if stops[0].z != layer_mode:
|
||||||
continue
|
continue
|
||||||
var origin := get_cell_rect(stops[0])
|
var origin := get_cell_rect(stops[0])
|
||||||
@@ -1074,10 +1296,9 @@ func _draw_elevators() -> void:
|
|||||||
var operational := bool(shaft.get(&"operational", true))
|
var operational := bool(shaft.get(&"operational", true))
|
||||||
var phase := StringName(shaft.get(&"phase", &"IDLE"))
|
var phase := StringName(shaft.get(&"phase", &"IDLE"))
|
||||||
var doors_open := operational and phase in [&"IDLE", &"BOARDING", &"ALIGHTING"]
|
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 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(passengers.size()):
|
||||||
for index: int in range(visible_count):
|
|
||||||
var actor_key := _elevator_actor_key(String(passengers[index]))
|
var actor_key := _elevator_actor_key(String(passengers[index]))
|
||||||
var is_staff := actor_key.begins_with("staff/")
|
var is_staff := actor_key.begins_with("staff/")
|
||||||
var person: Variant = null
|
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 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 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 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)
|
var center := Vector2(x, feet+3*p-_cell_dimensions().y*0.5)
|
||||||
_draw_pixel_actor(center,actor_key,is_staff,state,false,carrying,&"",true)
|
passengers_drawn.append(_actor_drawing(center,actor_key,is_staff,state,false,carrying,&"",true,observation))
|
||||||
PixelArt.elevator_gate(self,cabin,p,doors_open)
|
cabins.append({&"shaft_id": shaft_id, &"rect": cabin, &"pixel": p,
|
||||||
var capacity := int(shaft.get(&"capacity", 0))
|
&"operational": operational, &"doors_open": doors_open,
|
||||||
var load_text := "%d/%d人" % [passengers.size(), capacity]
|
&"phase": phase, &"passenger_count": passengers.size(),
|
||||||
if capacity > 0 and passengers.size() >= capacity:
|
&"passengers": passengers_drawn,
|
||||||
load_text = "満員 " + load_text
|
&"badge_at": Vector2(cabin.position.x, cabin.position.y-14),
|
||||||
if not operational:
|
&"badge_rect": _readable_badge_rect(Vector2(cabin.position.x, cabin.position.y-14), RoomPresentation.elevator_label(shaft)),
|
||||||
load_text = "停止 " + load_text
|
&"badge": RoomPresentation.elevator_label(shaft)})
|
||||||
_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"])
|
|
||||||
|
|
||||||
for shaft_id: Variant in _elevator_tracks.keys():
|
for shaft_id: Variant in _elevator_tracks.keys():
|
||||||
if not alive.has(shaft_id):
|
if not alive.has(shaft_id):
|
||||||
_elevator_tracks.erase(shaft_id)
|
_elevator_tracks.erase(shaft_id)
|
||||||
|
|
||||||
|
return cabins
|
||||||
|
|
||||||
|
|
||||||
func _elevator_actor_key(actor_id: String) -> String:
|
func _elevator_actor_key(actor_id: String) -> String:
|
||||||
if actor_id.begins_with("guest:"):
|
if actor_id.begins_with("guest:"):
|
||||||
@@ -1115,7 +1335,8 @@ func _elevator_actor_key(actor_id: String) -> String:
|
|||||||
return actor_id
|
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):
|
if not _elevator_tracks.has(shaft_id):
|
||||||
_elevator_tracks[shaft_id] = {&"floor":current_floor,&"from":current_floor,&"at":_animation_time}
|
_elevator_tracks[shaft_id] = {&"floor":current_floor,&"from":current_floor,&"at":_animation_time}
|
||||||
var track: Dictionary = _elevator_tracks[shaft_id]
|
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)
|
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 counts: Dictionary = {}
|
||||||
var alive: Dictionary = {}
|
var alive: Dictionary = {}
|
||||||
var service_slots: Dictionary = {}
|
var service_slots: Dictionary = {}
|
||||||
@@ -1144,18 +1378,18 @@ func _draw_actors() -> void:
|
|||||||
if _boarded_actor_ids.has(key) or _queue_hidden.has(key):
|
if _boarded_actor_ids.has(key) or _queue_hidden.has(key):
|
||||||
_actor_tracks.erase(key)
|
_actor_tracks.erase(key)
|
||||||
continue
|
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:
|
if guest.current_cell.z==layer_mode:
|
||||||
var receiving := not String(guest.current_service_id).is_empty()
|
var receiving := not String(guest.current_service_id).is_empty()
|
||||||
if _queue_positions.has(key):
|
if _queue_positions.has(key):
|
||||||
position = _queue_positions[key]
|
position = _queue_positions[key]
|
||||||
elif receiving:
|
elif receiving:
|
||||||
position = _service_use_position(guest,position,service_slots)
|
position = _service_use_position(guest,position,service_slots,observation)
|
||||||
if not position.is_finite():
|
if not position.is_finite():
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
position += _actor_offset(guest.current_cell,counts)
|
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")
|
var staff_service: Variant = _service(&"staff_service")
|
||||||
if staff_service != null and staff_service.has_method(&"get_staff_members"):
|
if staff_service != null and staff_service.has_method(&"get_staff_members"):
|
||||||
for staff: Variant in staff_service.call(&"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):
|
if _boarded_actor_ids.has(key) or _queue_hidden.has(key):
|
||||||
_actor_tracks.erase(key)
|
_actor_tracks.erase(key)
|
||||||
continue
|
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 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):
|
if _carried_patients.has(staff.staff_id):
|
||||||
var carrier: Variant = staff_service.get_staff(_carried_patients[staff.staff_id])
|
var carrier: Variant = staff_service.get_staff(_carried_patients[staff.staff_id])
|
||||||
if carrier != null:
|
if carrier != null:
|
||||||
position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell) - Vector2(0, 12*_cell_dimensions().x/16.0)
|
position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell,observation) - Vector2(0, 12*_cell_dimensions().x/16.0)
|
||||||
_draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||||
continue
|
continue
|
||||||
if _resting_staff.has(staff.staff_id):
|
if _resting_staff.has(staff.staff_id):
|
||||||
position = _resting_staff_position(staff.staff_id, position)
|
position = _resting_staff_position(staff.staff_id, position,observation)
|
||||||
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||||
continue
|
continue
|
||||||
if _treated_patients.has(staff.staff_id):
|
if _treated_patients.has(staff.staff_id):
|
||||||
position = _rescue_treatment_position(staff.staff_id, position)
|
position = _rescue_treatment_position(staff.staff_id, position,observation)
|
||||||
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||||
continue
|
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():
|
for key: Variant in _actor_tracks.keys():
|
||||||
if not alive.has(key):
|
if not alive.has(key):
|
||||||
_actor_tracks.erase(key)
|
_actor_tracks.erase(key)
|
||||||
|
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
func _staff_work_animation(staff: StaffRuntime) -> StringName:
|
func _staff_work_animation(staff: StaffRuntime) -> StringName:
|
||||||
if staff.role_id != &"cleaner":
|
if staff.role_id != &"cleaner":
|
||||||
@@ -1201,7 +1440,8 @@ func _staff_work_animation(staff: StaffRuntime) -> StringName:
|
|||||||
return &"idle"
|
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]:
|
if staff.state not in [StaffRuntime.State.WORKING, StaffRuntime.State.AVAILABLE]:
|
||||||
return fallback
|
return fallback
|
||||||
var service: Variant = _service(&"facility_service")
|
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)
|
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):
|
if not _actor_tracks.has(key):
|
||||||
_actor_tracks[key] = {&"cell":cell,&"from":cell,&"at":_animation_time,&"direction":1.0}
|
_actor_tracks[key] = {&"cell":cell,&"from":cell,&"at":_animation_time,&"direction":1.0}
|
||||||
var track: Dictionary = _actor_tracks[key]
|
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)
|
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
|
# 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.
|
# occupant into that facility's use position; authoritative paths stay intact.
|
||||||
var id := StringName(guest.target_facility_id)
|
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:
|
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 world_pixel := _cell_dimensions().x / 16.0
|
||||||
var scale := 0.5 if riding else 0.75
|
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
|
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):
|
elif _queue_positions.has(key) or (not staff and state == GuestRuntime.State.QUEUING):
|
||||||
mode = &"wait"
|
mode = &"wait"
|
||||||
var at := (center + Vector2(-8 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 17 * p)).round()
|
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))
|
return {&"key": key, &"center": center, &"at": at, &"scale": p, &"mode": mode,
|
||||||
People.draw(self, at, p, mode, frame, variant, staff, float(track.get(&"direction", 1.0)) < 0.0)
|
&"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:
|
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)
|
var instance: Variant = facility_service.call(&"get_instance", instance_id)
|
||||||
if instance == null:
|
if instance == null:
|
||||||
return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582")
|
return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582")
|
||||||
match int(instance.state):
|
match int(_room_presentations.get(instance_id, {}).get(&"state", instance.state)):
|
||||||
0:
|
FacilityInstance.State.BROKEN:
|
||||||
return Color("a24b45")
|
return Color("a24b45")
|
||||||
1, 2, 3:
|
FacilityInstance.State.NO_ACCESS, FacilityInstance.State.NO_POWER, FacilityInstance.State.NO_STAFF:
|
||||||
return Color("a77d3f")
|
return Color("a77d3f")
|
||||||
4:
|
FacilityInstance.State.CLOSED:
|
||||||
return Color("6b6258")
|
return Color("6b6258")
|
||||||
5:
|
FacilityInstance.State.DEGRADED:
|
||||||
return Color("89914d")
|
return Color("89914d")
|
||||||
_:
|
_:
|
||||||
return Color("6f9a55") if layer_mode == LayerMode.FRONT else Color("67518e")
|
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
|
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 facility_id: StringName = _treated_patients[patient_id]
|
||||||
var world: Variant = _service(&"world_service")
|
var world: Variant = _service(&"world_service")
|
||||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else []
|
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)
|
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 facility_id: StringName = _resting_staff[staff_id]
|
||||||
var world: Variant = _service(&"world_service")
|
var world: Variant = _service(&"world_service")
|
||||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else []
|
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):
|
if not ids.has(id):
|
||||||
ids.append(id)
|
ids.append(id)
|
||||||
ids.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b))
|
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),
|
&"count": ids.size(), &"limit": int(instance.customer_capacity),
|
||||||
&"staff": instance.active_staff_ids.duplicate(), &"suspension": StringName(instance.suspension_reason)}
|
&"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:
|
if count > visible_count:
|
||||||
value += "(他%d人)" % (count - visible_count)
|
value += "(他%d人)" % (count - visible_count)
|
||||||
return value
|
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())
|
||||||