Merge commit 'd2581afd26d1976cdfe4f44c89df49a772524d1e' into HEAD
@@ -3724,6 +3724,17 @@
|
||||
"scope": "normal placement; native and OS pending"
|
||||
}
|
||||
}
|
||||
,{
|
||||
"test": "tests/ui/test_p23_life_observation.gd",
|
||||
"template": "P23 life observation: <checks> checks, <failures> failures",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "750254af7d4b8b00508e25d1ef744837d3a7d14ddebe12398e8014bf5e8c68e8",
|
||||
"authority": {
|
||||
"path": "docs/spec/acceptance/P23-03.json",
|
||||
"scope": "component and read-only observation only; native unexecuted; whole unit blocked"
|
||||
}
|
||||
}
|
||||
],
|
||||
"unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting",
|
||||
"roster_count_is_not_hardcoded": true,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# P23-03 Phase 0 — API and state map
|
||||
|
||||
Base: `860136f7099f5e3aee99da5798a627a77f2d8eea`. Static inspection only; no engine execution.
|
||||
|
||||
## Exact source references
|
||||
|
||||
Line numbers below refer to the base; SHA256 covers the complete file.
|
||||
|
||||
| Source | Lines / API | SHA256 |
|
||||
| --- | --- | --- |
|
||||
| autoload/simulation_runtime.gd | 203 initialize(clock,state,seed); 225 seed assignment; 466 submit_command; 508 get_rng_stream; 511 create_snapshot; 1483 enable_production_operations; 1780 default demand configuration; 2831 arrival boundary; 2949 apply arrivals; 4137 queue_manual_checkpoint; 4141 save_to_path; 4169 load_from_path; 5066 seed streams; 6103 spawn_guest executor; 7629 build_facility executor; 10859 staff management report; 12464 supply management report | 4696df4a77feb465a5a3ba8b806569f7bcbe1d03c88289acfb78a3928f307d67 |
|
||||
| autoload/game_state.gd | 20 reset | cf0e884910220e54c56b5875983e53de439af9d893be1e1b65c2121d2a30722f |
|
||||
| scripts/core/simulation_kernel.gd | 91 reset; 104 advance_minutes; 122 advance_one_minute; 175 set_paused | 341c0c878c4894adbac628d0db6362ff69fc3347bdd9e61e50936e15dc2d1edb |
|
||||
| scripts/facility/facility_service.gd | 109 get_instances; 656 create_snapshot | ceb4a3e9224587423f273cdb2b56e6843ab176ac1fc4fe79dd498b5ed5c33b77 |
|
||||
| scripts/guest/guest_service.gd | 170 get_guests; 982 create_snapshot | 56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e |
|
||||
| scripts/staff/staff_service.gd | 166 get_staff_members; 682 get_tasks; 1064 create_snapshot | 355ad5c3c9f85dc4d4ffc3bc4293e36f81078c403d1662512a735b2c8d815fb3 |
|
||||
| scripts/transport/elevator_service.gd | 345 get_report; 362 create_snapshot | cea09dd9204551c26b31b30cd3aa847cc77e3b75fdac31662b9694a95fb3580c |
|
||||
| scripts/world/path_service.gd | 545 get_cell_traffic | 447135729a1106639a6eee6754af3195c8a8b458ca26980ae91fa39d80dcfbd4 |
|
||||
| scripts/supply/supply_logistics_bridge.gd | 122 get_report | b9e394af788595d2744b71f1907c8e7c3d762d38ff49992b1ee74e63b9639e80 |
|
||||
| scripts/facility/facility_instance.gd | 8 OperationState; 10 State; 30 state and subsequent live fields | 0c6aa493f3299611e05dd26aa429dda8cf7b5c479e4d09632036fb862f79a324 |
|
||||
| tests/transport/test_runtime_transport_comparison.gd | 91 existing fixed-demand setup; 109 real paid build command | ba577397fa8db5a6cf92cfd398dd7a5ece49e6b59856f14772b67b51ddc0bde9 |
|
||||
|
||||
## Deterministic demand capability
|
||||
|
||||
An existing deterministic input path is available without Runtime edits: fresh clock/state, `initialize(clock,state,seed)`, then the same scheduled `submit_command(&"spawn_guest", payload, ..., execute_at, idempotency_key)` sequence. The executor validates registered definitions and real walkable cells, creates wallets and GuestRuntime actors, and verifies Path actor projection and movement credit. Explicit IDs must avoid the reserved `guest-auto-` namespace. Inputs and processed results must both be recorded; a seed/hash alone does not prove identical arrivals.
|
||||
|
||||
The existing transport comparison exercises public spawning, real paid construction and Runtime-owned movement. It is fixed-demand experimentation, not proof of natural autonomous demand or all six observation categories. Its numeric assertions are not adopted as new acceptance thresholds. Automatic production arrivals are a separate path (`enable_production_operations`): population, weather and terminal-state gates mean the same seed alone cannot guarantee identical accepted arrivals. Layouts must use identical production settings and compare actual arrival inputs/results.
|
||||
|
||||
`build_facility` resolves the registered definition, runs `_build_facility_precheck`, then calls `construction_service.start_construction` with `definition.build_minutes`. Construction cost, time and capacities must come from definitions. B may add only the predeclared existing corridor/EV through this command.
|
||||
|
||||
## Clock and observation
|
||||
|
||||
`reset` defines initial time. Paused manual `advance_minutes` is explicitly supported; abort/pause boundaries must be checked. Commands run before minute increment, then time boundaries, mana, construction, logistics, facility completion, vitals, incidents, queues, AI and publication.
|
||||
|
||||
Facility/guest/staff getters expose typed objects; presentation must copy values without mutating them. Elevator reports contain shafts/trips. Path traffic provides actual density/capacity/block fields. Logistics reports contain requests/deliveries, source/destination IDs, carrier, requirements, weight, state/reason and task ID. They do not contain warehouse stock: requested quantities must never be labelled stock.
|
||||
|
||||
## Formal save/load and evidence
|
||||
|
||||
Use `queue_manual_checkpoint` or `save_to_path`, then `load_from_path` under a distinct real PID. The load API pauses the clock and invokes SaveService package validation before restore. `restore_snapshot` alone is not formal resume. Preserve all package members, source/spec/engine/settings pins, whole typed sparse snapshots and actual CUA/process/cleanup evidence.
|
||||
|
||||
Original references read: BASELINE.txt lines 10,13,16,25,37,43,69,83,106,216; IMPLEMENTATION_PLAN.md line 1800; immutable P23-03 spec; recorded direction 83e836e. All seven conditions remain mandatory, with no partial credit. Root native/OS/resume/full-regression evidence is missing.
|
||||
@@ -0,0 +1,253 @@
|
||||
# P23-03 offline repair — evidence gap
|
||||
|
||||
Status: blocked, not static-complete. No acceptance credit. The failed 0b7727a
|
||||
OS run remains failure history. No receipts, mirror files, product, values,
|
||||
styles or test gates were changed. No git writes or engine/OS/collector/full
|
||||
runs were performed.
|
||||
|
||||
## Corrected direction
|
||||
|
||||
The three previous pixel assumptions are superseded:
|
||||
|
||||
1. `viewport_to_screen` maps to content coordinates, not screenshot pixels.
|
||||
Actual request-0002 and request-0004 CUA receipt calibration records window
|
||||
size 1440x810, screenshot 1440x842, titlebar 32, backing scale 1 and stretch 1.
|
||||
The image lookup must apply the calibrated content origin and independently
|
||||
check agreement with actual CUA click offsets; the number 32 must not become
|
||||
a checker constant. Existing affine composition checks remain required.
|
||||
2. Source RGB cannot be required unchanged after JPEG conversion, NO_STAFF
|
||||
dimming, ghost outlines and antialiasing. Palette identity is source
|
||||
provenance, not a screenshot color oracle. Pixel proof needs measured
|
||||
within-image noise and independent expected-class/state/shift alternatives.
|
||||
3. Every report row must be accounted for, but not every row is visible. Use
|
||||
report/audit fields for visible, clipped/clamped, outside with reason,
|
||||
ghost/inactive and facility-state dimming. Require eligible visible coverage
|
||||
of all six categories on both FRONT and BACK. Never silently skip a row.
|
||||
|
||||
The spec requires draw-consumed rows, pixel rectangles and provenance to
|
||||
correspond; it does not require unchanged source RGB. The function docstring
|
||||
now explicitly warns that the implementation still uses the superseded checks.
|
||||
|
||||
## Verified material and blocking dependency
|
||||
|
||||
Mirror:
|
||||
`/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/offline-os-0b7727a-r07`
|
||||
|
||||
All 94 files named by MIRROR_INDEX.json were read and their SHA256 verified;
|
||||
zero missing files or mismatches. The only indexed conversion record is
|
||||
`os-pair/A/writer-request-0002/root-early-conversion.json`, with stdout/stderr.
|
||||
The only decoded PNG is the corresponding `root-early-decoded.png`, SHA256
|
||||
`206a9aafdc50d1f2f330690e14390de8c7ed5b0df024cedf8f2fbd35326f455a`.
|
||||
|
||||
The other three required stable screenshots are JPEG bytes despite .png names:
|
||||
|
||||
- `os-pair/A/writer-request-0002/screen-after.png`
|
||||
- `os-pair/A/writer-request-0004/screen-before.png`
|
||||
- `os-pair/A/writer-request-0004/screen-after.png`
|
||||
|
||||
Their magic starts `ffd8ffe000104a46`. No frozen decoded PNG/conversion records
|
||||
for these files are in the mirror. Searching the original source root named
|
||||
by MIRROR_INDEX for `*decoded*.png` and `*conversion*.json` also returned none.
|
||||
The existing conversion verifier requires a separate PNG, original raw hash,
|
||||
CUA call attribution and pinned `/usr/bin/sips` conversion record. Creating
|
||||
replacement conversion assertions, rewriting receipt paths, or silently
|
||||
changing that converter authority would not preserve this proof gate.
|
||||
|
||||
Minimal next input: Root supplies separate decoded PNGs and genuine conversion
|
||||
records/logs for those three existing raw images, adds their hashes to a new
|
||||
mirror inventory while preserving the original inventory and receipts. This
|
||||
does not require another OS capture. Alternatively the commander must explicitly
|
||||
specify an offline Python JPEG decoder authority and its provenance contract.
|
||||
Worker must not execute sips under the Python-only direction.
|
||||
|
||||
Until then the requested full frozen before/after comparison, BACK cropped
|
||||
pixel fixtures, calibrated noise and per-row pixel results cannot be submitted
|
||||
as complete. Geometry/report preparation alone cannot meet all requested tests.
|
||||
The behavioral repair and new fixtures/tests have not been implemented.
|
||||
|
||||
## Local commands and outputs
|
||||
|
||||
These read-only Python checks used the mirror path above as `p`:
|
||||
|
||||
```python
|
||||
import json, pathlib, hashlib
|
||||
p = pathlib.Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/offline-os-0b7727a-r07')
|
||||
j = json.loads((p / 'MIRROR_INDEX.json').read_text())
|
||||
bad = [r['path'] for r in j['files'] if not (p/r['path']).is_file()
|
||||
or hashlib.sha256((p/r['path']).read_bytes()).hexdigest() != r['sha256']]
|
||||
print('indexed', len(j['files']), 'mismatches', bad)
|
||||
print('conversion records', [r['path'] for r in j['files'] if 'conversion' in r['path']])
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
indexed 94 mismatches []
|
||||
conversion records ['os-pair/A/writer-request-0002/root-early-conversion.json', 'os-pair/A/writer-request-0002/root-early-conversion.json.stderr', 'os-pair/A/writer-request-0002/root-early-conversion.json.stdout']
|
||||
```
|
||||
|
||||
Calling the existing `screenshot_pixels(p/name)` for each of the three paths
|
||||
above (without manufacturing a conversion argument) produced:
|
||||
```
|
||||
writer-request-0002/screen-after.png: JPEG screenshot requires conversion record
|
||||
writer-request-0004/screen-before.png: JPEG screenshot requires conversion record
|
||||
writer-request-0004/screen-after.png: JPEG screenshot requires conversion record
|
||||
```
|
||||
|
||||
Command: `python3 tools/test_p23_life_observation_evidence.py`
|
||||
```
|
||||
................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 32 tests in 0.058s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
These are existing unit tests, not the requested new repair tests, and do not
|
||||
prove the repair or acceptance. Root native/OS/full review remains pending.
|
||||
Seven conditions, six categories, 30/88, no partial credit, timeout 900 and
|
||||
cap null remain unchanged.
|
||||
|
||||
## Follow-on: 106-entry mirror (2026-10-15)
|
||||
|
||||
The missing-conversion dependency above is **resolved**, not a current blocker.
|
||||
The prior text is retained as failure history. The worker read all 106 indexed
|
||||
files and verified every byte count and SHA256, plus the preserved initial94
|
||||
inventory SHA and unchanged entries. No mirror or original receipt was edited.
|
||||
|
||||
`OFFLINE_MIRROR_106_DIAGNOSTICS.json` contains the explicit original absolute
|
||||
path -> mirrored absolute path -> equal indexed SHA256 mapping for all106 files,
|
||||
including the eight named PNG/conversion files. Each conversion was checked
|
||||
against its corresponding actual CUA screenshot call ID/raw hash, pinned sips
|
||||
binary SHA, original argv, output PNG magic/dimensions/hash and stdout/stderr
|
||||
hashes. Original raw/png path strings and argv remain unchanged. A read-only
|
||||
resolver locates indexed mirrored bytes; it does not rewrite conversion records.
|
||||
Direct reads of the source artifact directory were denied by the host
|
||||
(`PermissionError: Operation not permitted`); equality to the frozen inventory
|
||||
and conversion-record hashes was verified, not a fresh direct source-directory
|
||||
comparison. This limitation is not represented as a missing PNG conversion.
|
||||
|
||||
New `--offline-mirror` preparation diagnostics check content/image dimensions,
|
||||
backing scale, content origin derived from recorded titlebar, affine composition,
|
||||
and agreement with the separately recorded click events. They independently
|
||||
derive existing audit row identities/local rectangles from report positions and
|
||||
source palette provenance. Every existing audit row has visible/clipped/outside
|
||||
accounting and a calibrated screenshot rectangle. Room enum/operation names are
|
||||
read from the actual FacilityInstance source. Dimming is derived from operation,
|
||||
not merely the NO_STAFF state name. These are preparation diagnostics, not a
|
||||
replacement acceptance pixel gate. In particular, audit font_width remains an
|
||||
engine assertion, and receipt click events have not been verified against the
|
||||
full helper-definition/tool trace. No source RGB tolerance was invented.
|
||||
|
||||
Actual frozen-frame results:
|
||||
|
||||
- FRONT request0002: 9 audit rows (3 room,1 queue,2 guest,3 business), all visible.
|
||||
EV/staff/goods coverage pending. Two rooms are INACTIVE and one OPERATING.
|
||||
One actual draw-consumed traffic overlay at cell[-6,2,0], local rect
|
||||
[261,294,60,92], has no corresponding rectangle-audit row.
|
||||
- BACK request0004: 4 audit rows (2 room,2 business), all visible and both rooms
|
||||
INACTIVE. Queue/EV/staff/goods coverage pending; no eligible traffic overlay.
|
||||
- Before/after temporal differences within all13 calibrated audit rectangles
|
||||
are zero. This establishes stability only, not spatial JPEG error or correct
|
||||
drawing class/state. It supplies no pixel acceptance credit.
|
||||
- The existing independent typed-stream checker recomputed all14 frozen stream
|
||||
records successfully; the CLI still returned blocked/pending(exit2), as required.
|
||||
|
||||
Current repair status: **incomplete / blocked**, not genuinely submitted.
|
||||
The legacy `compare_rectangles` and its normal-OS call site have deliberately
|
||||
not been relabelled as repaired. Remaining worker work includes an independent
|
||||
expected-class/state/shift pixel discriminator with measured within-image noise,
|
||||
state negatives, full ghost/inactive/traffic draw accounting, independent badge
|
||||
width provenance and integration into the strict OS checker. The added
|
||||
preparation path does not solve these items and must not be used for acceptance.
|
||||
Missing frozen provenance includes the FRONT traffic-overlay rectangle audit,
|
||||
opposite-layer ghost draw audit, and independent font measurements for badge
|
||||
widths. Existing reports/source can support further preparation; these missing
|
||||
records do not establish that a fresh OS capture is already necessary.
|
||||
|
||||
Local validation: Python AST syntax checks; 42 checker unit tests (existing32
|
||||
plus10 preparation tests); actual mirrored checker commands above. Tests cover
|
||||
wrong titlebar/click shifts, PID, nonfinite scales, clipped/outside rectangles,
|
||||
measured temporal noise, report rectangle/identity/coverage/palette tampering.
|
||||
They do not include the still-required pixel-state discriminator negatives.
|
||||
No Godot, native, sips execution, collector, OS run, full regression, git write,
|
||||
branch/integration, Runner action or agent delegation occurred. Root native,
|
||||
complete A/B FRONT/BACK pan/zoom/distinct-PID OS matrix, mandatory full regression
|
||||
and independent Opus review remain pending. Historical baselines remain32 tests,
|
||||
17+17 semantic,319 native,7153/2585 and7/6/30/88; the new local42 count is not a
|
||||
replacement acceptance baseline. Timeout900 and cap null are unchanged.
|
||||
|
||||
## Owner proof-method repair (2026-10-15)
|
||||
|
||||
The owner's recorded decision supersedes the proposed automatic class/state/
|
||||
style discriminator, shift/swap alternative scoring, measured-noise gate and
|
||||
independent badge-width measurement gate. They are no longer required work.
|
||||
Palette RGB is provenance only. Font width remains the recorded engine value;
|
||||
no independent font measurement or numeric image tolerance was introduced.
|
||||
All earlier entries above are retained as history, not current requirements.
|
||||
|
||||
Implemented within the narrowed Python edit scope:
|
||||
|
||||
- Shared `frame_rows` now handles both offline and normal OS screenshot frames,
|
||||
uses receipt calibration checked against actual click events, checks affine
|
||||
composition, independently derives row identity/local geometry/text/palette,
|
||||
validates room state/operation and actor enum names, and retains every audit
|
||||
row with visibility, clipping reason, calibrated image rectangle and failures.
|
||||
- The legacy `compare_rectangles` and its tests are removed and replaced by
|
||||
grounded negatives. Fully outside eligible image crops and exactly uniform
|
||||
crops fail; source RGB is never compared to screenshot RGB.
|
||||
- `drawing_rectangle_rows` now expects every draw-consumed traffic overlay.
|
||||
Missing/extra rows fail the mechanical frame, without synthesizing audit rows.
|
||||
- Pure-Python PNG crops and review manifests are written for each frozen frame.
|
||||
Manifests bind decoded whole-image/raw/conversion/request/receipt hashes,
|
||||
per-row source hash, recorded font width, state, operation, layer and geometry.
|
||||
A separate SHA file hashes the exact manifest bytes. Normal OS frames use the
|
||||
same path and emit per-image review materials. Pixel categories receive no
|
||||
credit without an independent direct-image review verdict bound to that SHA,
|
||||
covering every eligible row with category/state/position verdicts. The worker
|
||||
created no actual review verdict; synthetic verdict files exist only inside
|
||||
temporary unit-test fixtures.
|
||||
|
||||
Current scope blocker, found by reading the actual dependencies:
|
||||
`tests/fixtures/p23-03/passive_observation.gd:150` owns `draw_rectangles`, not
|
||||
`world_preview.gd`. Its explicit room/queue/cabin/actor/badge loops at lines
|
||||
152-171 do not consume traffic audit rows from the view or report. The transform
|
||||
and palette audit path at lines172-182 only processes that locally built array.
|
||||
`world_preview.gd:762` draws the traffic overlay, but has no existing rectangle
|
||||
audit emitter or callback. Changing only world_preview cannot connect traffic
|
||||
rows to the actual request's `rectangles` field without altering the fixture.
|
||||
No inert emitter, mislabelled badge, or checker-generated observed audit was
|
||||
added to pretend this connection exists. World source remains unchanged.
|
||||
|
||||
Minimal separate scope proposal: permit one read-only traffic-row loop in
|
||||
`passive_observation.gd::draw_rectangles`, before its existing transform/palette
|
||||
loop, consuming report traffic rows with density>0 or temporary_block. Use
|
||||
category `traffic`, comma-separated cell coordinates as id, the report rectangle
|
||||
and `scenes/main/world_preview.gd` as style source. Alternatively explicitly
|
||||
authorize wiring a view-owned traffic audit emitter into that same fixture.
|
||||
The broad contract lists this fixture, but the latest finish recipe explicitly
|
||||
narrows edits to checker/tests and world only; the worker did not override it.
|
||||
Step4 is therefore incomplete and this repair is **blocked**, not submitted.
|
||||
|
||||
Local validation: both Python files parse with `ast.parse`; 47 local checker
|
||||
tests pass. New tests exercise blank crops, missing/extra/traffic rows, enum/name
|
||||
errors, affine/click calibration shifts, image-outside crops, exact crop hashes,
|
||||
wrong-manifest/missing/extra/rejected review rows and no credit without review.
|
||||
The r07 offline CLI verified all106 indexed entries and exited2 as blocked:
|
||||
|
||||
- FRONT request0002: expected10 rows, observed9; mechanical failure
|
||||
`offline rectangle coverage`. The traffic overlay at [-6,2,0] remains a genuine
|
||||
unaudited overlay. EV/staff/goods coverage pending; zero pixel credit.
|
||||
- BACK request0004: expected4/observed4; mechanical checks pass.
|
||||
Queue/EV/staff/goods coverage pending; zero pixel credit.
|
||||
|
||||
Current review artifacts are in `offline-r07-review/`; complete diagnostics are
|
||||
in `OFFLINE_REPAIR_OWNER_METHOD_DIAGNOSTICS.json`. The prior
|
||||
`OFFLINE_MIRROR_106_DIAGNOSTICS.json` is preserved unchanged.
|
||||
|
||||
Still pending: Root frozen native checks, fresh A/B FRONT/BACK ordinary pan/zoom
|
||||
OS observation and formal distinct-PID resume, the exact full177 regression,
|
||||
and independent Opus direct-image/all-condition review. No Godot, Runner,
|
||||
native/OS, full regression, git write, integration or agent execution occurred.
|
||||
All7 conditions, all6 categories on both layers/layouts, source recomputation,
|
||||
raw/request/CUA/conversion provenance, calibrated geometry, 30/88 without partial
|
||||
credit, timeout900 and cap null remain unchanged. No acceptance PASS is claimed.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Offline checker repair on 55c29f1
|
||||
|
||||
Source HEAD: 55c29f119234dd6f993c604c4edc6f377fe3951c. Registration base remains
|
||||
860136f7099f5e3aee99da5798a627a77f2d8eea. No git writes or engine execution.
|
||||
|
||||
Read access: MIRROR_INDEX.json and all 2,622 named files were successfully read
|
||||
under `/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/offline-evidence-55c29f1-r06`.
|
||||
No mirror bytes were changed. The initial shell heredoc could not create its
|
||||
temporary file; the read check was executed using Python `-c` instead.
|
||||
|
||||
## First defects and production cause
|
||||
|
||||
Both layouts first failed at frame-030, checker line 328, with KeyError:
|
||||
`shaft_id`. State objects:
|
||||
|
||||
- A: `layout-A/objects/ac3ed64a6e6162918e4d44c45b2f78d09e50d473b607f3469350a06074a7efb3.json`
|
||||
- B: `layout-B/objects/97ea43afe3d1b21602ebe2a602e7b7d796ff05f03983cd4d1b3edc9157e39a08.json`
|
||||
|
||||
The exception came from `shaft['shaft_id']`, not `t['shaft_id']`. Every trip in
|
||||
these failing frames has shaft_id. Saved shaft rows have it only in config.
|
||||
There is no evidence of shaft-less QUEUED trips to support accepting that shape.
|
||||
|
||||
`autoload/simulation_runtime.gd:541` writes elevators.create_snapshot().
|
||||
`scripts/transport/elevator_service.gd:362-367` copies the saved cars without
|
||||
flattening config; get_report at 345-359 merges config into each report row before
|
||||
counting queued trips. request at 169-171 assigns shaft_id on creation, and the
|
||||
restore validator at 412-454 requires the exact trip keys and shaft/connector
|
||||
agreement. The connector spelling is defined at 617-618; trips are ordered at
|
||||
707-710. `scenes/main/world_preview.gd:973-975` consumes get_report and sorts
|
||||
trips by enqueue time/actor. The checker now counts using the merged row identity.
|
||||
|
||||
## Reviewable changes
|
||||
|
||||
- `tools/check_p23_life_observation_evidence.py`: replace
|
||||
`t['shaft_id'] == shaft['shaft_id']` with `t['shaft_id'] == row['shaft_id']`;
|
||||
validate exact trip keys, companion identity, shaft/stops/connector attribution
|
||||
and QUEUED/BOARDED state before recomputing. Whole report equality remains strict.
|
||||
- `tests/fixtures/p23-03/queued_elevator.json`: retain one actual frame-030
|
||||
queued trip and its shaft, without a cabin seat. Distilled unit data only.
|
||||
- `tools/test_p23_life_observation_evidence.py`: queued shape positive and
|
||||
shaft attribution, waiting_count, missing/extra key negatives; update two
|
||||
pre-existing abbreviated synthetic fixtures to the actual shaft/trip shape.
|
||||
- This repair report. The complete implementation diff is available through
|
||||
`git diff -- tools/check_p23_life_observation_evidence.py tools/test_p23_life_observation_evidence.py`;
|
||||
the new fixture and this file are untracked submission files.
|
||||
|
||||
## Local results
|
||||
|
||||
`inspect_stream` on each exact absolute mirror directory:
|
||||
|
||||
```
|
||||
layout-A inspect_stream PASS 17 frames
|
||||
layout-B inspect_stream PASS 17 frames
|
||||
```
|
||||
|
||||
For both, labels are frame-000, frame-030, frame-060, frame-090, frame-120,
|
||||
frame-150, frame-180, frame-210, frame-240, frame-270, frame-300, frame-330,
|
||||
frame-360, frame-390, frame-420, frame-450, frame-480. Each returns fields:
|
||||
guests, staff, tasks, elevators, room_service_fields, queues, traffic, goods,
|
||||
cabin_geometry. Every frame also passes the existing two-motion-history-frame
|
||||
check, unchanged. No later schema/semantic mismatch was observed.
|
||||
|
||||
Python syntax AST checks pass. Local checker tests: 32 tests, OK.
|
||||
The initial test run exposed two abbreviated synthetic fixtures; subsequent
|
||||
queue expectations were corrected for actual lift landing exclusion. Tests
|
||||
were preserved. git diff --check passes; changed files are within allowed_paths.
|
||||
completion_marker_contracts retains its one required P23 strict template entry;
|
||||
no marker changes. Seven conditions, six categories and 30/88 remain unchanged.
|
||||
|
||||
Root native results (319 tests, A/B PASS), matched metric values (48 metrics,
|
||||
7153/2585), production data and services were not modified. Root OS evidence
|
||||
remains pending as expected at worker submission; this repair grants no acceptance.
|
||||
@@ -0,0 +1,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 _landing_badges: Array[Dictionary] = []
|
||||
var _transport_report: Dictionary = {}
|
||||
var _life_observation: Dictionary = {}
|
||||
var _life_overlay: Node2D
|
||||
var _life_observation_serial := 0
|
||||
var _dragging := false
|
||||
var _drag_button := MOUSE_BUTTON_NONE
|
||||
|
||||
@@ -103,6 +106,10 @@ func _ready() -> void:
|
||||
_inactive_layer_art = InactiveLayerRooms.new()
|
||||
_inactive_layer_art.name = "InactiveLayerRooms"
|
||||
add_child(_inactive_layer_art)
|
||||
_life_overlay = Node2D.new()
|
||||
_life_overlay.name = "LifeOverlay"
|
||||
_life_overlay.draw.connect(_draw_life_overlay)
|
||||
add_child(_life_overlay)
|
||||
_layer_lens = ColorRect.new()
|
||||
_layer_lens.name = "LayerLens"
|
||||
_layer_lens.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -511,6 +518,9 @@ func _draw() -> void:
|
||||
|
||||
|
||||
func _draw_contents() -> void:
|
||||
_life_observation = {}
|
||||
if _life_overlay != null:
|
||||
_life_overlay.queue_redraw()
|
||||
if _inactive_layer_art != null:
|
||||
_inactive_layer_art.visible = false
|
||||
_draw_background()
|
||||
@@ -521,18 +531,27 @@ func _draw_contents() -> void:
|
||||
var map_rect := get_map_rect()
|
||||
if map_rect.size == Vector2.ZERO:
|
||||
return
|
||||
_life_observation = _build_life_observation()
|
||||
_life_observation_serial += 1
|
||||
_life_observation[&"observation_serial"] = _life_observation_serial
|
||||
_life_observation[&"overlay_consumed_serial"] = -1
|
||||
_room_presentations = _life_observation[&"rooms"]
|
||||
_carried_patients = _life_observation[&"carried_patients"]
|
||||
_treated_patients = _life_observation[&"treated_patients"]
|
||||
_resting_staff = _life_observation[&"resting_staff"]
|
||||
_queue_positions = _life_observation[&"queue_positions"]
|
||||
_queue_hidden = _life_observation[&"queue_hidden"]
|
||||
_landing_badges.assign(_life_observation[&"landing_badges"])
|
||||
_transport_report = _life_observation[&"elevators"]
|
||||
_boarded_actor_ids = _life_observation[&"boarded"]
|
||||
_actor_tracks = _life_observation[&"actor_tracks"]
|
||||
_elevator_tracks = _life_observation[&"elevator_tracks"]
|
||||
_draw_world_cells(bounds, map_rect)
|
||||
if show_grid:
|
||||
_draw_congestion_overlay(bounds)
|
||||
_prepare_room_presentations()
|
||||
_prepare_queues()
|
||||
_draw_world_entity_icons()
|
||||
_draw_legacy_stairs()
|
||||
_draw_elevators()
|
||||
_draw_other_layer()
|
||||
_draw_construction_labels()
|
||||
_draw_facility_labels()
|
||||
_draw_actors()
|
||||
_draw_excavation_jobs()
|
||||
_draw_build_preview()
|
||||
_draw_excavation_preview()
|
||||
@@ -540,6 +559,177 @@ func _draw_contents() -> void:
|
||||
_draw_map_caption(map_rect)
|
||||
|
||||
|
||||
## The report describes the last submitted draw, not an independently sampled world.
|
||||
## Callers cannot mutate either the frame or its interpolation tracks.
|
||||
func get_life_observation_report() -> Dictionary:
|
||||
return _life_observation.duplicate(true)
|
||||
|
||||
|
||||
## Pure with respect to Runtime and this Control: helpers mutate only fresh local
|
||||
## dictionaries (including copies of visual tracks). No whole Runtime snapshot
|
||||
## is copied per frame; whole typed snapshots belong to sparse diagnostics.
|
||||
func _build_life_observation() -> Dictionary:
|
||||
var model := _observe_rooms()
|
||||
model.merge(_observe_queues())
|
||||
model[&"layer"] = layer_mode
|
||||
model[&"zoom"] = zoom_level
|
||||
model[&"pan"] = pan_offset
|
||||
model[&"viewport"] = size
|
||||
model[&"animation_time"] = _animation_time
|
||||
model[&"actor_tracks"] = _actor_tracks.duplicate(true)
|
||||
model[&"elevator_tracks"] = _elevator_tracks.duplicate(true)
|
||||
model[&"boarded"] = {}
|
||||
var clock := get_node_or_null("/root/GameClock")
|
||||
model[&"paused"] = clock != null and bool(clock.get("paused"))
|
||||
model[&"cabins"] = _observe_elevators(model)
|
||||
model[&"actor_drawings"] = _observe_actors(model)
|
||||
var guests: Array[Dictionary] = []
|
||||
var staff: Array[Dictionary] = []
|
||||
var tasks: Array[Dictionary] = []
|
||||
var guest_service: Variant = _service(&"guest_service")
|
||||
if guest_service != null and guest_service.has_method(&"get_guests"):
|
||||
for guest: GuestRuntime in guest_service.get_guests():
|
||||
guests.append({&"id": guest.guest_id, &"state": GuestRuntime.State.keys()[guest.state],
|
||||
&"cell": guest.current_cell, &"target": guest.target_facility_id,
|
||||
&"service": guest.current_service_id, &"queue": guest.queue_request_id})
|
||||
var staff_service: Variant = _service(&"staff_service")
|
||||
if staff_service != null:
|
||||
for member: StaffRuntime in staff_service.get_staff_members():
|
||||
staff.append({&"id": member.staff_id, &"state": StaffRuntime.State.keys()[member.state],
|
||||
&"cell": member.current_cell, &"task": member.current_task_id,
|
||||
&"facility": member.assigned_facility_id, &"role": member.role_id,
|
||||
&"carried_inventory_id": member.carried_inventory_id})
|
||||
for task: StaffTask in staff_service.get_tasks():
|
||||
tasks.append({&"id": task.task_id, &"state": StaffTask.State.keys()[task.state],
|
||||
&"type": task.task_type, &"staff": task.assigned_staff_id,
|
||||
&"target": task.target_entity_id, &"rescue_facility": task.rescue_facility_id})
|
||||
model[&"guests"] = guests
|
||||
model[&"staff"] = staff
|
||||
model[&"tasks"] = tasks
|
||||
_fit_life_actors(model)
|
||||
var bridge: Variant = _service(&"supply_logistics")
|
||||
var goods: Dictionary = bridge.get_report() if bridge != null else {&"requests": [], &"deliveries": []}
|
||||
model[&"goods"] = goods
|
||||
var badges: Array[Dictionary] = []
|
||||
var facilities: Variant = _service(&"facility_service")
|
||||
var world: Variant = _service(&"world_service")
|
||||
if facilities != null and world != null:
|
||||
for instance: FacilityInstance in facilities.get_instances():
|
||||
if instance.origin.z != layer_mode:
|
||||
continue
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(instance.instance_id)
|
||||
if cells.is_empty():
|
||||
continue
|
||||
var rect := _cell_bounds_rect(cells)
|
||||
var data: Dictionary = model[&"rooms"][instance.instance_id]
|
||||
data[&"rect"] = rect
|
||||
data[&"definition_id"] = instance.definition_id
|
||||
var count := RoomPresentation.slots(rect, _cell_dimensions().x / 16.0, instance.definition_id).size()
|
||||
badges.append({&"facility": instance.instance_id, &"kind": &"business",
|
||||
&"at": rect.position + Vector2(2, 2),
|
||||
&"text": RoomPresentation.business_label(data) + " " + RoomPresentation.label(data, count)})
|
||||
var requests: Array[Dictionary] = []
|
||||
for request: Dictionary in goods.get(&"requests", []):
|
||||
if request[&"source_facility_id"] == instance.instance_id or request[&"destination_facility_id"] == instance.instance_id:
|
||||
requests.append(request)
|
||||
if not requests.is_empty():
|
||||
badges.append({&"facility": instance.instance_id, &"kind": &"supply",
|
||||
&"at": rect.position + Vector2(2, 16), &"requests": requests,
|
||||
&"text": RoomPresentation.supply_label(requests)})
|
||||
model[&"badges"] = badges
|
||||
_resolve_observation_labels(model)
|
||||
var obstacles: Array[Rect2] = []
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
obstacles.append(cabin[&"rect"])
|
||||
obstacles.append(cabin[&"badge_rect"])
|
||||
for collection: Array in [badges, model[&"landing_badges"], model.get(&"actor_aggregations", [])]:
|
||||
for badge: Dictionary in collection:
|
||||
obstacles.append(_readable_badge_rect(badge[&"at"], badge[&"text"]))
|
||||
model[&"traffic_obstacles"] = obstacles
|
||||
var traffic: Array[Dictionary] = []
|
||||
var path: Variant = _service(&"path_service")
|
||||
var bounds := _world_bounds()
|
||||
if path != null and not bounds.is_empty():
|
||||
for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1):
|
||||
for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1):
|
||||
var cell := Vector3i(x, y, layer_mode)
|
||||
var row: Dictionary = path.get_cell_traffic(cell)
|
||||
if not row.is_empty():
|
||||
var cell_rect := get_cell_rect(cell)
|
||||
var rect := cell_rect.grow(-2.0)
|
||||
var world_pixel := _cell_dimensions().x / 16.0
|
||||
var thickness := clampf(rect.size.y * 0.16, 2.0, 5.0)
|
||||
var band := Rect2(Vector2(rect.position.x + rect.size.x * 0.12,
|
||||
cell_rect.end.y - 2 * world_pixel - thickness * 0.5),
|
||||
Vector2(rect.size.x * 0.76, thickness))
|
||||
traffic.append({&"cell": cell, &"traffic": row, &"rect": rect, &"band_rect": band,
|
||||
&"segment_rects": _traffic_segments(band, rect, obstacles)})
|
||||
model[&"traffic"] = traffic
|
||||
return model
|
||||
|
||||
|
||||
## Subtract opaque observation regions, retaining a visible density mark even
|
||||
## when the ordinary floor band is completely behind a moving cabin.
|
||||
func _traffic_segments(band: Rect2, cell: Rect2, obstacles: Array[Rect2]) -> Array[Rect2]:
|
||||
for y: float in [band.position.y, cell.position.y, cell.end.y - band.size.y]:
|
||||
var pieces: Array[Rect2] = [Rect2(Vector2(band.position.x, y), band.size)]
|
||||
for obstacle: Rect2 in obstacles:
|
||||
var remaining: Array[Rect2] = []
|
||||
for piece: Rect2 in pieces:
|
||||
var cut := piece.intersection(obstacle)
|
||||
if not cut.has_area():
|
||||
remaining.append(piece)
|
||||
continue
|
||||
for part: Rect2 in [Rect2(piece.position, Vector2(piece.size.x, cut.position.y-piece.position.y)),
|
||||
Rect2(Vector2(piece.position.x, cut.end.y), Vector2(piece.size.x, piece.end.y-cut.end.y)),
|
||||
Rect2(Vector2(piece.position.x, cut.position.y), Vector2(cut.position.x-piece.position.x, cut.size.y)),
|
||||
Rect2(Vector2(cut.end.x, cut.position.y), Vector2(piece.end.x-cut.end.x, cut.size.y))]:
|
||||
if part.has_area():
|
||||
remaining.append(part)
|
||||
pieces = remaining
|
||||
if not pieces.is_empty():
|
||||
return pieces
|
||||
return []
|
||||
|
||||
|
||||
func _resolve_observation_labels(model: Dictionary) -> void:
|
||||
var placed: Array[Dictionary] = []
|
||||
for i: int in model[&"cabins"].size():
|
||||
var cabin: Dictionary = model[&"cabins"][i]
|
||||
placed.append({&"id": "cabin/%d/rect" % i, &"rect": cabin[&"rect"]})
|
||||
placed.append({&"id": "cabin/%d/badge" % i, &"rect": cabin[&"badge_rect"]})
|
||||
for kind: String in ["business", "supply", "landing", "aggregation"]:
|
||||
var collection: Array = model[&"badges"] if kind in ["business", "supply"] else model[&"landing_badges"] if kind == "landing" else model.get(&"actor_aggregations", [])
|
||||
for i: int in collection.size():
|
||||
var badge: Dictionary = collection[i]
|
||||
if kind in ["business", "supply"] and badge[&"kind"] != kind:
|
||||
continue
|
||||
var anchor: Vector2 = badge[&"at"]
|
||||
var at := anchor
|
||||
var displaced_by: Array[String] = []
|
||||
# Each collision advances to an existing right edge. Positive widths
|
||||
# guarantee termination; touching edges are allowed.
|
||||
while true:
|
||||
var blocker: Dictionary = {}
|
||||
for previous: Dictionary in placed:
|
||||
if _readable_badge_rect(at, badge[&"text"]).intersection(previous[&"rect"]).has_area():
|
||||
blocker = previous
|
||||
break
|
||||
if blocker.is_empty():
|
||||
break
|
||||
displaced_by.append(blocker[&"id"])
|
||||
at.x = (blocker[&"rect"] as Rect2).end.x
|
||||
badge[&"anchor"] = anchor
|
||||
badge[&"at"] = at
|
||||
badge[&"displaced_by"] = displaced_by.duplicate(true)
|
||||
placed.append({&"id": "%s/%d" % [kind, i], &"rect": _readable_badge_rect(at, badge[&"text"])})
|
||||
|
||||
|
||||
func _draw_life_badges(canvas: CanvasItem) -> void:
|
||||
for badge: Dictionary in _life_observation.get(&"badges", []):
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"], canvas)
|
||||
|
||||
|
||||
func _draw_background() -> void:
|
||||
var night := false
|
||||
var clock := get_node_or_null("/root/GameClock")
|
||||
@@ -650,40 +840,43 @@ func _draw_leaf_cluster(center: Vector2, p: float, variation: int) -> void:
|
||||
draw_rect(Rect2(c+offset,Vector2(3+i%2,2)*p),Color("95ad69") if i%3==0 else Color("648957"))
|
||||
|
||||
|
||||
func _draw_congestion_overlay(bounds: Dictionary) -> void:
|
||||
var path_service: Variant = _service(&"path_service")
|
||||
if path_service == null or not path_service.has_method(&"get_cell_traffic"):
|
||||
func _draw_life_overlay() -> void:
|
||||
if _life_observation.is_empty():
|
||||
return
|
||||
for y in range(int(bounds[&"min_y"]), int(bounds[&"max_y"]) + 1):
|
||||
for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1):
|
||||
var cell := Vector3i(x, y, layer_mode)
|
||||
var traffic_value: Variant = path_service.call(&"get_cell_traffic", cell)
|
||||
if not traffic_value is Dictionary or traffic_value.is_empty():
|
||||
continue
|
||||
var traffic: Dictionary = traffic_value
|
||||
var density := int(traffic.get(&"current_density", 0))
|
||||
var capacity := maxi(int(traffic.get(&"base_capacity", 1)), 1)
|
||||
var blocked := bool(traffic.get(&"temporary_block", false))
|
||||
if density <= 0 and not blocked:
|
||||
continue
|
||||
var ratio := float(density) / float(capacity)
|
||||
var color := Color("55c96b")
|
||||
if blocked or ratio > 1.0:
|
||||
color = Color("e34332")
|
||||
elif ratio >= 0.65:
|
||||
color = Color("e3a62f")
|
||||
var rect := get_cell_rect(cell).grow(-2.0)
|
||||
var center := rect.get_center()
|
||||
var route_width := clampf(rect.size.y * 0.16, 2.0, 5.0)
|
||||
draw_line(
|
||||
Vector2(rect.position.x + rect.size.x * 0.12, center.y),
|
||||
Vector2(rect.end.x - rect.size.x * 0.12, center.y),
|
||||
Color(color, 0.90),
|
||||
route_width
|
||||
)
|
||||
if blocked:
|
||||
draw_line(rect.position, rect.end, color, 2.0)
|
||||
draw_line(Vector2(rect.end.x, rect.position.y), Vector2(rect.position.x, rect.end.y), color, 2.0)
|
||||
# These are actual child indices and the passes executed by this callback.
|
||||
_life_observation[&"overlay_child_indices"] = [_inactive_layer_art.get_index(), _life_overlay.get_index(), _layer_lens.get_index()]
|
||||
_life_observation[&"overlay_passes"] = []
|
||||
_life_observation[&"overlay_passes"].append(&"traffic")
|
||||
_draw_congestion_overlay(_life_overlay)
|
||||
_life_observation[&"overlay_passes"].append(&"cabins")
|
||||
_draw_elevators(_life_overlay)
|
||||
_draw_life_badges(_life_overlay)
|
||||
_life_observation[&"overlay_passes"].append(&"actors")
|
||||
for cabin: Dictionary in _life_observation.get(&"cabins", []):
|
||||
for actor: Dictionary in cabin[&"passengers"]:
|
||||
_draw_observed_actor(actor, _life_overlay)
|
||||
_draw_actors(_life_overlay)
|
||||
for badge: Dictionary in _life_observation.get(&"actor_aggregations", []):
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"], _life_overlay)
|
||||
_life_observation[&"overlay_consumed_serial"] = _life_observation[&"observation_serial"]
|
||||
|
||||
|
||||
func _draw_congestion_overlay(canvas: CanvasItem) -> void:
|
||||
for row: Dictionary in _life_observation.get(&"traffic", []):
|
||||
var traffic: Dictionary = row[&"traffic"]
|
||||
var density := int(traffic[&"current_density"])
|
||||
var blocked := bool(traffic[&"temporary_block"])
|
||||
if density <= 0 and not blocked:
|
||||
continue
|
||||
var ratio := float(density) / maxi(int(traffic[&"base_capacity"]), 1)
|
||||
var color := Color("e34332") if blocked or ratio > 1.0 else Color("e3a62f") if ratio >= 0.65 else Color("55c96b")
|
||||
# At overview zoom a whole cell can be covered by opaque badges. Still
|
||||
# submit its density band, beneath those later draws, and report no visible
|
||||
# fragments. No traffic cell or raw density row is dropped.
|
||||
if row[&"segment_rects"].is_empty():
|
||||
canvas.draw_rect(row[&"band_rect"], Color(color, 0.90))
|
||||
for segment: Rect2 in row[&"segment_rects"]:
|
||||
canvas.draw_rect(segment, Color(color, 0.90))
|
||||
|
||||
|
||||
## Visual-only level comes from the existing live FacilityInstance, not the
|
||||
@@ -794,29 +987,40 @@ func _draw_world_entity_icons() -> void:
|
||||
draw_rect(Rect2(rect.end.x-4*p,rect.position.y+2*p,2*p,2*p),_facility_color(instance_id))
|
||||
if instance_id==selected_entity_id:
|
||||
draw_rect(rect.grow(-p),Color("f5d582"),false,2*p)
|
||||
var data: Dictionary = _room_presentations.get(instance_id, {})
|
||||
if not data.is_empty() and (rect.size.x >= 80 or instance_id == selected_entity_id) and id not in [&"guest_corridor", &"service_corridor", &"guest_stairs", &"service_stairs", &"guest_elevator", &"service_elevator"]:
|
||||
var status := RoomPresentation.label(data, RoomPresentation.slots(rect, p, id).size())
|
||||
_draw_readable_badge(rect.position+Vector2(2, 2), status.replace("利用", "治療") if id == &"infirmary" else status.replace("利用", "休憩") if id in [&"staff_waiting_room", &"staff_dormitory"] else status)
|
||||
|
||||
|
||||
func _draw_readable_badge(at: Vector2, text: String) -> void:
|
||||
var width := ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x+6
|
||||
var rect := Rect2(at, Vector2(width, 14))
|
||||
func _readable_badge_rect(at: Vector2, text: String) -> Rect2:
|
||||
# A conservative em-width box makes geometry independently reproducible.
|
||||
# Text still uses the existing fallback font and size.
|
||||
return Rect2(at, Vector2(text.length() * 10 + 6, 14))
|
||||
|
||||
|
||||
func _draw_readable_badge(at: Vector2, text: String, canvas: CanvasItem = null) -> void:
|
||||
if canvas == null:
|
||||
canvas = self
|
||||
var rect := _readable_badge_rect(at, text)
|
||||
if not rect.intersects(Rect2(Vector2.ZERO, size)):
|
||||
return
|
||||
draw_rect(rect, Color("34413b", 0.94))
|
||||
draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce"))
|
||||
canvas.draw_rect(rect, Color("34413b"))
|
||||
canvas.draw_string(ThemeDB.fallback_font, at+Vector2(3, 11), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color("fff2ce"))
|
||||
|
||||
|
||||
func _prepare_room_presentations() -> void:
|
||||
_room_presentations.clear()
|
||||
_carried_patients.clear()
|
||||
_treated_patients.clear()
|
||||
_resting_staff.clear()
|
||||
var model := _observe_rooms()
|
||||
_room_presentations = model[&"rooms"]
|
||||
_carried_patients = model[&"carried_patients"]
|
||||
_treated_patients = model[&"treated_patients"]
|
||||
_resting_staff = model[&"resting_staff"]
|
||||
|
||||
|
||||
func _observe_rooms() -> Dictionary:
|
||||
var _room_presentations: Dictionary = {}
|
||||
var _carried_patients: Dictionary = {}
|
||||
var _treated_patients: Dictionary = {}
|
||||
var _resting_staff: Dictionary = {}
|
||||
var service: Variant = _service(&"facility_service")
|
||||
if service == null:
|
||||
return
|
||||
return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff}
|
||||
for instance: Variant in service.get_instances():
|
||||
if instance.origin.z == layer_mode:
|
||||
_room_presentations[instance.instance_id] = RoomPresentation.describe(instance)
|
||||
@@ -844,12 +1048,22 @@ func _prepare_room_presentations() -> void:
|
||||
else:
|
||||
_carried_patients[task.target_entity_id] = task.assigned_staff_id
|
||||
|
||||
return {&"rooms": _room_presentations, &"carried_patients": _carried_patients, &"treated_patients": _treated_patients, &"resting_staff": _resting_staff}
|
||||
|
||||
|
||||
func _prepare_queues() -> void:
|
||||
_queue_positions.clear()
|
||||
_queue_hidden.clear()
|
||||
_landing_badges.clear()
|
||||
_transport_report.clear()
|
||||
var model := _observe_queues()
|
||||
_queue_positions = model[&"queue_positions"]
|
||||
_queue_hidden = model[&"queue_hidden"]
|
||||
_landing_badges.assign(model[&"landing_badges"])
|
||||
_transport_report = model[&"elevators"]
|
||||
|
||||
|
||||
func _observe_queues() -> Dictionary:
|
||||
var _queue_positions: Dictionary = {}
|
||||
var _queue_hidden: Dictionary = {}
|
||||
var _landing_badges: Array[Dictionary] = []
|
||||
var _transport_report: Dictionary = {}
|
||||
var groups: Dictionary = {}
|
||||
var facilities: Variant = _service(&"facility_service")
|
||||
var guests: Variant = _service(&"guest_service")
|
||||
@@ -877,7 +1091,8 @@ func _prepare_queues() -> void:
|
||||
var lift_cells: Dictionary = {}
|
||||
for shaft: Dictionary in _transport_report.get(&"shafts", []):
|
||||
for stop: Vector3i in _cells_from_serialized(shaft.get(&"stops", [])):
|
||||
lift_cells[stop] = true
|
||||
for offset: int in range(maxi(int(shaft.get(&"width", 2)), 1)):
|
||||
lift_cells[stop + Vector3i(offset, 0, 0)] = true
|
||||
var reserved: Dictionary = {}
|
||||
var cells: Array = groups.keys()
|
||||
cells.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return a.y < b.y or (a.y == b.y and a.x < b.x))
|
||||
@@ -892,6 +1107,8 @@ func _prepare_queues() -> void:
|
||||
var next := cell + Vector3i(direction*distance, 0, 0)
|
||||
if world == null or not world.is_walkable(next):
|
||||
break
|
||||
if lift_cells.has(next):
|
||||
break
|
||||
var occupant: StringName = world.get_occupant(next)
|
||||
var facility: Variant = facilities.get_instance(occupant) if facilities != null else null
|
||||
if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]:
|
||||
@@ -908,10 +1125,10 @@ func _prepare_queues() -> void:
|
||||
index += 1
|
||||
for hidden: int in range(index, members.size()):
|
||||
_queue_hidden[members[hidden]] = true
|
||||
var text := "待ち%d人" % members.size()
|
||||
if index < members.size():
|
||||
text += "(他%d人)" % (members.size()-index)
|
||||
_landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text})
|
||||
var text := RoomPresentation.queue_label(members.size(), members.size()-index)
|
||||
_landing_badges.append({&"at": get_cell_rect(cell).position + Vector2(-80 if lift_cells.has(cell) else 0, -16), &"text": text, &"cell": cell, &"members": members.duplicate(), &"visible_count": index, &"overflow": members.size()-index})
|
||||
|
||||
return {&"queue_positions": _queue_positions, &"queue_hidden": _queue_hidden, &"landing_badges": _landing_badges, &"elevators": _transport_report, &"shaft_footprint": lift_cells.keys()}
|
||||
|
||||
|
||||
func _add_queue_member(groups: Dictionary, key: String, cell: Vector3i) -> void:
|
||||
@@ -1038,15 +1255,20 @@ func _draw_facility_labels() -> void:
|
||||
draw_string(ThemeDB.fallback_font,at+Vector2(4,12),label,HORIZONTAL_ALIGNMENT_CENTER,label_width-8,font_size,Color("f0e2b9"))
|
||||
|
||||
|
||||
func _draw_elevators() -> void:
|
||||
_boarded_actor_ids.clear()
|
||||
var transport: Variant = _service(&"elevators")
|
||||
if transport == null or not transport.has_method(&"get_report"):
|
||||
_elevator_tracks.clear()
|
||||
return
|
||||
var report_value: Variant = _transport_report
|
||||
if not report_value is Dictionary:
|
||||
return
|
||||
func _draw_elevators(canvas: CanvasItem) -> void:
|
||||
for row: Dictionary in _life_observation.get(&"cabins", []):
|
||||
PixelArt.elevator_cabin(canvas, row[&"rect"], row[&"pixel"], layer_mode == LayerMode.BACK, row[&"operational"])
|
||||
PixelArt.elevator_gate(canvas, row[&"rect"], row[&"pixel"], row[&"doors_open"])
|
||||
_draw_readable_badge(row[&"badge_at"], row[&"badge"], canvas)
|
||||
for badge: Dictionary in _life_observation.get(&"landing_badges", []):
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"], canvas)
|
||||
|
||||
|
||||
func _observe_elevators(observation: Dictionary) -> Array[Dictionary]:
|
||||
var cabins: Array[Dictionary] = []
|
||||
var _boarded_actor_ids: Dictionary = observation[&"boarded"]
|
||||
var _elevator_tracks: Dictionary = observation[&"elevator_tracks"]
|
||||
var report_value: Dictionary = observation[&"elevators"]
|
||||
var alive: Dictionary = {}
|
||||
var p := _cell_dimensions().x/16.0
|
||||
for value: Variant in report_value.get(&"shafts", []):
|
||||
@@ -1062,7 +1284,7 @@ func _draw_elevators() -> void:
|
||||
for actor_id: Variant in passengers:
|
||||
_boarded_actor_ids[_elevator_actor_key(String(actor_id))] = true
|
||||
var floor_value := float(shaft.get(&"visual_floor", shaft.get(&"current_floor", stops[0].y)))
|
||||
var visual_floor := _tracked_elevator_floor(shaft_id, floor_value)
|
||||
var visual_floor := _tracked_elevator_floor(shaft_id, floor_value, observation)
|
||||
if stops[0].z != layer_mode:
|
||||
continue
|
||||
var origin := get_cell_rect(stops[0])
|
||||
@@ -1074,10 +1296,9 @@ func _draw_elevators() -> void:
|
||||
var operational := bool(shaft.get(&"operational", true))
|
||||
var phase := StringName(shaft.get(&"phase", &"IDLE"))
|
||||
var doors_open := operational and phase in [&"IDLE", &"BOARDING", &"ALIGHTING"]
|
||||
PixelArt.elevator_cabin(self,cabin,p,layer_mode==LayerMode.BACK,operational)
|
||||
var passengers_drawn: Array[Dictionary] = []
|
||||
var seat_columns := maxi(int((cabin.size.x / p - 4.0) / 8.0), 1)
|
||||
var visible_count := mini(passengers.size(), seat_columns*2)
|
||||
for index: int in range(visible_count):
|
||||
for index: int in range(passengers.size()):
|
||||
var actor_key := _elevator_actor_key(String(passengers[index]))
|
||||
var is_staff := actor_key.begins_with("staff/")
|
||||
var person: Variant = null
|
||||
@@ -1088,24 +1309,23 @@ func _draw_elevators() -> void:
|
||||
var carrying := is_staff and person != null and not String(person.carried_inventory_id).is_empty()
|
||||
var state := int(person.state) if person != null else 0
|
||||
var x := cabin.position.x + (2.0 + (index % seat_columns + 0.5)*8.0)*p
|
||||
var feet := cabin.end.y - (2.0 + int(index / seat_columns)*9.0)*p
|
||||
var feet := cabin.end.y - (2.0 + (int(index / seat_columns) % 2)*9.0)*p
|
||||
var center := Vector2(x, feet+3*p-_cell_dimensions().y*0.5)
|
||||
_draw_pixel_actor(center,actor_key,is_staff,state,false,carrying,&"",true)
|
||||
PixelArt.elevator_gate(self,cabin,p,doors_open)
|
||||
var capacity := int(shaft.get(&"capacity", 0))
|
||||
var load_text := "%d/%d人" % [passengers.size(), capacity]
|
||||
if capacity > 0 and passengers.size() >= capacity:
|
||||
load_text = "満員 " + load_text
|
||||
if not operational:
|
||||
load_text = "停止 " + load_text
|
||||
_draw_readable_badge(Vector2(cabin.position.x, cabin.position.y-14), load_text)
|
||||
for badge: Dictionary in _landing_badges:
|
||||
_draw_readable_badge(badge[&"at"], badge[&"text"])
|
||||
passengers_drawn.append(_actor_drawing(center,actor_key,is_staff,state,false,carrying,&"",true,observation))
|
||||
cabins.append({&"shaft_id": shaft_id, &"rect": cabin, &"pixel": p,
|
||||
&"operational": operational, &"doors_open": doors_open,
|
||||
&"phase": phase, &"passenger_count": passengers.size(),
|
||||
&"passengers": passengers_drawn,
|
||||
&"badge_at": Vector2(cabin.position.x, cabin.position.y-14),
|
||||
&"badge_rect": _readable_badge_rect(Vector2(cabin.position.x, cabin.position.y-14), RoomPresentation.elevator_label(shaft)),
|
||||
&"badge": RoomPresentation.elevator_label(shaft)})
|
||||
|
||||
for shaft_id: Variant in _elevator_tracks.keys():
|
||||
if not alive.has(shaft_id):
|
||||
_elevator_tracks.erase(shaft_id)
|
||||
|
||||
return cabins
|
||||
|
||||
|
||||
func _elevator_actor_key(actor_id: String) -> String:
|
||||
if actor_id.begins_with("guest:"):
|
||||
@@ -1115,7 +1335,8 @@ func _elevator_actor_key(actor_id: String) -> String:
|
||||
return actor_id
|
||||
|
||||
|
||||
func _tracked_elevator_floor(shaft_id: StringName, current_floor: float) -> float:
|
||||
func _tracked_elevator_floor(shaft_id: StringName, current_floor: float, observation: Dictionary = {}) -> float:
|
||||
var _elevator_tracks: Dictionary = observation.get(&"elevator_tracks", self._elevator_tracks)
|
||||
if not _elevator_tracks.has(shaft_id):
|
||||
_elevator_tracks[shaft_id] = {&"floor":current_floor,&"from":current_floor,&"at":_animation_time}
|
||||
var track: Dictionary = _elevator_tracks[shaft_id]
|
||||
@@ -1130,7 +1351,20 @@ func _tracked_elevator_floor(shaft_id: StringName, current_floor: float) -> floa
|
||||
return lerpf(float(track[&"from"]),current_floor,t)
|
||||
|
||||
|
||||
func _draw_actors() -> void:
|
||||
func _draw_actors(canvas: CanvasItem = null) -> void:
|
||||
for row: Dictionary in _life_observation.get(&"actor_drawings", []):
|
||||
_draw_observed_actor(row, canvas)
|
||||
|
||||
|
||||
func _observe_actors(observation: Dictionary) -> Array[Dictionary]:
|
||||
var rows: Array[Dictionary] = []
|
||||
var _actor_tracks: Dictionary = observation[&"actor_tracks"]
|
||||
var _boarded_actor_ids: Dictionary = observation[&"boarded"]
|
||||
var _queue_hidden: Dictionary = observation[&"queue_hidden"]
|
||||
var _queue_positions: Dictionary = observation[&"queue_positions"]
|
||||
var _carried_patients: Dictionary = observation[&"carried_patients"]
|
||||
var _treated_patients: Dictionary = observation[&"treated_patients"]
|
||||
var _resting_staff: Dictionary = observation[&"resting_staff"]
|
||||
var counts: Dictionary = {}
|
||||
var alive: Dictionary = {}
|
||||
var service_slots: Dictionary = {}
|
||||
@@ -1144,18 +1378,18 @@ func _draw_actors() -> void:
|
||||
if _boarded_actor_ids.has(key) or _queue_hidden.has(key):
|
||||
_actor_tracks.erase(key)
|
||||
continue
|
||||
var position := _tracked_actor_position(key,guest.current_cell)
|
||||
var position := _tracked_actor_position(key,guest.current_cell,observation)
|
||||
if guest.current_cell.z==layer_mode:
|
||||
var receiving := not String(guest.current_service_id).is_empty()
|
||||
if _queue_positions.has(key):
|
||||
position = _queue_positions[key]
|
||||
elif receiving:
|
||||
position = _service_use_position(guest,position,service_slots)
|
||||
position = _service_use_position(guest,position,service_slots,observation)
|
||||
if not position.is_finite():
|
||||
continue
|
||||
else:
|
||||
position += _actor_offset(guest.current_cell,counts)
|
||||
_draw_pixel_actor(position,key,false,int(guest.state),receiving,false,_guest_activity(guest))
|
||||
rows.append(_actor_drawing(position,key,false,int(guest.state),receiving,false,_guest_activity(guest),false,observation))
|
||||
var staff_service: Variant = _service(&"staff_service")
|
||||
if staff_service != null and staff_service.has_method(&"get_staff_members"):
|
||||
for staff: Variant in staff_service.call(&"get_staff_members"):
|
||||
@@ -1166,27 +1400,32 @@ func _draw_actors() -> void:
|
||||
if _boarded_actor_ids.has(key) or _queue_hidden.has(key):
|
||||
_actor_tracks.erase(key)
|
||||
continue
|
||||
var position := _tracked_actor_position(key,staff.current_cell)
|
||||
var position := _tracked_actor_position(key,staff.current_cell,observation)
|
||||
if staff.current_cell.z==layer_mode:
|
||||
if _queue_positions.has(key):
|
||||
rows.append(_actor_drawing(_queue_positions[key],key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation))
|
||||
continue
|
||||
if _carried_patients.has(staff.staff_id):
|
||||
var carrier: Variant = staff_service.get_staff(_carried_patients[staff.staff_id])
|
||||
if carrier != null:
|
||||
position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell) - Vector2(0, 12*_cell_dimensions().x/16.0)
|
||||
_draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
||||
position = _tracked_actor_position("staff/"+String(carrier.staff_id), carrier.current_cell,observation) - Vector2(0, 12*_cell_dimensions().x/16.0)
|
||||
rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||
continue
|
||||
if _resting_staff.has(staff.staff_id):
|
||||
position = _resting_staff_position(staff.staff_id, position)
|
||||
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
||||
position = _resting_staff_position(staff.staff_id, position,observation)
|
||||
if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||
continue
|
||||
if _treated_patients.has(staff.staff_id):
|
||||
position = _rescue_treatment_position(staff.staff_id, position)
|
||||
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
|
||||
position = _rescue_treatment_position(staff.staff_id, position,observation)
|
||||
if position.is_finite(): rows.append(_actor_drawing(position,key,true,int(staff.state),false,false,&"",false,observation))
|
||||
continue
|
||||
_draw_pixel_actor(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts))),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff))
|
||||
rows.append(_actor_drawing(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts),observation)),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff),false,observation))
|
||||
for key: Variant in _actor_tracks.keys():
|
||||
if not alive.has(key):
|
||||
_actor_tracks.erase(key)
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
func _staff_work_animation(staff: StaffRuntime) -> StringName:
|
||||
if staff.role_id != &"cleaner":
|
||||
@@ -1201,7 +1440,8 @@ func _staff_work_animation(staff: StaffRuntime) -> StringName:
|
||||
return &"idle"
|
||||
|
||||
|
||||
func _staff_work_position(staff: Variant, fallback: Vector2) -> Vector2:
|
||||
func _staff_work_position(staff: Variant, fallback: Vector2, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
if staff.state not in [StaffRuntime.State.WORKING, StaffRuntime.State.AVAILABLE]:
|
||||
return fallback
|
||||
var service: Variant = _service(&"facility_service")
|
||||
@@ -1227,7 +1467,8 @@ func _staff_work_position(staff: Variant, fallback: Vector2) -> Vector2:
|
||||
return Vector2(rect.position.x+(slot+0.5)*rect.size.x/columns, rect.position.y+26*p-_cell_dimensions().y*0.5)
|
||||
|
||||
|
||||
func _tracked_actor_position(key: String, cell: Vector3i) -> Vector2:
|
||||
func _tracked_actor_position(key: String, cell: Vector3i, observation: Dictionary = {}) -> Vector2:
|
||||
var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks)
|
||||
if not _actor_tracks.has(key):
|
||||
_actor_tracks[key] = {&"cell":cell,&"from":cell,&"at":_animation_time,&"direction":1.0}
|
||||
var track: Dictionary = _actor_tracks[key]
|
||||
@@ -1246,7 +1487,8 @@ func _tracked_actor_position(key: String, cell: Vector3i) -> Vector2:
|
||||
return cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(cell),t)
|
||||
|
||||
|
||||
func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary) -> Vector2:
|
||||
func _service_use_position(guest: Variant, fallback: Vector2, _slots: Dictionary, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
# Guest paths stop at an access cell. While a service is active, project the
|
||||
# occupant into that facility's use position; authoritative paths stay intact.
|
||||
var id := StringName(guest.target_facility_id)
|
||||
@@ -1281,6 +1523,21 @@ func _guest_activity(guest: Variant) -> StringName:
|
||||
|
||||
|
||||
func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false) -> void:
|
||||
_draw_observed_actor(_actor_drawing(center, key, staff, state, receiving, carrying, activity, riding))
|
||||
|
||||
|
||||
func _draw_observed_actor(row: Dictionary, canvas: CanvasItem = null) -> void:
|
||||
if canvas == null:
|
||||
canvas = self
|
||||
var at: Vector2 = row[&"at"]
|
||||
var p: float = row[&"scale"]
|
||||
canvas.draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2))
|
||||
People.draw(canvas, at, p, row[&"mode"], row[&"frame"], row[&"variant"], row[&"staff"], row[&"flip"])
|
||||
|
||||
|
||||
func _actor_drawing(center: Vector2, key: String, staff: bool, state: int, receiving: bool, carrying: bool, activity: StringName = &"", riding := false, observation: Dictionary = {}) -> Dictionary:
|
||||
var _actor_tracks: Dictionary = observation.get(&"actor_tracks", self._actor_tracks)
|
||||
var _queue_positions: Dictionary = observation.get(&"queue_positions", self._queue_positions)
|
||||
var world_pixel := _cell_dimensions().x / 16.0
|
||||
var scale := 0.5 if riding else 0.75
|
||||
var p := maxf(floorf(world_pixel * scale), 1.0) if world_pixel >= 2.0 else world_pixel * scale
|
||||
@@ -1307,8 +1564,274 @@ func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, re
|
||||
elif _queue_positions.has(key) or (not staff and state == GuestRuntime.State.QUEUING):
|
||||
mode = &"wait"
|
||||
var at := (center + Vector2(-8 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 17 * p)).round()
|
||||
draw_rect(Rect2(at + Vector2(5, 17) * p, Vector2(8, 1) * p), Color("303c40", 0.2))
|
||||
People.draw(self, at, p, mode, frame, variant, staff, float(track.get(&"direction", 1.0)) < 0.0)
|
||||
return {&"key": key, &"center": center, &"at": at, &"scale": p, &"mode": mode,
|
||||
&"frame": frame, &"variant": variant, &"staff": staff, &"state": state,
|
||||
&"receiving": receiving, &"carrying": carrying, &"activity": activity,
|
||||
&"riding": riding, &"flip": float(track.get(&"direction", 1.0)) < 0.0}
|
||||
|
||||
|
||||
## Identity cores are tied to the unchanged PixelPeople poses, not sprite boxes.
|
||||
func _life_core(row: Dictionary) -> Rect2:
|
||||
var phase := int(row[&"frame"]) % 8
|
||||
var core := Rect2(6, 1, 6, 12)
|
||||
if row[&"mode"] in [&"sleep", &"collapsed", &"standard_room_sleep"]:
|
||||
core = Rect2(2, 11, 13, 5)
|
||||
if row[&"mode"] == &"standard_room_sleep" and phase in [2, 3, 4]:
|
||||
core.position.y -= 1
|
||||
core.size.y += 1
|
||||
elif row[&"mode"] in [&"walk", &"carry_walk"] and phase in [2, 6]:
|
||||
core.position.y -= 1
|
||||
if row[&"flip"]:
|
||||
core.position.x = 16-core.end.x
|
||||
return Rect2(row[&"at"]+core.position*row[&"scale"], core.size*row[&"scale"])
|
||||
|
||||
|
||||
func _life_track_region(key: String, cell: Vector3i, model: Dictionary) -> Dictionary:
|
||||
var track: Dictionary = model[&"actor_tracks"].get(key, {})
|
||||
var transit: bool = not model[&"paused"] and track.get(&"from", cell) != cell and _animation_time-float(track.get(&"at", 0.0)) < 0.42
|
||||
var rect := get_cell_rect(cell)
|
||||
if transit:
|
||||
rect = rect.merge(get_cell_rect(track[&"from"]))
|
||||
return {&"bounds": rect, &"transit": transit}
|
||||
|
||||
|
||||
## Keep non-passengers on a real corridor landing when a cabin crosses their pose.
|
||||
func _life_landing_region(region: Dictionary, cell: Vector3i, model: Dictionary) -> Dictionary:
|
||||
var blocked := false
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
if (region[&"bounds"] as Rect2).intersects(cabin[&"rect"]):
|
||||
blocked = true
|
||||
break
|
||||
if not blocked:
|
||||
return region
|
||||
var world: Variant = _service(&"world_service")
|
||||
var facilities: Variant = _service(&"facility_service")
|
||||
var footprint: Array = model[&"shaft_footprint"]
|
||||
var origins: Array = footprint.filter(func(c: Vector3i) -> bool: return c.y == cell.y and c.z == cell.z)
|
||||
origins.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x))
|
||||
var lane: Array[Vector3i] = []
|
||||
for origin: Vector3i in origins:
|
||||
for direction: int in [-1, 1]:
|
||||
for distance: int in range(1, 9):
|
||||
var target := origin + Vector3i(direction*distance, 0, 0)
|
||||
if world == null or footprint.has(target) or not world.is_walkable(target):
|
||||
break
|
||||
var facility: Variant = facilities.get_instance(world.get_occupant(target)) if facilities != null else null
|
||||
if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]:
|
||||
break
|
||||
if not lane.has(target):
|
||||
lane.append(target)
|
||||
lane.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x))
|
||||
if lane.is_empty():
|
||||
var anchor := get_cell_rect(cell).position+Vector2(-80, -16)
|
||||
var nearest := INF
|
||||
for badge: Dictionary in model[&"landing_badges"]:
|
||||
var landing: Vector3i = badge[&"cell"]
|
||||
if landing.y == cell.y and landing.z == cell.z and absf(landing.x-cell.x) < nearest:
|
||||
nearest = absf(landing.x-cell.x)
|
||||
anchor = badge[&"at"]
|
||||
return {&"bounds": region[&"bounds"], &"transit": false, &"no_lane": true, &"badge_at": anchor}
|
||||
var selected: Vector3i = lane[0]
|
||||
for target: Vector3i in lane:
|
||||
if not model[&"queue_positions"].values().has(cell_to_canvas(target)):
|
||||
selected = target
|
||||
break
|
||||
return {&"bounds": get_cell_rect(selected), &"transit": false, &"center": cell_to_canvas(selected)}
|
||||
|
||||
|
||||
func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary) -> Dictionary:
|
||||
var key: String = row[&"key"]
|
||||
var cell: Vector3i = person[&"cell"]
|
||||
var result := _life_track_region(key, cell, model)
|
||||
var p := _cell_dimensions().x/16.0
|
||||
var world: Variant = _service(&"world_service")
|
||||
var facility_id: StringName = &""
|
||||
if model[&"queue_positions"].has(key):
|
||||
result = {&"bounds": Rect2(model[&"queue_positions"][key]-_cell_dimensions()*0.5, _cell_dimensions()), &"transit": false}
|
||||
elif not row[&"staff"]:
|
||||
if not String(person[&"service"]).is_empty():
|
||||
var room: Dictionary = model[&"rooms"].get(person[&"target"], {})
|
||||
if (room.get(&"people", []) as Array).has(person[&"id"]):
|
||||
facility_id = person[&"target"]
|
||||
else:
|
||||
var id: StringName = person[&"id"]
|
||||
if model[&"carried_patients"].has(id):
|
||||
for carrier: Dictionary in model[&"staff"]:
|
||||
if carrier[&"id"] == model[&"carried_patients"][id]:
|
||||
result = _life_actor_region({&"key": "staff/"+String(carrier[&"id"]), &"staff": true}, carrier, model)
|
||||
var rect: Rect2 = result[&"bounds"]
|
||||
rect = rect.grow(p)
|
||||
rect.position.y -= 12*p
|
||||
result[&"bounds"] = rect
|
||||
if result.has(&"center"):
|
||||
result[&"center"] -= Vector2(0, 12*p)
|
||||
elif model[&"resting_staff"].has(id):
|
||||
facility_id = model[&"resting_staff"][id]
|
||||
elif model[&"treated_patients"].has(id):
|
||||
facility_id = model[&"treated_patients"][id]
|
||||
elif person[&"state"] in ["WORKING", "AVAILABLE"] and world != null:
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(person[&"facility"])
|
||||
var room: Dictionary = model[&"rooms"].get(person[&"facility"], {})
|
||||
var slot := (room.get(&"staff", []) as Array).find(id)
|
||||
var rect := _cell_bounds_rect(cells)
|
||||
var columns := maxi(int(rect.size.x/(16*p)), 1)
|
||||
if cells.has(cell) and slot >= 0 and slot < columns and rect.size.y >= 40*p:
|
||||
result = {&"bounds": Rect2(rect.position+Vector2(slot*rect.size.x/columns, 0), Vector2(rect.size.x/columns, rect.size.y)), &"transit": false}
|
||||
if not facility_id.is_empty() and world != null:
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id)
|
||||
if not cells.is_empty():
|
||||
result = {&"bounds": _cell_bounds_rect(cells), &"transit": false}
|
||||
if not model[&"queue_positions"].has(key) and not result.has(&"center") and not result.get(&"no_lane", false):
|
||||
result = _life_landing_region(result, cell, model)
|
||||
result[&"bounds"] = (result[&"bounds"] as Rect2).grow(-p)
|
||||
return result
|
||||
|
||||
|
||||
func _life_scaled(row: Dictionary, scale: float) -> Dictionary:
|
||||
var result := row.duplicate(true)
|
||||
result[&"scale"] = scale
|
||||
result[&"at"] = (row[&"center"]+Vector2(-8*scale, _cell_dimensions().y*0.5-3*_cell_dimensions().x/16.0-17*scale)).round()
|
||||
return result
|
||||
|
||||
|
||||
## Forward/backward packing keeps each member's own interval. No relocation.
|
||||
func _life_pack(group: Array, scale: float) -> Array:
|
||||
var trial: Array = []
|
||||
var right := -INF
|
||||
for member: Dictionary in group:
|
||||
var row := _life_scaled(member[&"row"], minf(scale, member[&"row"][&"scale"]))
|
||||
var core := _life_core(row)
|
||||
var bounds: Rect2 = member[&"bounds"]
|
||||
var x := maxf(maxf(core.position.x, bounds.position.x), right)
|
||||
row[&"at"].x += x-core.position.x
|
||||
right = x+core.size.x
|
||||
trial.append(row)
|
||||
var left := INF
|
||||
for i: int in range(trial.size()-1, -1, -1):
|
||||
var row: Dictionary = trial[i]
|
||||
var core := _life_core(row)
|
||||
var bounds: Rect2 = group[i][&"bounds"]
|
||||
var x := minf(core.position.x, minf(bounds.end.x, left)-core.size.x)
|
||||
row[&"at"].x += x-core.position.x
|
||||
left = x
|
||||
if x < bounds.position.x or core.position.y < bounds.position.y or core.end.y > bounds.end.y:
|
||||
return []
|
||||
return trial
|
||||
|
||||
|
||||
## Rebuild vertical components at every fitted scale. Ceilings only decrease;
|
||||
## once none decreases, the feet/cores and therefore the partition are fixed.
|
||||
func _life_fitted_groups(members: Array) -> Array:
|
||||
var groups: Array = []
|
||||
while true:
|
||||
for member: Dictionary in members:
|
||||
member[&"row"].merge(_life_scaled(member[&"row"], member[&"row"][&"scale"]), true)
|
||||
members.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
var ay := _life_core(a[&"row"]).position.y
|
||||
var by := _life_core(b[&"row"]).position.y
|
||||
return ay < by or (ay == by and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
||||
groups = []
|
||||
var bottom := -INF
|
||||
for member: Dictionary in members:
|
||||
var core := _life_core(member[&"row"])
|
||||
if groups.is_empty() or core.position.y >= bottom:
|
||||
groups.append([])
|
||||
bottom = core.end.y
|
||||
else:
|
||||
bottom = maxf(bottom, core.end.y)
|
||||
groups.back().append(member)
|
||||
var reduced := false
|
||||
for group: Array in groups:
|
||||
group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
||||
var ceiling := 1.0
|
||||
for member: Dictionary in group:
|
||||
ceiling = maxf(ceiling, member[&"row"][&"scale"])
|
||||
if _life_pack(group, ceiling).is_empty() and ceiling > 1:
|
||||
for member: Dictionary in group:
|
||||
var scale := minf(member[&"row"][&"scale"], maxf(1, int(ceiling)-1))
|
||||
member[&"row"].merge(_life_scaled(member[&"row"], scale), true)
|
||||
reduced = true
|
||||
if not reduced:
|
||||
return groups
|
||||
return groups
|
||||
|
||||
|
||||
func _fit_life_actors(model: Dictionary) -> void:
|
||||
var people: Dictionary = {}
|
||||
for person: Dictionary in model[&"guests"]:
|
||||
people["guest/"+String(person[&"id"])] = person
|
||||
for person: Dictionary in model[&"staff"]:
|
||||
people["staff/"+String(person[&"id"])] = person
|
||||
var members: Array = []
|
||||
var stranded: Array = []
|
||||
var all_rows: Array = model[&"actor_drawings"].duplicate()
|
||||
var passenger_bounds: Dictionary = {}
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
for row: Dictionary in cabin[&"passengers"]:
|
||||
all_rows.append(row)
|
||||
passenger_bounds[row[&"key"]] = (cabin[&"rect"] as Rect2).grow(-_cell_dimensions().x/16.0)
|
||||
for row: Dictionary in all_rows:
|
||||
var person: Dictionary = people[row[&"key"]]
|
||||
row[&"cell"] = person[&"cell"]
|
||||
var region := _life_actor_region(row, person, model) if not row[&"riding"] else {&"bounds": passenger_bounds[row[&"key"]], &"transit": false}
|
||||
if region.has(&"center"):
|
||||
var jitter := Vector2.ZERO
|
||||
if not model[&"carried_patients"].has(person[&"id"]):
|
||||
var track: Dictionary = model[&"actor_tracks"][row[&"key"]]
|
||||
var t := 1.0 if model[&"paused"] else clampf((_animation_time-float(track[&"at"]))/0.42, 0, 1)
|
||||
jitter = row[&"center"]-cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(person[&"cell"]), t)
|
||||
row[&"center"] = region[&"center"]+jitter
|
||||
row.merge(_life_scaled(row, row[&"scale"]), true)
|
||||
region[&"row"] = row
|
||||
if region.get(&"no_lane", false):
|
||||
stranded.append(region)
|
||||
elif not region[&"transit"]:
|
||||
members.append(region)
|
||||
var groups := _life_fitted_groups(members)
|
||||
model[&"actor_aggregations"] = []
|
||||
var hidden: Dictionary = {}
|
||||
for member: Dictionary in stranded:
|
||||
var row: Dictionary = member[&"row"]
|
||||
hidden[row[&"key"]] = true
|
||||
var aggregate: Dictionary = {}
|
||||
for existing: Dictionary in model[&"actor_aggregations"]:
|
||||
if existing[&"at"] == member[&"badge_at"]:
|
||||
aggregate = existing
|
||||
break
|
||||
if aggregate.is_empty():
|
||||
aggregate = {&"members": [], &"drawn_keys": [], &"at": member[&"badge_at"], &"text": ""}
|
||||
model[&"actor_aggregations"].append(aggregate)
|
||||
aggregate[&"members"].append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]})
|
||||
aggregate[&"text"] = "+%d" % aggregate[&"members"].size()
|
||||
for group: Array in groups:
|
||||
group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
||||
var base := 1.0
|
||||
for member: Dictionary in group:
|
||||
base = maxf(base, member[&"row"][&"scale"])
|
||||
var fitted := _life_pack(group, base)
|
||||
var retained: Array = group
|
||||
if fitted.is_empty():
|
||||
retained = []
|
||||
var identities: Array = []
|
||||
var drawn: Array = []
|
||||
for member: Dictionary in group:
|
||||
var row: Dictionary = member[&"row"]
|
||||
identities.append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]})
|
||||
var attempt := retained.duplicate()
|
||||
attempt.append(member)
|
||||
var packed := _life_pack(attempt, 1)
|
||||
if not packed.is_empty():
|
||||
retained = attempt
|
||||
fitted = packed
|
||||
drawn.append(row[&"key"])
|
||||
else:
|
||||
hidden[row[&"key"]] = true
|
||||
model[&"actor_aggregations"].append({&"members": identities, &"drawn_keys": drawn, &"at": (group[0][&"bounds"] as Rect2).position, &"text": "+%d" % (group.size()-retained.size())})
|
||||
for i: int in fitted.size():
|
||||
retained[i][&"row"].merge(fitted[i], true)
|
||||
model[&"actor_drawings"] = model[&"actor_drawings"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"]))
|
||||
for cabin: Dictionary in model[&"cabins"]:
|
||||
cabin[&"passengers"] = cabin[&"passengers"].filter(func(row: Dictionary) -> bool: return not hidden.has(row[&"key"]))
|
||||
|
||||
|
||||
func _draw_excavation_jobs() -> void:
|
||||
@@ -1421,14 +1944,14 @@ func _facility_color(instance_id: StringName) -> Color:
|
||||
var instance: Variant = facility_service.call(&"get_instance", instance_id)
|
||||
if instance == null:
|
||||
return Color("6f8b67") if layer_mode == LayerMode.FRONT else Color("427582")
|
||||
match int(instance.state):
|
||||
0:
|
||||
match int(_room_presentations.get(instance_id, {}).get(&"state", instance.state)):
|
||||
FacilityInstance.State.BROKEN:
|
||||
return Color("a24b45")
|
||||
1, 2, 3:
|
||||
FacilityInstance.State.NO_ACCESS, FacilityInstance.State.NO_POWER, FacilityInstance.State.NO_STAFF:
|
||||
return Color("a77d3f")
|
||||
4:
|
||||
FacilityInstance.State.CLOSED:
|
||||
return Color("6b6258")
|
||||
5:
|
||||
FacilityInstance.State.DEGRADED:
|
||||
return Color("89914d")
|
||||
_:
|
||||
return Color("6f9a55") if layer_mode == LayerMode.FRONT else Color("67518e")
|
||||
@@ -1579,7 +2102,9 @@ func _normalized_rotation(value: int) -> int:
|
||||
return posmod(value, 360) / 90 * 90
|
||||
|
||||
|
||||
func _rescue_treatment_position(patient_id: StringName, fallback: Vector2) -> Vector2:
|
||||
func _rescue_treatment_position(patient_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
var _treated_patients: Dictionary = observation.get(&"treated_patients", self._treated_patients)
|
||||
var facility_id: StringName = _treated_patients[patient_id]
|
||||
var world: Variant = _service(&"world_service")
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else []
|
||||
@@ -1592,7 +2117,9 @@ func _rescue_treatment_position(patient_id: StringName, fallback: Vector2) -> Ve
|
||||
return anchors[slot]+Vector2(0,-2*p-_cell_dimensions().y*0.5)
|
||||
|
||||
|
||||
func _resting_staff_position(staff_id: StringName, fallback: Vector2) -> Vector2:
|
||||
func _resting_staff_position(staff_id: StringName, fallback: Vector2, observation: Dictionary = {}) -> Vector2:
|
||||
var _room_presentations: Dictionary = observation.get(&"rooms", self._room_presentations)
|
||||
var _resting_staff: Dictionary = observation.get(&"resting_staff", self._resting_staff)
|
||||
var facility_id: StringName = _resting_staff[staff_id]
|
||||
var world: Variant = _service(&"world_service")
|
||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id) if world != null else []
|
||||
|
||||
@@ -10,7 +10,9 @@ static func describe(instance: Variant) -> Dictionary:
|
||||
if not ids.has(id):
|
||||
ids.append(id)
|
||||
ids.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b))
|
||||
return {&"operation": int(instance.operation_state), &"people": ids,
|
||||
return {&"state": int(instance.state), &"state_name": FacilityInstance.State.keys()[instance.state],
|
||||
&"reasons": instance.state_reasons.duplicate(),
|
||||
&"operation": int(instance.operation_state), &"people": ids,
|
||||
&"count": ids.size(), &"limit": int(instance.customer_capacity),
|
||||
&"staff": instance.active_staff_ids.duplicate(), &"suspension": StringName(instance.suspension_reason)}
|
||||
|
||||
@@ -50,3 +52,52 @@ static func label(data: Dictionary, visible_count: int) -> String:
|
||||
if count > visible_count:
|
||||
value += "(他%d人)" % (count - visible_count)
|
||||
return value
|
||||
|
||||
|
||||
static func business_label(data: Dictionary) -> String:
|
||||
if data.is_empty():
|
||||
return ""
|
||||
if int(data[&"operation"]) == FacilityInstance.OperationState.SUSPENDED:
|
||||
return "休止・摘発" if data[&"suspension"] == &"RAIDED" else "休止・破壊"
|
||||
match int(data[&"state"]):
|
||||
FacilityInstance.State.BROKEN: return "破損"
|
||||
FacilityInstance.State.NO_ACCESS: return "通路なし"
|
||||
FacilityInstance.State.NO_POWER: return "魔力なし"
|
||||
FacilityInstance.State.NO_STAFF: return "職員不足"
|
||||
FacilityInstance.State.CLOSED: return "営業停止"
|
||||
FacilityInstance.State.DEGRADED: return "低下運転"
|
||||
FacilityInstance.State.OPEN: return "営業中"
|
||||
return String(data[&"state_name"])
|
||||
|
||||
|
||||
static func queue_label(count: int, overflow: int) -> String:
|
||||
return "待ち%d人" % count + ("(他%d人)" % overflow if overflow > 0 else "")
|
||||
|
||||
|
||||
static func elevator_label(shaft: Dictionary) -> String:
|
||||
var count := (shaft[&"boarded"] as Array).size()
|
||||
var capacity := int(shaft[&"capacity"])
|
||||
var value := "%d/%d人" % [count, capacity]
|
||||
if capacity > 0 and count >= capacity:
|
||||
value = "満員 " + value
|
||||
if not bool(shaft[&"operational"]):
|
||||
value = "停止 " + value
|
||||
else:
|
||||
value = "運転 " + value
|
||||
var phase := String(shaft.get(&"phase", &"IDLE"))
|
||||
var doors_open := bool(shaft[&"operational"]) and phase in ["IDLE", "BOARDING", "ALIGHTING"]
|
||||
return value + (" 開 " if doors_open else " 閉 ") + phase
|
||||
|
||||
|
||||
static func supply_label(requests: Array[Dictionary]) -> String:
|
||||
# These are requests, never invented stock quantities. Preserve actual states.
|
||||
var states: Dictionary = {}
|
||||
for request: Dictionary in requests:
|
||||
var state := String(request[&"state"])
|
||||
states[state] = int(states.get(state, 0)) + 1
|
||||
var names: Array = states.keys()
|
||||
names.sort()
|
||||
var parts: PackedStringArray = []
|
||||
for state: String in names:
|
||||
parts.append("%s:%d" % [state, states[state]])
|
||||
return "物資依頼 " + " / ".join(parts)
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
extends RefCounted
|
||||
## Sparse whole typed captures. No acceptance flags and no truncation.
|
||||
const Codec := preload("res://tests/fixtures/p08-02/mixed_world.gd")
|
||||
var directory: String
|
||||
var manifest: FileAccess
|
||||
var observer: MotionObserver
|
||||
|
||||
class MotionObserver extends Node:
|
||||
var view: WorldPreview
|
||||
var frames: Array = []
|
||||
var elapsed := 0.0
|
||||
var scaled_elapsed := 0.0
|
||||
func _ready() -> void:
|
||||
RenderingServer.frame_post_draw.connect(after_draw)
|
||||
view.draw.connect(on_view_draw)
|
||||
func _process(delta: float) -> void:
|
||||
elapsed += delta
|
||||
var clock := get_node_or_null("/root/GameClock")
|
||||
if clock == null or not bool(clock.get("paused")):
|
||||
scaled_elapsed += delta * maxf(float(clock.get("speed")) if clock != null else 1.0, 0.0)
|
||||
func on_view_draw() -> void:
|
||||
# Read the draw-consumed report after the real _draw completes.
|
||||
after_draw.call_deferred()
|
||||
func display_sample(report: Dictionary) -> Dictionary:
|
||||
var sample := {}
|
||||
for key: String in ["animation_time", "paused", "layer", "zoom", "pan", "viewport", "actor_tracks", "elevator_tracks", "actor_drawings", "cabins"]:
|
||||
sample[key] = report[key]
|
||||
return sample
|
||||
func after_draw() -> void:
|
||||
if not is_instance_valid(view): return
|
||||
var frame := Engine.get_process_frames()
|
||||
if not frames.is_empty() and frame <= int(frames[-1]["frame"]): return
|
||||
var report := view.get_life_observation_report()
|
||||
if report.is_empty(): return
|
||||
var sample := display_sample(report)
|
||||
if not frames.is_empty() and sample == frames[-1]["display"] and not sample["paused"]: return
|
||||
# Keep only two draw frames, never whole Runtime copies per frame.
|
||||
frames.append({"frame": frame, "delta": elapsed, "scaled_delta": scaled_elapsed, "display": sample})
|
||||
if frames.size() > 2: frames.pop_front()
|
||||
elapsed = 0.0
|
||||
scaled_elapsed = 0.0
|
||||
func _exit_tree() -> void:
|
||||
if is_instance_valid(view) and view.draw.is_connected(on_view_draw): view.draw.disconnect(on_view_draw)
|
||||
if RenderingServer.frame_post_draw.is_connected(after_draw): RenderingServer.frame_post_draw.disconnect(after_draw)
|
||||
|
||||
func watch(view: WorldPreview) -> void:
|
||||
observer = MotionObserver.new()
|
||||
observer.view = view
|
||||
view.add_child(observer)
|
||||
|
||||
func await_draws(tree: SceneTree, view: WorldPreview, n: int = 2, bound: int = 120) -> bool:
|
||||
var start := Engine.get_process_frames()
|
||||
for unused: int in range(bound):
|
||||
view.queue_redraw()
|
||||
await tree.process_frame
|
||||
if not is_instance_valid(observer): break
|
||||
var report := view.get_life_observation_report()
|
||||
if observer.frames.size() == n and not report.is_empty() and int(observer.frames[0]["frame"]) > start and observer.frames[-1]["display"] == observer.display_sample(report):
|
||||
return true
|
||||
# Persist timeout as a failure, never a skip.
|
||||
record("draw-history-timeout", {"start_frame": start, "bound": bound, "required_frames": n})
|
||||
return false
|
||||
|
||||
func open(path: String) -> bool:
|
||||
directory = path
|
||||
if DirAccess.dir_exists_absolute(path):
|
||||
return false
|
||||
if DirAccess.make_dir_recursive_absolute(path.path_join("objects")) != OK:
|
||||
return false
|
||||
manifest = FileAccess.open(path.path_join("manifest.jsonl"), FileAccess.WRITE)
|
||||
return manifest != null
|
||||
|
||||
func object(value: Variant) -> String:
|
||||
var text := JSON.stringify(Codec.new().typed(value)) + "\n"
|
||||
var digest := text.sha256_text()
|
||||
var path := directory.path_join("objects").path_join(digest + ".json")
|
||||
if FileAccess.file_exists(path):
|
||||
return digest if FileAccess.get_sha256(path) == digest else ""
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return ""
|
||||
file.store_string(text)
|
||||
file.close()
|
||||
return digest if FileAccess.get_sha256(path) == digest else ""
|
||||
|
||||
func capture(label: String, runtime: Variant, view: WorldPreview) -> bool:
|
||||
if manifest == null:
|
||||
return false
|
||||
var state: Dictionary = runtime.create_snapshot()
|
||||
var report := view.get_life_observation_report()
|
||||
var state_hash := object(state)
|
||||
var report_hash := object(report)
|
||||
var audit_hash := object(rest_audit(runtime))
|
||||
var motion_hash := object({"frames": observer.frames.duplicate(true) if is_instance_valid(observer) else []})
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
var after_hash := object(after)
|
||||
if state_hash.is_empty() or report_hash.is_empty() or audit_hash.is_empty():
|
||||
return false
|
||||
manifest.store_line(JSON.stringify({"label": label, "pid": OS.get_process_id(),
|
||||
"ticks_usec": Time.get_ticks_usec(), "state": state_hash, "report": report_hash,
|
||||
"rest_audit": audit_hash, "motion": motion_hash, "state_after": after_hash,
|
||||
"input_method": "diagnostic", "acceptance": "pending"}))
|
||||
manifest.flush()
|
||||
return var_to_bytes(state) == var_to_bytes(after)
|
||||
|
||||
func close() -> void:
|
||||
if manifest != null:
|
||||
manifest.close()
|
||||
manifest = null
|
||||
|
||||
# Public selection inputs; Runtime output is only a comparison operand.
|
||||
func rest_audit(runtime: Variant) -> Dictionary:
|
||||
var rows: Array = []
|
||||
for member: StaffRuntime in runtime.staff_service.get_staff_members():
|
||||
var memberships: Array = []
|
||||
for raid_id: StringName in runtime.defense_service.get_raid_ids():
|
||||
var raid: Dictionary = runtime.defense_service.get_raid(raid_id)
|
||||
if raid.get(&"combatants", {}).has(member.staff_id):
|
||||
memberships.append({"raid_id": raid_id, "state": DefenseService.RaidState.keys()[raid[&"state"]],
|
||||
"side": raid[&"combatants"][member.staff_id].get(&"side", &"")})
|
||||
var facilities: Array = []
|
||||
for room: FacilityInstance in runtime.facility_service.get_instances():
|
||||
var probes: Array = []
|
||||
var candidates: Dictionary = {}
|
||||
var occupied := room.occupied_cells.duplicate()
|
||||
if occupied.is_empty(): occupied.append(room.origin)
|
||||
for cell: Vector3i in occupied:
|
||||
for delta: Vector3i in [Vector3i.ZERO, Vector3i.LEFT, Vector3i.RIGHT]:
|
||||
var present: bool = runtime.path_service.has_cell(cell + delta)
|
||||
probes.append({"cell": cell + delta, "has_cell": present})
|
||||
if present: candidates[cell + delta] = true
|
||||
var ordered: Array = candidates.keys()
|
||||
ordered.sort_custom(func(a: Vector3i, b: Vector3i) -> bool:
|
||||
if a.z != b.z: return a.z < b.z
|
||||
if a.y != b.y: return a.y < b.y
|
||||
return a.x < b.x)
|
||||
var routes: Array = []
|
||||
for cell: Vector3i in ordered:
|
||||
var path: Array = runtime.path_service.find_path(member.current_cell, cell,
|
||||
[WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"])
|
||||
var edges: Array = []
|
||||
for index: int in range(1, path.size()):
|
||||
# Empty reports remain explicit mismatch evidence.
|
||||
edges.append(runtime.path_service.get_edge_report(path[index - 1], path[index], [&"staff"]))
|
||||
routes.append({"cell": cell, "path": path, "edges": edges,
|
||||
"calculate_path_cost": runtime.path_service.calculate_path_cost(path, [&"staff"]) if not path.is_empty() else -1})
|
||||
facilities.append({"instance_id": room.instance_id, "definition_id": room.definition_id,
|
||||
"operation_state": FacilityInstance.OperationState.keys()[room.operation_state],
|
||||
"origin": room.origin, "occupied_cells": room.occupied_cells.duplicate(),
|
||||
"probes": probes, "ordered": ordered, "routes": routes})
|
||||
rows.append({"staff_id": member.staff_id, "state": StaffRuntime.State.keys()[member.state],
|
||||
"health": member.health, "current_task_id": member.current_task_id, "current_cell": member.current_cell,
|
||||
"defense_memberships": memberships, "actor_lock": runtime.event_service.get_actor_locks().has(member.staff_id),
|
||||
"in_transit": runtime.is_staff_in_transit(member.staff_id), "facilities": facilities,
|
||||
"comparison_only": runtime.get_staff_rest_report(member.staff_id)})
|
||||
return {"staff": rows, "path_service_sha256": FileAccess.get_sha256("res://scripts/world/path_service.gd")}
|
||||
|
||||
func record(label: String, value: Dictionary) -> bool:
|
||||
if manifest == null: return false
|
||||
var digest := object(value)
|
||||
if digest.is_empty(): return false
|
||||
manifest.store_line(JSON.stringify({"label": label, "pid": OS.get_process_id(),
|
||||
"ticks_usec": Time.get_ticks_usec(), "object": digest}))
|
||||
manifest.flush()
|
||||
return true
|
||||
@@ -0,0 +1 @@
|
||||
uid://d23lifestr03
|
||||
@@ -0,0 +1,102 @@
|
||||
extends RefCounted
|
||||
## Public-command experiment; no synthetic state, costs or capacities.
|
||||
## Predeclared metric: total destination-arrival minutes of the entire cohort.
|
||||
## B must be strictly smaller. Incomplete cohorts cannot establish improvement.
|
||||
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
||||
const SEED := 806042
|
||||
const WINDOW := 480
|
||||
const START := Vector3i(2, 0, 0)
|
||||
const GOAL := Vector3i(2, 1, 0)
|
||||
var stream: RefCounted
|
||||
|
||||
func declaration() -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = SEED
|
||||
var arrivals: Array[Dictionary] = []
|
||||
for index: int in range(48):
|
||||
arrivals.append({&"id": StringName("p23-%03d" % index),
|
||||
&"offset": (index / 4) * 15 + rng.randi_range(0, 2)})
|
||||
arrivals.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
return a[&"offset"] < b[&"offset"] or (a[&"offset"] == b[&"offset"] and String(a[&"id"]) < String(b[&"id"])))
|
||||
return {&"seed": SEED, &"initial_clock": [1, 6, 0], &"budget": &"GameState.reset",
|
||||
&"production": &"Runtime.initialize defaults; production operations not enabled",
|
||||
&"arrivals": arrivals, &"from": START, &"to": GOAL, &"window": WINDOW,
|
||||
&"metric": &"sum_destination_arrival_minutes_entire_cohort",
|
||||
&"improvement": {&"definition_id": &"guest_elevator", &"origin": Vector3i(4, 0, 0)}}
|
||||
|
||||
func run(tree: SceneTree, directory: String, layout: String) -> bool:
|
||||
if layout not in ["A", "B"]: return false
|
||||
var global_clock := tree.root.get_node_or_null("GameClock")
|
||||
if global_clock != null: global_clock.set_paused(true)
|
||||
stream = Stream.new()
|
||||
if not stream.open(directory): return false
|
||||
var input := declaration()
|
||||
if not stream.record("declaration", input):
|
||||
stream.close()
|
||||
return false
|
||||
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
|
||||
var clock: Variant = load("res://autoload/game_clock.gd").new()
|
||||
var state: Variant = load("res://autoload/game_state.gd").new()
|
||||
clock.reset(1, 6, 0)
|
||||
state.reset()
|
||||
var ok: bool = runtime.initialize(clock, state, SEED)[&"valid"]
|
||||
clock.set_paused(true)
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
ok = stream.record("initial", runtime.create_snapshot()) and ok
|
||||
var builds: Array = [[&"guest_elevator", Vector3i.ZERO],
|
||||
[&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)]]
|
||||
if layout == "B": builds.append([input[&"improvement"][&"definition_id"], input[&"improvement"][&"origin"]])
|
||||
var construction_minutes := 0
|
||||
# Identical preparation time derived from the same registered definitions.
|
||||
for id: StringName in [&"guest_elevator", &"guest_corridor"]:
|
||||
construction_minutes = maxi(construction_minutes, runtime.definition_registry.get_facility(id).build_minutes)
|
||||
for index: int in range(builds.size()):
|
||||
var payload := {&"definition_id": builds[index][0], &"origin": builds[index][1]}
|
||||
var result: CommandResult = runtime.submit_command(&"build_facility", payload,
|
||||
&"player", &"", 0, clock.absolute_minute, StringName("p23.build.%d" % index))
|
||||
ok = stream.record("build-%d" % index, {&"payload": payload, &"result": result.to_dictionary()}) and result.is_success() and ok
|
||||
clock.advance_minutes(construction_minutes + 1)
|
||||
ok = stream.record("prepared", runtime.create_snapshot()) and ok
|
||||
runtime.domain_events.subscribe(&"*", &"p23.capture", Callable(self, "_event"))
|
||||
var view := WorldPreview.new()
|
||||
view.size = Vector2(1440, 810)
|
||||
view.set_runtime(runtime)
|
||||
tree.root.add_child(view)
|
||||
stream.watch(view)
|
||||
var finished: Dictionary = {}
|
||||
for offset: int in range(WINDOW + 1):
|
||||
if not ok: break
|
||||
if offset > 0: clock.advance_minutes(1)
|
||||
for arrival: Dictionary in input[&"arrivals"]:
|
||||
if arrival[&"offset"] != offset: continue
|
||||
var payload := {&"guest_id": arrival[&"id"], &"guest_type_id": &"traveler", &"gold": 100,
|
||||
&"options": {&"current_cell": START, &"state": GuestRuntime.State.MOVING,
|
||||
&"state_entered_at": clock.absolute_minute - 1, &"planned_exit_at": clock.absolute_minute + 2000,
|
||||
&"path_handle": {&"target_cell": GOAL}}}
|
||||
var result: CommandResult = runtime.submit_command(&"spawn_guest", payload,
|
||||
&"world", arrival[&"id"], 10, -1, StringName("p23.spawn.%s" % arrival[&"id"]))
|
||||
ok = stream.record("arrival-%s" % arrival[&"id"], {&"offset": offset, &"payload": payload, &"result": result.to_dictionary()}) and result.is_success() and ok
|
||||
# Typed per-minute projections retain paths, queues, density and actual
|
||||
# completed services. Whole aggregate snapshots are only sampled sparsely.
|
||||
var guest_records: Dictionary = runtime.guest_service.create_snapshot()
|
||||
ok = stream.record("tick-%03d" % offset, {&"minute": clock.absolute_minute,
|
||||
&"guests": guest_records, &"path": runtime.path_service.create_snapshot(),
|
||||
&"elevators": runtime.elevators.create_snapshot(), &"facilities": runtime.facility_service.create_snapshot()}) and ok
|
||||
for guest: Dictionary in guest_records[&"guests"]:
|
||||
if guest[&"current_cell"] != [GOAL.x, GOAL.y, GOAL.z] or finished.has(guest[&"guest_id"]): continue
|
||||
finished[guest[&"guest_id"]] = offset
|
||||
if offset % 30 == 0:
|
||||
var drawn: bool = await stream.await_draws(tree, view)
|
||||
ok = drawn and stream.capture("frame-%03d" % offset, runtime, view) and ok
|
||||
ok = stream.record("first-arrivals", {&"offsets": finished}) and ok
|
||||
ok = stream.record("final", runtime.create_snapshot()) and ok
|
||||
stream.close()
|
||||
view.free()
|
||||
runtime.free()
|
||||
clock.free()
|
||||
state.free()
|
||||
return ok
|
||||
|
||||
func _event(event: SimulationDomainEvent) -> void:
|
||||
stream.record("event", {&"type": event.event_type, &"minute": event.absolute_minute,
|
||||
&"entities": event.entity_ids, &"payload": event.payload.duplicate(true)})
|
||||
@@ -0,0 +1 @@
|
||||
uid://dtxpm0n6cfom
|
||||
@@ -0,0 +1,193 @@
|
||||
extends "res://tests/fixtures/p08-03/placement.gd"
|
||||
## Real Runtime commands; inherited formal codec/save-operation helpers unchanged.
|
||||
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
||||
const Comparison := preload("res://tests/fixtures/p23-03/layout_comparison.gd")
|
||||
const PurchaseQuoteV2 := preload("res://scripts/supply/supply_purchase_quote_v2.gd")
|
||||
var stream: RefCounted
|
||||
var layout := "A"
|
||||
var facility_ids: Dictionary = {}
|
||||
|
||||
func setup() -> void:
|
||||
clock.reset(1, 6, 0)
|
||||
var state: Variant = tree.root.get_node("GameState")
|
||||
state.reset()
|
||||
expect(runtime.initialize(clock, state, 806042).get(&"valid", false), "initialize observation")
|
||||
clock.set_paused(true)
|
||||
tree.root.size = Vector2i(1440, 810)
|
||||
layout = OS.get_environment("P23_LAYOUT")
|
||||
expect(layout in ["A", "B"], "declared layout A or B")
|
||||
main = load("res://scenes/main/main.tscn").instantiate()
|
||||
main.manual_save_path = "user://saves/manual_ui"
|
||||
if role == "reader":
|
||||
import_package(directory.path_join("formal"), String(main.manual_save_path))
|
||||
tree.root.add_child(main)
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
stream = Stream.new()
|
||||
expect(stream.open(directory.path_join(role + "-stream")), "fresh observation stream")
|
||||
stream.watch(main.world_preview)
|
||||
await frames()
|
||||
|
||||
func command(id: StringName, payload: Dictionary) -> bool:
|
||||
var result: CommandResult = runtime.submit_command(id, payload)
|
||||
expect(result.is_success(), String(id) + " " + str(payload) + ": " + str(result.code))
|
||||
expect(stream.record("command", {&"command": id, &"payload": payload,
|
||||
&"result": result.to_dictionary(), &"minute": clock.absolute_minute}), "record public command")
|
||||
return result.is_success()
|
||||
|
||||
func advance(minutes: int) -> void:
|
||||
for unused: int in range(minutes):
|
||||
clock.advance_minutes(1)
|
||||
await frames()
|
||||
await observe_progress()
|
||||
if clock.absolute_minute % 30 == 0:
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture("minute-%d" % clock.absolute_minute, runtime, main.world_preview), "sparse minute observation")
|
||||
|
||||
func observe_progress() -> void:
|
||||
pass
|
||||
|
||||
func prepare() -> void:
|
||||
# Both layouts receive the same runtime-derived budget for all setup spends.
|
||||
# Cafeteria/kitchen move together to x=6, leaving x=4..5 for the declared B EV.
|
||||
var builds: Array = [[&"reception", Vector3i(-8, 0, 0)],
|
||||
[&"cafeteria", Vector3i(6, 0, 0)], [&"warehouse", Vector3i(-8, 0, 1)],
|
||||
[&"kitchen", Vector3i(6, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)],
|
||||
[&"standard_room", Vector3i(-8, 2, 0)], [&"guest_stairs", Vector3i(-4, 1, 0)],
|
||||
[&"guest_stairs", Vector3i(-4, 2, 0)], [&"guest_corridor", Vector3i(-5, 2, 0)],
|
||||
[&"guest_corridor", Vector3i(-6, 2, 0)], [&"guest_elevator", Vector3i.ZERO],
|
||||
[&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)],
|
||||
[&"staff_waiting_room", Vector3i(-4, 0, 1)]]
|
||||
var improvement: Dictionary = Comparison.new().declaration()[&"improvement"]
|
||||
var budget_builds := builds.duplicate(true)
|
||||
budget_builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
||||
var assignments: Array = [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"],
|
||||
[&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]
|
||||
var purchase_quantity := 40
|
||||
var needed := {&"gold": 0, &"materials": 0}
|
||||
var costs: Array = []
|
||||
for row: Array in budget_builds:
|
||||
var definition: FacilityDefinition = runtime.definition_registry.get_facility(row[0])
|
||||
costs.append({&"definition": row[0], &"cost": definition.construction_cost.duplicate(true)})
|
||||
for key: StringName in needed:
|
||||
needed[key] += int(definition.construction_cost.get(key, 0))
|
||||
var construction_total := needed.duplicate(true)
|
||||
var signing_costs: Array = []
|
||||
var signing_total := 0
|
||||
for assignment: Array in assignments:
|
||||
var candidate: Dictionary = runtime.mvp_content_catalog.get_recruitment_entry(assignment[0])
|
||||
expect(candidate.has(&"signing_cost"), "registered signing cost " + String(assignment[0]))
|
||||
var signing_cost := int(candidate[&"signing_cost"])
|
||||
signing_costs.append({&"candidate_id": assignment[0], &"signing_cost": signing_cost,
|
||||
&"source": "MvpContentCatalog.get_recruitment_entry.signing_cost"})
|
||||
signing_total += signing_cost
|
||||
needed[&"gold"] += signing_total
|
||||
# Before warehouse construction, use the purchase service's catalog/quote
|
||||
# branch. Check this total against its live preview immediately before buying.
|
||||
var item := SupplyCatalog.get_item(SupplyCatalog.RAW_FOOD)
|
||||
var purchase_quote := PurchaseQuoteV2.make(SupplyCatalog.RAW_FOOD, purchase_quantity, int(item[&"unit_price"]))
|
||||
expect(not purchase_quote.is_empty(), "registered raw-food purchase quote")
|
||||
var purchase_total := int(purchase_quote[&"effective_total"] if PurchaseQuoteV2.eligible(runtime) else purchase_quote[&"base_total"])
|
||||
needed[&"gold"] += purchase_total
|
||||
var purchase_cost := {&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": purchase_quantity,
|
||||
&"unit_price": item[&"unit_price"], &"quote": purchase_quote, &"total_cost": purchase_total,
|
||||
&"source": "SupplyCatalog.unit_price / PurchaseQuoteV2.make; preview_purchase eligibility"}
|
||||
var state: Variant = tree.root.get_node("GameState")
|
||||
var deltas := {}
|
||||
for key: StringName in needed:
|
||||
if needed[key] > state.get_resource(key):
|
||||
deltas[key] = needed[key] - state.get_resource(key)
|
||||
budget = {"setup": "diagnostic", "costs": costs, "needed": needed,
|
||||
"initial": state.resources.duplicate(true), "deltas": deltas,
|
||||
"construction_total": construction_total, "signing_costs": signing_costs,
|
||||
"signing_total": signing_total, "purchase_cost": purchase_cost}
|
||||
if not deltas.is_empty(): command(&"change_resources", {&"deltas": deltas})
|
||||
expect(stream.record("budget", budget), "common diagnostic budget")
|
||||
if layout == "B": builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
||||
# Preserve the supply scenario's staged support/construction sequence.
|
||||
for index: int in range(builds.size()):
|
||||
if index == 5: await advance(180)
|
||||
if index == 7: await advance(60)
|
||||
if index == 8: await advance(10)
|
||||
command(&"build_facility", {&"definition_id": builds[index][0], &"origin": builds[index][1]})
|
||||
var transport_minutes := 0
|
||||
for id: StringName in [&"guest_elevator", &"guest_corridor"]:
|
||||
transport_minutes = maxi(transport_minutes, runtime.definition_registry.get_facility(id).build_minutes)
|
||||
await advance(maxi(10, transport_minutes))
|
||||
for facility: FacilityInstance in runtime.facility_service.get_instances():
|
||||
facility_ids[facility.definition_id] = facility.instance_id
|
||||
command(&"set_facility_conditions", {&"facility_id": facility_ids[&"cafeteria"],
|
||||
&"conditions": {&"related_facility_id": facility_ids[&"kitchen"]}})
|
||||
for assignment: Array in assignments:
|
||||
command(&"hire_staff_candidate", {&"candidate_id": assignment[0]})
|
||||
command(&"set_staff_work_assignment", {&"staff_id": assignment[0],
|
||||
&"facility_id": facility_ids[assignment[1]], &"template_id": &"DAY"})
|
||||
var actual_quote: Dictionary = runtime.preview_supply_purchase(facility_ids[&"warehouse"], SupplyCatalog.RAW_FOOD, purchase_quantity)
|
||||
expect(int(actual_quote[&"total_cost"]) == purchase_total, "budget matches actual purchase quote")
|
||||
expect(stream.record("setup-purchase-quote", actual_quote), "record actual setup purchase quote")
|
||||
command(&"purchase_supplies", {&"warehouse_id": facility_ids[&"warehouse"],
|
||||
&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": purchase_quantity})
|
||||
command(&"set_kitchen_production", {&"facility_id": facility_ids[&"kitchen"],
|
||||
&"enabled": true, &"target_stock": 8})
|
||||
# The OS observation uses the already declared transport cohort, plus
|
||||
# ordinary public arrivals whose service choice remains Runtime-owned.
|
||||
# These commands are recorded and identical across A/B; no actor is moved,
|
||||
# despawned, or inserted into a room/queue by the fixture.
|
||||
var demand: Dictionary = Comparison.new().declaration()
|
||||
expect(stream.record("observation-demand", {"cohort": demand[&"arrivals"],
|
||||
"schedule": "all observation actors at the recorded current minute; offsets apply only to native layout comparison",
|
||||
"minute": clock.absolute_minute}), "declared observation demand")
|
||||
for arrival: Dictionary in demand[&"arrivals"]:
|
||||
command(&"spawn_guest", {&"guest_id": StringName("os-" + String(arrival[&"id"])),
|
||||
&"guest_type_id": &"traveler", &"gold": 100,
|
||||
&"options": {&"current_cell": Comparison.START, &"state": GuestRuntime.State.MOVING,
|
||||
&"state_entered_at": clock.absolute_minute - 1, &"planned_exit_at": clock.absolute_minute + 2000,
|
||||
&"path_handle": {&"target_cell": Comparison.GOAL}}})
|
||||
command(&"spawn_guest", {&"guest_id": StringName("service-" + String(arrival[&"id"])),
|
||||
&"guest_type_id": &"traveler", &"gold": 100})
|
||||
await advance(120)
|
||||
|
||||
func layer_observations(prefix: String) -> void:
|
||||
await button(main.front_button)
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture(prefix + "-FRONT", runtime, main.world_preview), "FRONT state/report")
|
||||
await button(main.back_button)
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture(prefix + "-BACK", runtime, main.world_preview), "BACK state/report")
|
||||
|
||||
func writer() -> void:
|
||||
await prepare()
|
||||
await layer_observations("writer")
|
||||
capture("presave")
|
||||
await button(main.find_child("ManualSaveButton", true, false))
|
||||
expect(main.build_status_label.text == "手動セーブを保存しました。", "normal save")
|
||||
capture("checkpoint")
|
||||
export_package(String(main.manual_save_path), "formal")
|
||||
var formal := inspect_formal()
|
||||
var presave := FileAccess.get_file_as_bytes(directory.path_join("writer-presave.var"))
|
||||
expect(presave == var_to_bytes(formal), "whole presave formal model")
|
||||
expect(not operation_results.is_empty(), "save result recorded")
|
||||
if not operation_results.is_empty():
|
||||
verify_operation(formal, operation_results.back(), &"SAVE_CHECKPOINT_SUCCEEDED")
|
||||
var members := {}
|
||||
for member: String in MEMBERS:
|
||||
members[member] = FileAccess.get_sha256(directory.path_join("formal/" + member))
|
||||
json_file("save.json", {"pid": OS.get_process_id(), "members": members})
|
||||
|
||||
func reader(distinct: bool) -> void:
|
||||
var saved: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(directory.path_join("save.json")))
|
||||
expect(distinct and saved.pid != OS.get_process_id(), "distinct actual reader PID")
|
||||
await load_menu(main.find_child("SaveLoadMenu", true, false))
|
||||
await frames()
|
||||
expect(main.build_status_label.text == "手動セーブを読み込みました。", "normal resume")
|
||||
capture("checkpoint")
|
||||
var formal := inspect_formal()
|
||||
expect(FileAccess.get_file_as_bytes(directory.path_join("writer-formal-model.var")) == var_to_bytes(formal), "whole formal resume model")
|
||||
for member: String in MEMBERS:
|
||||
expect(FileAccess.get_sha256(directory.path_join("formal/" + member)) == saved.members[member], "formal member " + member)
|
||||
expect(not operation_results.is_empty(), "load result recorded")
|
||||
if not operation_results.is_empty():
|
||||
verify_operation(formal, operation_results.back(), &"SAVE_RECOVERY_LOADED")
|
||||
await layer_observations("reader")
|
||||
|
||||
func finish_observations() -> void:
|
||||
stream.close()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmoi8je5hse38
|
||||
@@ -0,0 +1,266 @@
|
||||
extends "res://tests/fixtures/p23-03/observation.gd"
|
||||
## Passive endpoint implementation. All gameplay/input is performed by Root CUA.
|
||||
## Shared parent contains the handler endpoints, but every such endpoint below
|
||||
## is overridden; this class never emits UI signals or invokes product handlers.
|
||||
var receipts: Array = []
|
||||
var terminated := false
|
||||
var terminal_record: Dictionary = {}
|
||||
var terminal_cause_recorded := false
|
||||
var reveal_rows: Array = []
|
||||
var all_requests: Array = []
|
||||
var input_observer: Node
|
||||
var last_dispatch: Dictionary = {}
|
||||
var observed_categories: Dictionary = {}
|
||||
|
||||
func observe_progress() -> void:
|
||||
if role != "writer" or terminated: return
|
||||
var wanted: Dictionary = {}
|
||||
var report: Dictionary = main.world_preview.get_life_observation_report()
|
||||
for room: Dictionary in report.get(&"rooms", {}).values():
|
||||
if int(room.get(&"count", 0)) > 0: wanted["room-users"] = true
|
||||
for badge: Dictionary in report.get(&"landing_badges", []):
|
||||
if int(badge.get(&"overflow", 0)) > 0: wanted["queue-overflow"] = true
|
||||
for cabin: Dictionary in report.get(&"cabins", []):
|
||||
if not cabin[&"passengers"].is_empty(): wanted["EV-passengers"] = true
|
||||
var elevators: Dictionary = runtime.elevators.get_report()
|
||||
for shaft: Dictionary in elevators.get(&"shafts", []):
|
||||
if not shaft.get(&"boarded", []).is_empty(): wanted["EV-passengers"] = true
|
||||
for trip: Dictionary in elevators.get(&"trips", []):
|
||||
if trip.get(&"state", &"") == &"QUEUED": wanted["queue-present"] = true
|
||||
for member: StaffRuntime in runtime.staff_service.get_staff_members():
|
||||
if not member.carried_inventory_id.is_empty(): wanted["carried-goods"] = true
|
||||
if not report.get(&"resting_staff", {}).is_empty(): wanted["rest-position"] = true
|
||||
for category: String in wanted:
|
||||
if observed_categories.has(category): continue
|
||||
observed_categories[category] = true
|
||||
await layer_observations("actual-" + category)
|
||||
if category == "EV-passengers": await motion_observations()
|
||||
|
||||
func motion_observations() -> void:
|
||||
await button(main.front_button)
|
||||
await button(main.pause_button)
|
||||
expect(not clock.paused, "ordinary resume for motion observation")
|
||||
var minute: int = clock.absolute_minute
|
||||
while clock.absolute_minute == minute and not terminated:
|
||||
await tree.process_frame
|
||||
await frames()
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture("active-motion", runtime, main.world_preview), "active interpolation history")
|
||||
await button(main.pause_button)
|
||||
expect(clock.paused, "ordinary pause for stable observation")
|
||||
await frames()
|
||||
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
|
||||
expect(stream.capture("paused-motion", runtime, main.world_preview), "paused interpolation history")
|
||||
|
||||
class InputObserver extends Node:
|
||||
var events: Array = []
|
||||
var endpoint: RefCounted = null
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton or event is InputEventKey or event is InputEventMouseMotion:
|
||||
var row := {"ticks_usec": Time.get_ticks_usec(), "text": event.as_text(), "pressed": event.is_pressed()}
|
||||
if event is InputEventMouseButton:
|
||||
row["button"] = event.button_index
|
||||
row["position"] = [event.position.x, event.position.y]
|
||||
row["ctrl"] = event.ctrl_pressed
|
||||
if endpoint != null and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
row["dispatch"] = endpoint.dispatch_position(event.position)
|
||||
events.append(row)
|
||||
|
||||
func expect(ok: bool, message: String) -> void:
|
||||
if terminated and ok:
|
||||
return
|
||||
if terminated and terminal_cause_recorded:
|
||||
if not ok:
|
||||
terminal_record["suppressed_after_terminal"] += 1
|
||||
json_file(role + "-terminal.json", terminal_record)
|
||||
return
|
||||
if terminated and not ok:
|
||||
terminal_cause_recorded = true
|
||||
super.expect(ok, message)
|
||||
|
||||
func terminate(stem: String, reason: String) -> void:
|
||||
terminated = true
|
||||
terminal_record = {"pid": OS.get_process_id(), "role": role, "sequence": serial,
|
||||
"stem": stem, "reason": reason, "suppressed_after_terminal": 0}
|
||||
json_file(role + "-terminal.json", terminal_record)
|
||||
expect(false, reason)
|
||||
tree.quit(1)
|
||||
|
||||
func request(action: Dictionary) -> void:
|
||||
if terminated:
|
||||
return
|
||||
serial += 1
|
||||
var stem := "%s-request-%04d" % [role, serial]
|
||||
var folder := directory.path_join(stem)
|
||||
DirAccess.make_dir_recursive_absolute(folder)
|
||||
var window: Window = main.get_window()
|
||||
var view: WorldPreview = main.world_preview
|
||||
expect(await stream.await_draws(tree, view), "real draw history before capture")
|
||||
action["report"] = Codec.new().typed(view.get_life_observation_report())
|
||||
action["viewport_to_screen"] = affine(view.get_screen_transform() * view.get_global_transform_with_canvas().affine_inverse())
|
||||
action["local_to_viewport"] = affine(view.get_global_transform_with_canvas())
|
||||
action["local_to_screen"] = affine(view.get_screen_transform())
|
||||
action["rectangles"] = Codec.new().typed(draw_rectangles(view.get_life_observation_report(), view))
|
||||
action["requested_ticks_usec"] = Time.get_ticks_usec()
|
||||
action["requested_unix_ns"] = int(Time.get_unix_time_from_system() * 1000000000.0)
|
||||
action["screenshot_receipt_schema"] = {"screenshots": [{"path": "evidence-root-relative raw image path", "raw_sha256": "original byte SHA256", "tool_call_id": "actual CUA screenshot call ID"}]}
|
||||
expect(stream.capture(stem, runtime, view), "request state/report")
|
||||
action.merge({"sequence": serial, "target_pid": OS.get_process_id(), "role": role,
|
||||
"state": "waiting_for_root_cua", "window_title": window.title,
|
||||
"window_position": [window.position.x, window.position.y],
|
||||
"window_size": [window.size.x, window.size.y], "screen_scale": DisplayServer.screen_get_scale(window.current_screen),
|
||||
"content_scale_factor": window.content_scale_factor, "setup": "diagnostic"})
|
||||
json_file(stem + "/request.json", action)
|
||||
var path := folder.path_join("ack.json")
|
||||
var first_event: int = input_observer.events.size()
|
||||
var deadline := Time.get_ticks_msec() + 300000
|
||||
print("P23_ROOT_CUA_WAIT " + folder.path_join("request.json"))
|
||||
while not FileAccess.file_exists(path) and Time.get_ticks_msec() < deadline:
|
||||
await tree.process_frame
|
||||
if not FileAccess.file_exists(path):
|
||||
terminate(stem, "Root CUA acknowledgement timeout")
|
||||
return
|
||||
var ack: Variant = JSON.parse_string(FileAccess.get_file_as_string(path))
|
||||
if not ack is Dictionary or ack.get("target_pid") != OS.get_process_id() or ack.get("sequence") != serial or ack.get("request_sha256") != FileAccess.get_sha256(folder.path_join("request.json")):
|
||||
terminate(stem, "Root CUA acknowledgement identity/hash")
|
||||
return
|
||||
await frames()
|
||||
for event: Dictionary in input_observer.events.slice(first_event):
|
||||
if event.has("dispatch"):
|
||||
last_dispatch = event["dispatch"]
|
||||
json_file(stem + "/observed.json", {"pid": OS.get_process_id(), "sequence": serial,
|
||||
"ack_observed_ticks_usec": Time.get_ticks_usec(), "request_sha256": FileAccess.get_sha256(folder.path_join("request.json")),
|
||||
"ack_observed_unix_ns": int(Time.get_unix_time_from_system() * 1000000000.0),
|
||||
"observer_endpoint": "null_modal_receipts_only" if input_observer.endpoint == null else "passive_read_only_dispatch",
|
||||
"events": input_observer.events.slice(first_event), "status": main.build_status_label.text})
|
||||
receipts.append("os-pair/" + layout + "/" + stem + "/ack.json")
|
||||
all_requests.append("os-pair/" + layout + "/" + stem + "/ack.json")
|
||||
|
||||
func annotate(row: Dictionary) -> void:
|
||||
var view: WorldPreview = main.world_preview
|
||||
row["rectangles"] = Codec.new().typed(draw_rectangles(view.get_life_observation_report(), view))
|
||||
row["viewport_to_screen"] = affine(view.get_screen_transform() * view.get_global_transform_with_canvas().affine_inverse())
|
||||
row["reveal"] = reveal_rows.duplicate(true)
|
||||
reveal_rows.clear()
|
||||
row["input_method"] = "native_os"
|
||||
row["requests"] = receipts.duplicate()
|
||||
receipts.clear()
|
||||
|
||||
# Rectangles describe existing draw commands; no second renderer is invoked.
|
||||
func draw_rectangles(report: Dictionary, view: WorldPreview) -> Dictionary:
|
||||
var rows: Array = []
|
||||
for id: Variant in report.get(&"rooms", {}):
|
||||
var room: Dictionary = report[&"rooms"][id]
|
||||
if room.has(&"rect"):
|
||||
rows.append({"category": "room", "id": id, "rect": room[&"rect"], "style_source": "scripts/ui/pixel_tower_art.gd"})
|
||||
for index: int in range(report.get(&"landing_badges", []).size()):
|
||||
var badge: Dictionary = report[&"landing_badges"][index]
|
||||
rows.append(badge_rectangle("queue", str(index), badge[&"at"], badge[&"text"]))
|
||||
for cabin: Dictionary in report.get(&"cabins", []):
|
||||
rows.append({"category": "EV", "id": cabin[&"shaft_id"], "rect": cabin[&"rect"], "style_source": "scripts/ui/pixel_tower_art.gd"})
|
||||
for actor: Dictionary in cabin[&"passengers"]:
|
||||
rows.append(actor_rectangle(actor))
|
||||
for actor: Dictionary in report.get(&"actor_drawings", []):
|
||||
rows.append(actor_rectangle(actor))
|
||||
if actor[&"staff"] and report[&"resting_staff"].has(StringName(String(actor[&"key"]).trim_prefix("staff/"))):
|
||||
var rest := actor_rectangle(actor)
|
||||
rest["category"] = "rest"
|
||||
rows.append(rest)
|
||||
for index: int in range(report.get(&"actor_aggregations", []).size()):
|
||||
var badge: Dictionary = report[&"actor_aggregations"][index]
|
||||
rows.append(badge_rectangle("aggregation", str(index), badge[&"at"], badge[&"text"]))
|
||||
for index: int in range(report.get(&"badges", []).size()):
|
||||
var badge: Dictionary = report[&"badges"][index]
|
||||
rows.append(badge_rectangle("goods" if badge[&"kind"] == &"supply" else "business", str(index), badge[&"at"], badge[&"text"]))
|
||||
for entry: Dictionary in report.get(&"traffic", []):
|
||||
var traffic: Dictionary = entry[&"traffic"]
|
||||
if traffic[&"current_density"] > 0 or traffic[&"temporary_block"]:
|
||||
var cell: Vector3i = entry[&"cell"]
|
||||
rows.append({"category": "traffic", "id": str(cell.x) + "," + str(cell.y) + "," + str(cell.z),
|
||||
"rect": entry[&"rect"], "style_source": "scenes/main/world_preview.gd"})
|
||||
var transform := view.get_global_transform_with_canvas()
|
||||
for row: Dictionary in rows:
|
||||
row["viewport_rect"] = transform * (row["rect"] as Rect2)
|
||||
row["screen_rect"] = view.get_screen_transform() * (row["rect"] as Rect2)
|
||||
var source: String = FileAccess.get_file_as_string("res://" + row["style_source"])
|
||||
var regex := RegEx.new()
|
||||
regex.compile('Color\\("([0-9a-fA-F]{6})"')
|
||||
var colors: Array = []
|
||||
for found: RegExMatch in regex.search_all(source):
|
||||
if not colors.has(found.get_string(1)): colors.append(found.get_string(1))
|
||||
row["style_colors"] = colors
|
||||
return {"rows": rows}
|
||||
|
||||
func badge_rectangle(category: String, id: String, at: Vector2, text: String) -> Dictionary:
|
||||
var width := ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x + 6
|
||||
return {"category": category, "id": id, "rect": Rect2(at, Vector2(text.length() * 10 + 6, 14)),
|
||||
"text": text, "font_width": width - 6, "style_source": "scenes/main/world_preview.gd"}
|
||||
|
||||
func actor_rectangle(actor: Dictionary) -> Dictionary:
|
||||
return {"category": "staff" if actor[&"staff"] else "guest", "id": actor[&"key"],
|
||||
"rect": Rect2(actor[&"at"], Vector2(16, 18) * float(actor[&"scale"])), "style_source": "scripts/ui/pixel_people.gd"}
|
||||
|
||||
func point(node: Control) -> Dictionary:
|
||||
var viewport: Vector2 = node.get_global_transform_with_canvas() * (node.size * 0.5)
|
||||
var screen: Vector2 = node.get_screen_transform() * (node.size * 0.5)
|
||||
return {"node": String(node.get_path()), "viewport": [viewport.x, viewport.y], "screen": [screen.x, screen.y], "window_id": node.get_window().get_window_id(),
|
||||
"viewport_to_screen": affine(node.get_screen_transform() * node.get_global_transform_with_canvas().affine_inverse())}
|
||||
|
||||
func setup() -> void:
|
||||
await super.setup()
|
||||
input_method = "native_os"
|
||||
expect(DisplayServer.get_name() != "headless", "real OS display")
|
||||
tree.root.title = "The Tree P23-03 " + layout + " " + role + " PID " + str(OS.get_process_id())
|
||||
input_observer = InputObserver.new()
|
||||
# No handler dispatch: this observer only records incoming OS events.
|
||||
tree.root.add_child(input_observer)
|
||||
json_file(role + "-profile.json", {"pid": OS.get_process_id(),
|
||||
"user_data_dir": OS.get_user_data_dir(), "engine": Engine.get_version_info(),
|
||||
"display": DisplayServer.get_name()})
|
||||
|
||||
func button(node: BaseButton) -> void:
|
||||
expect(node != null and not node.disabled, "reachable ordinary button")
|
||||
if node == null or node.disabled: return
|
||||
await request({"action": "click", "point": point(node), "button_text": str(node.get("text"))})
|
||||
|
||||
func load_menu(menu: MenuButton) -> void:
|
||||
var action := point(menu)
|
||||
action.merge({"action": "load_menu", "item_id": 0, "instructions": "Open this normal menu and choose manual load (item 0)."})
|
||||
await request(action)
|
||||
|
||||
func screenshot(label: String) -> void:
|
||||
await frames()
|
||||
await request({"action": "screenshot", "id": label, "layout": layout})
|
||||
|
||||
func layer_observations(prefix: String) -> void:
|
||||
if role == "writer" and prefix == "writer": await screenshot("calibration")
|
||||
await button(main.front_button)
|
||||
await screenshot(prefix + "-FRONT")
|
||||
await button(main.back_button)
|
||||
await screenshot(prefix + "-BACK")
|
||||
if role == "writer" and prefix == "writer": await ordinary_camera()
|
||||
|
||||
func ordinary_camera() -> void:
|
||||
var view: WorldPreview = main.world_preview
|
||||
var at := point(view)
|
||||
expect(not view.pan_mode, "ordinary zoom starts with move mode off")
|
||||
var zoom: float = view.zoom_level
|
||||
await request({"action": "wheel", "point": at, "direction": "down",
|
||||
"ctrl": false, "move_mode": false})
|
||||
expect(view.zoom_level != zoom, "ordinary wheel changes zoom")
|
||||
await screenshot("ordinary-wheel-zoom")
|
||||
await button(main.pan_button)
|
||||
await screenshot("pan-mode-button")
|
||||
expect(view.pan_mode, "PanModeButton enables move mode")
|
||||
zoom = view.zoom_level
|
||||
var pan: Vector2 = view.pan_offset
|
||||
await request({"action": "wheel", "point": point(view), "direction": "up",
|
||||
"ctrl": false, "move_mode": true})
|
||||
expect(view.pan_offset != pan and view.zoom_level == zoom, "ordinary move-mode wheel pans")
|
||||
await screenshot("move-mode-wheel-pan")
|
||||
|
||||
func finish_observations() -> void:
|
||||
json_file(role + "-requests.json", {"requests": all_requests, "pid": OS.get_process_id()})
|
||||
if is_instance_valid(input_observer): input_observer.queue_free()
|
||||
super.finish_observations()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://d3oqtdp6dodu1
|
||||
@@ -0,0 +1,8 @@
|
||||
extends SceneTree
|
||||
const Passive := preload("res://tests/fixtures/p23-03/passive_observation.gd")
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
func run() -> void:
|
||||
var result: Dictionary = await Passive.new().run(self, OS.get_environment("P23_OBSERVATION_ROOT"), OS.get_environment("P23_OBSERVATION_ROLE"))
|
||||
print("P23 passive OS observation: %d checks, %d failures" % [result.checks, result.failures])
|
||||
quit(0 if result.failures == 0 else 1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://mn33trympqu6
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"provenance": "Distilled layout-A frame-030 state ac3ed64a6e6162918e4d44c45b2f78d09e50d473b607f3469350a06074a7efb3; retain first real queued trip, no cabin seat. Unit fixture only.",
|
||||
"saved": {
|
||||
"version": 1,
|
||||
"current_minute": 691,
|
||||
"request_sequence": 8,
|
||||
"shafts": [{
|
||||
"config": {
|
||||
"shaft_id": "transport.shaft.0.0.0.2",
|
||||
"module_ids": ["facility_00000003"],
|
||||
"stops": [[0, 0, 0], [0, 1, 0]],
|
||||
"width": 2, "capacity": 4, "travel_minutes_per_floor": 10, "access_id": ""
|
||||
},
|
||||
"operational": true, "operator_open": true, "powered": true,
|
||||
"current_floor": 1, "target_floor": 1, "boarding_floor": 0,
|
||||
"phase": "PICKUP", "remaining_minutes": 9, "phase_total_minutes": 10,
|
||||
"phase_started_at": 690, "phase_origin_floor": 1, "phase_passenger_count": 0,
|
||||
"boarded": []
|
||||
}],
|
||||
"trips": [{
|
||||
"actor_id": "guest:p23-000", "request_id": "transport.trip.000000000002",
|
||||
"shaft_id": "transport.shaft.0.0.0.2",
|
||||
"connector_id": "transport.shaft.0.0.0.2.link.0.1",
|
||||
"from": [0, 0, 0], "to": [0, 1, 0], "enqueued_at": 674, "state": "QUEUED"
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"purpose": "Historical negative only; not fresh acceptance evidence",
|
||||
"source_request": "os-pair/A/reader-request-0003/request.json",
|
||||
"source_request_sha256": "7dc3ee0bcc428e9d103726ee2448c767f1dd4107f9490833dc860c0b5140068c",
|
||||
"manifest_sha256": "53aa8e2f12e5d0a089529457fcd55ee1ba0600e8dffff2225bb8b057d007fe1a",
|
||||
"independent_judgment": [11, false, false, false],
|
||||
"source_rows": [11, 29],
|
||||
"report_inputs": {
|
||||
"actor_drawings": [
|
||||
{"key": "guest/guest-auto-00000041", "at": [651.0, 517.0], "scale": 3.0, "mode": "wait", "frame": 12, "flip": false},
|
||||
{"key": "staff/staff_gina", "at": [651.0, 517.0], "scale": 3.0, "mode": "idle", "frame": 3, "flip": false}
|
||||
],
|
||||
"cabins": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"purpose": "Historical diagnostic input for placement-rule clarification; not fresh acceptance evidence",
|
||||
"source_request": "os-pair/A/reader-request-0003/request.json",
|
||||
"source_request_sha256": "7dc3ee0bcc428e9d103726ee2448c767f1dd4107f9490833dc860c0b5140068c",
|
||||
"guests": [
|
||||
{"id": "guest-auto-00000044", "state": "RECEIVING_SERVICE", "cell": [5, 0, 0], "target": "facility_00000003", "service": "service_00000009", "queue": ""},
|
||||
{"id": "guest-auto-00000019", "state": "RECEIVING_SERVICE", "cell": [-6, 2, 0], "target": "facility_00000007", "service": "service_00000002", "queue": ""}
|
||||
],
|
||||
"room_rects": {
|
||||
"facility_00000003": [1027.0, 388.0, 256.0, 192.0],
|
||||
"facility_00000007": [131.0, 292.0, 128.0, 96.0]
|
||||
},
|
||||
"traffic": [
|
||||
{"cell": [5, 0, 0], "traffic": {"base_capacity": 4, "current_density": 6, "dynamic_cost": 0, "temporary_block": false}, "rect": [965.0, 486.0, 60.0, 92.0]},
|
||||
{"cell": [-6, 2, 0], "traffic": {"base_capacity": 4, "current_density": 3, "dynamic_cost": 0, "temporary_block": false}, "rect": [261.0, 294.0, 60.0, 92.0]}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"applicability": "Historical negative only. Distilled local-coordinate fields; never S/T execution proof.",
|
||||
"source_commit": "9a26f9ab8c6691ba3cbe32ac4bc4525f4a9c7106",
|
||||
"manifest_sha256": "a25e15f00d09acb16b05e180a6263c3771c84449bd6e0c99b6968fd739727bd2",
|
||||
"request_sha256": "7099ca03da3cef5819e7965f6fdea8777b9995a9e12806e870e844ec2b02b9a5",
|
||||
"request_relative_to_R15": "os-pair/A/reader-request-0003/request.json",
|
||||
"actual_negative_rows": [6, 17],
|
||||
"cabin_rect": [659, 463, 96, 76],
|
||||
"passenger_count": 0,
|
||||
"actor": {"key": "guest/os-p23-024", "center": [739, 532], "at": [715, 517], "scale": 3, "mode": "wait", "frame": 6, "flip": false},
|
||||
"traffic": {"cell": [0, 1, 0], "rect": [645, 390, 60, 92], "band_rect": [652.2, 473.5, 45.6, 5], "traffic": {"base_capacity": 4, "current_density": 16, "dynamic_cost": 0, "temporary_block": false}},
|
||||
"queue_badge": {"at": [563, 468], "text": "待ち27人(他18人)"},
|
||||
"note": "Raw R15 maps/receipts remain unchanged. New geometry tests calculate a counterfactual projection in memory only."
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
extends SceneTree
|
||||
## Component and read-only rendering checks; never OS or same-demand proof.
|
||||
const Presentation := preload("res://scripts/ui/room_presentation.gd")
|
||||
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
|
||||
class QueueWorld extends RefCounted:
|
||||
var revision := 0
|
||||
func get_bounds() -> Dictionary:
|
||||
return {&"min_x": -1, &"max_x": 2, &"min_y": 0, &"max_y": 1}
|
||||
func is_walkable(cell: Vector3i) -> bool:
|
||||
return cell.y >= 0 and cell.y <= 1 and cell.x >= -1 and cell.x <= 2
|
||||
func get_occupant(_cell: Vector3i) -> StringName:
|
||||
return &""
|
||||
|
||||
class QueueLift extends RefCounted:
|
||||
var layer := 0
|
||||
var visual_floor := 0.3
|
||||
func get_report() -> Dictionary:
|
||||
var trips: Array = []
|
||||
for i: int in 3:
|
||||
trips.append({&"state": PathService.ELEVATOR_REQUEST_QUEUED, &"from": Vector3i(0,0,layer),
|
||||
&"actor_id": "guest:q%d" % i, &"enqueued_at": i})
|
||||
return {&"shafts": [{&"shaft_id": &"wide", &"stops": [Vector3i(0,0,layer), Vector3i(0,1,layer)],
|
||||
&"width": 2, &"capacity": 4, &"boarded": [], &"visual_floor": visual_floor,
|
||||
&"operational": true, &"phase": &"PICKUP"}], &"trips": trips}
|
||||
|
||||
class QueueRuntime extends RefCounted:
|
||||
var world_service := QueueWorld.new()
|
||||
var elevators := QueueLift.new()
|
||||
|
||||
func repair28_geometry() -> void:
|
||||
var view := WorldPreview.new()
|
||||
view.size = Vector2(408,332)
|
||||
var fixture := QueueRuntime.new()
|
||||
view._runtime = fixture
|
||||
root.add_child(view)
|
||||
for layer: int in [0,1]:
|
||||
view.layer_mode = layer as WorldPreview.LayerMode
|
||||
fixture.elevators.layer = layer
|
||||
var model: Dictionary = view._observe_queues()
|
||||
expect(model[&"shaft_footprint"].has(Vector3i(1,0,layer)), "width-2 interior belongs to shaft")
|
||||
expect(model[&"queue_positions"].size() == 1 and model[&"queue_hidden"].size() == 2, "shaft stops outward lane and retains overflow")
|
||||
var badge: Dictionary = model[&"landing_badges"][0]
|
||||
expect(badge[&"members"].size() == badge[&"visible_count"] + badge[&"overflow"], "no queue member lost")
|
||||
model.merge({&"boarded": {}, &"elevator_tracks": {}, &"paused": true})
|
||||
var cabins: Array[Dictionary] = view._observe_elevators(model)
|
||||
expect(cabins.size() == 1, "active-layer cabin remains visible")
|
||||
for at: Vector2 in model[&"queue_positions"].values():
|
||||
for cell: Vector3i in model[&"shaft_footprint"]:
|
||||
expect(not view.get_cell_rect(cell).has_point(at), "queue anchor outside full footprint")
|
||||
expect(not cabins[0][&"rect"].has_point(at), "queue anchor outside empty cabin")
|
||||
expect(cabins[0][&"passenger_count"] == 0 and cabins[0][&"passengers"].is_empty(), "empty cabin has no passenger")
|
||||
expect(cabins[0][&"operational"] and not cabins[0][&"doors_open"] and cabins[0][&"phase"] == &"PICKUP", "typed moving cabin state")
|
||||
for floor: float in [0.0, 0.8]:
|
||||
fixture.elevators.visual_floor = floor
|
||||
var pose_model: Dictionary = view._observe_queues()
|
||||
var cell := Vector3i(0, 0 if floor == 0 else 1, layer)
|
||||
var key := "guest/landing"
|
||||
var person := {&"id": &"landing", &"cell": cell, &"service": &"", &"target": &""}
|
||||
pose_model.merge({&"boarded": {}, &"elevator_tracks": {}, &"paused": true,
|
||||
&"actor_tracks": {key: {&"cell": cell, &"from": cell, &"at": 0.0, &"direction": 1.0}},
|
||||
&"rooms": {}, &"guests": [person], &"staff": [], &"carried_patients": {},
|
||||
&"resting_staff": {}, &"treated_patients": {}})
|
||||
pose_model[&"cabins"] = view._observe_elevators(pose_model)
|
||||
var row: Dictionary = view._actor_drawing(view.cell_to_canvas(cell), key, false,
|
||||
GuestRuntime.State.MOVING, false, false, &"", false, pose_model)
|
||||
var region: Dictionary = view._life_actor_region(row, person, pose_model)
|
||||
expect(region.has(&"center") and not region[&"transit"], "same-floor and fractional cabin use landing region")
|
||||
expect(not (region[&"bounds"] as Rect2).intersects(pose_model[&"cabins"][0][&"rect"]), "landing bounds outside cabin")
|
||||
pose_model[&"actor_drawings"] = [row]
|
||||
view._fit_life_actors(pose_model)
|
||||
expect(pose_model[&"actor_drawings"].size() == 1 and pose_model[&"actor_aggregations"].is_empty(), "landing actor retained")
|
||||
expect(not view._life_core(row).intersects(pose_model[&"cabins"][0][&"rect"]), "fitted non-passenger core outside cabin")
|
||||
expect(row[&"cell"] == cell and not row[&"riding"] and row[&"state"] == GuestRuntime.State.MOVING, "landing placement preserves actual state")
|
||||
fixture.elevators.visual_floor = 0.3
|
||||
for operational: bool in [true,false]:
|
||||
for phase: StringName in [&"IDLE", &"BOARDING", &"PICKUP"]:
|
||||
var shaft: Dictionary = fixture.elevators.get_report()[&"shafts"][0]
|
||||
shaft[&"operational"] = operational
|
||||
shaft[&"phase"] = phase
|
||||
var text := Presentation.elevator_label(shaft)
|
||||
expect(text.contains("0/4人") and text.contains(String(phase)), "count and phase visible")
|
||||
expect(text.begins_with("運転" if operational else "停止"), "operation visible beyond lamp")
|
||||
expect(text.contains(" 開 " if operational and phase != &"PICKUP" else " 閉 "), "doors visible beyond lamp")
|
||||
var obstacles: Array[Rect2] = [Rect2(20,0,20,90), Rect2(45,80,20,14)]
|
||||
var segments := view._traffic_segments(Rect2(10,85,60,5), Rect2(0,0,80,96), obstacles)
|
||||
var area := 0.0
|
||||
for segment: Rect2 in segments:
|
||||
area += segment.get_area()
|
||||
for obstacle: Rect2 in obstacles:
|
||||
expect(not segment.intersects(obstacle), "traffic never crosses cabin or badge")
|
||||
expect(is_equal_approx(area, 100.0), "traffic preserves all uncovered area")
|
||||
var covering: Array[Rect2] = [Rect2(0,80,80,16)]
|
||||
var fallback := view._traffic_segments(Rect2(10,85,60,5), Rect2(0,0,80,96), covering)
|
||||
expect(fallback.size() == 1 and fallback[0] == Rect2(10,0,60,5), "shaft-column density remains visible")
|
||||
view.free()
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func repair36_labels() -> void:
|
||||
var view := WorldPreview.new()
|
||||
var members: Array[String] = ["guest/a", "guest/b", "guest/c"]
|
||||
var identities: Array[Dictionary] = []
|
||||
for key: String in members:
|
||||
identities.append({&"key": key, &"id": key.trim_prefix("guest/"), &"state": 0, &"cell": Vector3i.ZERO, &"mode": &"idle"})
|
||||
var queue := {&"at": Vector2(40,40), &"text": "待ち3人(他3人)", &"cell": Vector3i.ZERO, &"members": members.duplicate(), &"visible_count": 0, &"overflow": members.size()}
|
||||
var aggregate := {&"at": queue[&"at"], &"text": "+3", &"members": identities.duplicate(true), &"drawn_keys": []}
|
||||
var model := {&"cabins": [], &"badges": [], &"landing_badges": [queue], &"actor_aggregations": [aggregate]}
|
||||
var blocked := Rect2(100,100,20,20)
|
||||
model[&"cabins"] = [{&"rect": blocked, &"badge_at": Vector2(200,200), &"badge": "EV", &"badge_rect": view._readable_badge_rect(Vector2(200,200), "EV")}]
|
||||
model[&"shaft_footprint"] = []
|
||||
var region: Dictionary = view._life_landing_region({&"bounds": blocked, &"transit": false}, Vector3i.ZERO, model)
|
||||
expect(region.get(&"no_lane", false) and region[&"badge_at"] == queue[&"at"], "lane-less stranded actor uses hidden queue anchor")
|
||||
view._resolve_observation_labels(model)
|
||||
expect(not view._readable_badge_rect(queue[&"at"], queue[&"text"]).intersection(view._readable_badge_rect(aggregate[&"at"], aggregate[&"text"])).has_area(), "hidden queue and stranded aggregate labels do not overlap")
|
||||
expect(queue[&"text"] == "待ち3人(他3人)" and aggregate[&"text"] == "+3", "complete counts remain readable")
|
||||
expect(queue[&"members"] == members and aggregate[&"members"] == identities and aggregate[&"drawn_keys"].is_empty(), "label placement preserves member identities and visibility")
|
||||
expect(queue[&"at"] == queue[&"anchor"] and aggregate[&"anchor"] == queue[&"anchor"], "uncontested queue keeps anchor and stranded anchor remains recorded")
|
||||
var solitary := {&"at": Vector2(400,400), &"text": "+1"}
|
||||
model[&"actor_aggregations"] = [solitary]
|
||||
view._resolve_observation_labels(model)
|
||||
expect(solitary[&"at"] == solitary[&"anchor"] and solitary[&"displaced_by"].is_empty(), "uncontested label stays at anchor")
|
||||
view.free()
|
||||
|
||||
func expect(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error("P23 life observation: " + message)
|
||||
|
||||
func run() -> void:
|
||||
var layout := OS.get_environment("P23_LAYOUT")
|
||||
var output_directory := OS.get_environment("P23_OUTPUT")
|
||||
if layout in ["A", "B"] and not output_directory.is_empty():
|
||||
var comparison := preload("res://tests/fixtures/p23-03/layout_comparison.gd").new()
|
||||
expect(await comparison.run(self, output_directory, layout), "native layout " + layout)
|
||||
print("P23 life observation: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
return
|
||||
var clock: Variant = root.get_node("GameClock")
|
||||
clock.set_paused(true)
|
||||
repair28_geometry()
|
||||
repair36_labels()
|
||||
# Isolated real typed components test state labels, without claiming gameplay.
|
||||
var labels: Dictionary = {}
|
||||
for state: int in FacilityInstance.State.values():
|
||||
var room := FacilityInstance.new().configure(&"component", &"reception")
|
||||
room.state = state as FacilityInstance.State
|
||||
var description := Presentation.describe(room)
|
||||
expect(description[&"state_name"] == FacilityInstance.State.keys()[state], "enum name preserved")
|
||||
var text := Presentation.business_label(description)
|
||||
expect(not text.is_empty() and not labels.has(text), "distinct visible business reason")
|
||||
labels[text] = true
|
||||
for reason: StringName in [&"RAIDED", &"DESTROYED"]:
|
||||
var room := FacilityInstance.new().configure(&"component", &"reception", {&"suspension_reason": reason})
|
||||
var description := Presentation.describe(room)
|
||||
expect(description[&"operation"] == FacilityInstance.OperationState.SUSPENDED, "actual suspension operation")
|
||||
var text := Presentation.business_label(description)
|
||||
expect(not labels.has(text), "raid and damage have separate visible labels")
|
||||
labels[text] = true
|
||||
var room := FacilityInstance.new().configure(&"component", &"reception", {&"customer_capacity": 3})
|
||||
var service := ServiceInstance.new()
|
||||
service.guest_ids.assign([&"b", &"a"])
|
||||
room.active_services[&"service"] = service
|
||||
room.queue_entries.assign([&"queue-only"])
|
||||
var description := Presentation.describe(room)
|
||||
expect(description[&"people"] == [&"a", &"b"] and description[&"count"] == 2, "queue entries are not room occupants")
|
||||
description[&"people"].clear()
|
||||
expect(service.guest_ids.size() == 2, "presentation owns its arrays")
|
||||
var runtime: Variant = root.get_node("SimulationRuntime")
|
||||
var view := WorldPreview.new()
|
||||
view.size = Vector2(1440, 810)
|
||||
view.set_runtime(runtime)
|
||||
root.add_child(view)
|
||||
# R13's four same-cell MOVING guests. Component fixture only, no Runtime edit.
|
||||
var crowd: Array = []
|
||||
var offsets := [0, 16, -16, 8]
|
||||
for i: int in 4:
|
||||
crowd.append({&"row": {&"key": "guest/os-p23-%03d" % (12+i),
|
||||
&"center": Vector2(108+offsets[i], 118-4*(i%2)),
|
||||
&"at": Vector2(84+offsets[i], 103-4*(i%2)), &"scale": 3.0,
|
||||
&"mode": &"idle", &"frame": 0, &"flip": false,
|
||||
&"state": GuestRuntime.State.MOVING, &"cell": Vector3i(1,1,0)},
|
||||
&"bounds": Rect2(80,74,56,88), &"transit": false})
|
||||
crowd.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x)
|
||||
# _life_pack uses the view's cell dimensions for the unchanged feet formula.
|
||||
# The x-capacity assertion is independent of the current default world size.
|
||||
expect(view._life_pack(crowd, 3).is_empty(), "four cores cannot fit 72px in 56px")
|
||||
var saved_size := view.size
|
||||
var saved_zoom := view.zoom_level
|
||||
var dim: Vector2 = view._cell_dimensions()
|
||||
view.zoom_level *= 64.0/dim.x
|
||||
var fitted: Array = view._life_pack(crowd, 2)
|
||||
expect(fitted.size() == 4, "all four R13 occupants fit at p=2")
|
||||
for row: Dictionary in fitted:
|
||||
expect(row[&"scale"] == 2.0 and row[&"cell"] == Vector3i(1,1,0), "fitting preserves cell and uses p=2")
|
||||
var regroup: Array = crowd.slice(0, 2).duplicate(true)
|
||||
for i: int in 2:
|
||||
regroup[i][&"row"][&"center"] = Vector2(108, 118+26*i)
|
||||
regroup[i][&"bounds"] = Rect2(80,74,20,130)
|
||||
var regrouped: Array = view._life_fitted_groups(regroup)
|
||||
expect(regrouped.size() == 2, "scale change splits vertical component")
|
||||
for group: Array in regrouped:
|
||||
expect(group.size() == 1 and group[0][&"row"][&"scale"] == 2.0, "regroup avoids stale p=1 fit")
|
||||
var saturated: Array = []
|
||||
for i: int in 10:
|
||||
var member: Dictionary = crowd[0].duplicate(true)
|
||||
member[&"row"][&"key"] = "guest/saturation-%02d" % i
|
||||
member[&"row"][&"center"] = Vector2(108,118)
|
||||
saturated.append(member)
|
||||
var saturated_groups: Array = view._life_fitted_groups(saturated)
|
||||
expect(saturated_groups.size() == 1, "saturation keeps one vertical component")
|
||||
expect(view._life_pack(saturated_groups[0], 1).is_empty(), "ten cores cannot fit 60px in 56px")
|
||||
expect(view._life_pack(saturated_groups[0].slice(0,9), 1).size() == 9, "nine cores fit without losing identities")
|
||||
var aggregate_model := {&"guests": [], &"staff": [], &"actor_drawings": [], &"cabins": []}
|
||||
var aggregate_rows: Array = []
|
||||
for member: Dictionary in saturated:
|
||||
var row: Dictionary = member[&"row"].duplicate(true)
|
||||
row[&"riding"] = true
|
||||
aggregate_rows.append(row)
|
||||
aggregate_model[&"guests"].append({&"id": String(row[&"key"]).trim_prefix("guest/"), &"cell": Vector3i(1,1,0)})
|
||||
aggregate_model[&"cabins"].append({&"rect": Rect2(76,70,64,96), &"passengers": aggregate_rows})
|
||||
view._fit_life_actors(aggregate_model)
|
||||
expect(aggregate_model[&"cabins"][0][&"passengers"].size() == 9, "aggregation draws all nine fitting members")
|
||||
var aggregate: Dictionary = aggregate_model[&"actor_aggregations"][0]
|
||||
expect(aggregate[&"members"].size() == 10 and aggregate[&"drawn_keys"].size() == 9, "aggregation retains every identity")
|
||||
expect(aggregate[&"text"] == "+1" and aggregate[&"at"] == Vector2(80,74), "actual aggregation badge position and text")
|
||||
view.zoom_level = saved_zoom
|
||||
view.size = saved_size
|
||||
var before: PackedByteArray = var_to_bytes(runtime.create_snapshot())
|
||||
var tracks: PackedByteArray = var_to_bytes([view._actor_tracks, view._elevator_tracks])
|
||||
var model := view._build_life_observation()
|
||||
expect(var_to_bytes(runtime.create_snapshot()) == before, "builder does not mutate Runtime")
|
||||
expect(var_to_bytes([view._actor_tracks, view._elevator_tracks]) == tracks, "builder does not advance visual tracks")
|
||||
expect(model.has(&"rooms") and model.has(&"landing_badges") and model.has(&"elevators") and model.has(&"staff") and model.has(&"goods") and model.has(&"badges"), "all six category fields exist")
|
||||
expect(model[&"traffic"].size() > 0 and not view.show_grid, "normal view observes real walkable traffic")
|
||||
await process_frame
|
||||
await process_frame
|
||||
var report := view.get_life_observation_report()
|
||||
expect(not report.is_empty(), "actual drawing published an observation")
|
||||
expect(report.get(&"observation_serial", -1) == report.get(&"overlay_consumed_serial", -2), "LifeOverlay consumed this observation")
|
||||
expect(report.get(&"overlay_passes", []) == [&"traffic", &"cabins", &"actors"], "traffic drawn before cabins and actors")
|
||||
var indices: Array = report.get(&"overlay_child_indices", [])
|
||||
expect(indices.size() == 3 and indices[0] < indices[1] and indices[1] < indices[2], "LifeOverlay above inactive art and below lens")
|
||||
# The getter exposes the last actual draw and must not create another model.
|
||||
var original: PackedByteArray = var_to_bytes(report)
|
||||
if report.has(&"traffic"):
|
||||
report[&"traffic"].clear()
|
||||
expect(var_to_bytes(view.get_life_observation_report()) == original, "nested report arrays are isolated")
|
||||
report.clear()
|
||||
expect(var_to_bytes(view.get_life_observation_report()) == original, "public report is a deep copy")
|
||||
var output := OS.get_environment("P23_LIFE_DIAGNOSTIC_ROOT")
|
||||
if not output.is_empty():
|
||||
var stream := Stream.new()
|
||||
expect(stream.open(output), "fresh sparse diagnostic stream")
|
||||
expect(stream.capture("initial", runtime, view), "whole typed state and draw report capture")
|
||||
stream.close()
|
||||
view.free()
|
||||
print("P23 life observation: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://d23lifeobs03
|
||||
@@ -0,0 +1,362 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only native collection. Does not run regression or claim acceptance."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
import re
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'
|
||||
CONDITIONS = ('room_occupancy_and_operation', 'queues_and_overflow',
|
||||
'elevator_state_and_passengers', 'staff_state_and_jobs',
|
||||
'supplies_and_carried_goods', 'same_demand_route_improvement',
|
||||
'normal_os_and_distinct_pid_resume')
|
||||
OS_CHILD_TIMEOUT_SECONDS = 3600
|
||||
RECORDER = 'tests/fixtures/p23-03/passive_os_recorder.gd'
|
||||
|
||||
def collect_screenshots(out, pair, role):
|
||||
"""Bind every external CUA screenshot to its request and decoded PNG.
|
||||
|
||||
The external receipt supplies screenshots[{path,raw_sha256,tool_call_id}].
|
||||
Paths are relative to the evidence root; raw receipt bytes remain unchanged.
|
||||
"""
|
||||
checker = module('p23_screenshot_binding', HERE/'check_p23_life_observation_evidence.py')
|
||||
bindings = []
|
||||
requests = json.loads((pair/(role+'-requests.json')).read_text())['requests']
|
||||
for ack_name in requests:
|
||||
ack_path = out/ack_name
|
||||
checker.require(ack_path.resolve().is_relative_to(out.resolve()), 'ACK path escape')
|
||||
ack = json.loads(ack_path.read_text())
|
||||
request_path = ack_path.with_name('request.json')
|
||||
request = json.loads(request_path.read_text())
|
||||
receipt_path = out/ack['input_receipt']
|
||||
checker.require(receipt_path.resolve().is_relative_to(out.resolve()), 'CUA receipt path escape')
|
||||
cua = json.loads(receipt_path.read_text())
|
||||
checker.require(ack['request_sha256'] == checker.sha(request_path), 'CUA request hash')
|
||||
checker.require(cua['screenshots'], 'CUA screenshots missing')
|
||||
for number, shot in enumerate(cua['screenshots']):
|
||||
raw = out/shot['path']
|
||||
checker.require(raw.resolve().is_relative_to(out.resolve()) and not raw.is_symlink(), 'screenshot path escape')
|
||||
checker.require(checker.sha(raw) == shot['raw_sha256'] and shot['tool_call_id'], 'raw screenshot CUA binding')
|
||||
kind, _, _ = checker.image_kind(raw.read_bytes())
|
||||
conversion = None
|
||||
if kind == 'jpeg':
|
||||
png = ack_path.with_name('decoded-%02d.png' % number)
|
||||
receipt = ack_path.with_name('conversion-%02d.json' % number)
|
||||
convert_screenshot(raw.resolve(), png.resolve(), shot, receipt.resolve())
|
||||
conversion = str(receipt.relative_to(out))
|
||||
else:
|
||||
png = raw
|
||||
width, height, _ = checker.png_pixels(png.read_bytes())
|
||||
bindings.append(dict(request=str(request_path.relative_to(out)), request_sha256=checker.sha(request_path),
|
||||
sequence=request['sequence'], target_pid=request['target_pid'],
|
||||
input_receipt=ack['input_receipt'], input_receipt_sha256=checker.sha(receipt_path),
|
||||
screenshot_index=number, tool_call_id=shot['tool_call_id'],
|
||||
raw_path=shot['path'], raw_sha256=shot['raw_sha256'], conversion=conversion,
|
||||
png_path=str(png.relative_to(out)), png_sha256=checker.sha(png), width=width, height=height))
|
||||
checker.require(bindings, 'empty screenshot collection')
|
||||
destination = pair/(role+'-screenshots.json')
|
||||
with destination.open('x') as output:
|
||||
json.dump(bindings, output, indent=2)
|
||||
output.write('\n')
|
||||
return str(destination.relative_to(out))
|
||||
|
||||
def convert_screenshot(raw_path, png_path, cua_receipt, receipt_path):
|
||||
"""Root-only conversion; raw CUA bytes are never renamed or rewritten."""
|
||||
checker = module('p23_conversion_checker', HERE/'check_p23_life_observation_evidence.py')
|
||||
raw, png, receipt = map(Path, (raw_path, png_path, receipt_path))
|
||||
checker.require(raw.is_absolute() and png.is_absolute() and receipt.is_absolute(),
|
||||
'absolute conversion paths required')
|
||||
checker.require(raw.is_file() and not raw.is_symlink() and not png.exists()
|
||||
and not receipt.exists() and raw.resolve() != png.resolve(), 'fresh separate conversion output')
|
||||
raw_hash = checker.sha(raw)
|
||||
checker.require(raw_hash == cua_receipt['raw_sha256'] and cua_receipt['tool_call_id'],
|
||||
'CUA conversion input hash/call mismatch')
|
||||
kind, width, height = checker.image_kind(raw.read_bytes())
|
||||
checker.require(kind == 'jpeg', 'conversion requires JPEG input')
|
||||
converter = '/usr/bin/sips'
|
||||
converter_hash = checker.sha(converter)
|
||||
argv = [converter, '-s', 'format', 'png', str(raw), '--out', str(png)]
|
||||
process = subprocess.run(argv, capture_output=True, timeout=900, check=False)
|
||||
rec = dict(raw_path=str(raw), raw_sha256=raw_hash, raw_kind=kind,
|
||||
tool_call_id=cua_receipt['tool_call_id'], converter=converter,
|
||||
converter_sha256=converter_hash, argv=argv, exit_code=process.returncode,
|
||||
stdout_sha256=hashlib.sha256(process.stdout).hexdigest(),
|
||||
stderr_sha256=hashlib.sha256(process.stderr).hexdigest(),
|
||||
png_path=str(png), png_sha256=checker.sha(png) if png.is_file() else None,
|
||||
width=width, height=height)
|
||||
# Preserve failure receipts too. These logs allow independent hash checking.
|
||||
with receipt.open('x') as output:
|
||||
json.dump(rec, output, indent=2)
|
||||
output.write('\n')
|
||||
with Path(str(receipt) + '.stdout').open('xb') as output:
|
||||
output.write(process.stdout)
|
||||
with Path(str(receipt) + '.stderr').open('xb') as output:
|
||||
output.write(process.stderr)
|
||||
checker.require(checker.sha(raw) == raw_hash, 'raw CUA bytes changed during conversion')
|
||||
checker.require(checker.sha(converter) == converter_hash, 'converter changed during conversion')
|
||||
checker.inspect_conversion(rec, cua_receipt, converter_hash)
|
||||
return rec
|
||||
|
||||
def collect_os(r, root, out, stage, engine, commit, refs, protected, outer):
|
||||
"""Adapted P08-03 collect_os protocol, retaining owned-process guards.
|
||||
|
||||
Root supplies CUA ack/receipt files in the requested directories. Neither
|
||||
controller nor passive endpoints generate input or change permissions.
|
||||
"""
|
||||
r.require(not outer.native.helper['live_engines'](), 'external engine present')
|
||||
checker = module('p23_os_pins', HERE/'check_p23_life_observation_evidence.py')
|
||||
before = r.inventory(stage, stage=True)
|
||||
public = (stage/'project.godot').read_bytes()
|
||||
(out/'os-pair').mkdir()
|
||||
phases = []
|
||||
for label in ('A-writer', 'A-reader', 'B-writer', 'B-reader'):
|
||||
layout, role = label.split('-')
|
||||
pair = out/'os-pair'/layout
|
||||
pair.mkdir(exist_ok=role == 'reader')
|
||||
r.require(not outer.native.helper['live_engines'](), 'external engine present')
|
||||
r.require(r.source_refs(root) == refs and r.default_player(root) == protected, 'OS before guards')
|
||||
r.require(r.inventory(stage, stage=True) == before, 'OS stage before guard')
|
||||
r.require(shutil.disk_usage(out).free >= 500*1024*1024, 'OS disk admission')
|
||||
folder = out/'os-runs'/label
|
||||
folder.mkdir(parents=True)
|
||||
name = 'TheTree-PCK-tests-'+uuid.uuid4().hex+'-p23-observation-'+label.lower()
|
||||
profile = r.profile_path(name)
|
||||
r.require(not profile.exists(), 'fresh OS private profile')
|
||||
formal_source = pair/'formal' if role == 'reader' else None
|
||||
formal_before = r.inventory(formal_source) if formal_source else {}
|
||||
if role == 'reader':
|
||||
r.require(set(formal_before) == {'world.bin','manifest.json','thumbnail.png','journal.log'}, 'all formal inputs')
|
||||
formal = profile/'saves/manual_ui'
|
||||
formal.mkdir(parents=True)
|
||||
for member, pin in formal_before.items():
|
||||
r.copy_source(formal_source/member, formal/member)
|
||||
r.require(r.sha(formal/member) == pin['sha256'], 'formal fixture copy SHA')
|
||||
recorder = folder/'passive_os.gd'
|
||||
r.copy_source(root/RECORDER, recorder)
|
||||
proof = folder/'teardown'
|
||||
(proof/'owned-audio').mkdir(parents=True)
|
||||
env = r.fresh_environment(dict(os.environ))
|
||||
env.update(P23_OBSERVATION_ROOT=str(pair), P23_OBSERVATION_ROLE=role, P23_LAYOUT=layout,
|
||||
THE_TREE_AUDIO_TEARDOWN_PROOF_DIR=str(proof/'owned-audio'),
|
||||
THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF=str(proof/'control.json'))
|
||||
command = [engine, '--path', str(stage), '--script', str(recorder)]
|
||||
base = {'protocol':'p23-observation-native-os-v1', 'role':role, 'layout':layout, 'commit':commit,
|
||||
'source_map':{n:v['sha256'] for n,v in refs.items()}, 'stage':str(stage),
|
||||
'stage_before':before, 'profile':str(profile), 'profile_name':name,
|
||||
'profile_before':{'existed':profile.exists(), 'files':r.inventory(profile) if profile.exists() else {}}, 'formal_input':formal_before,
|
||||
'formal_source':str(formal_source) if formal_source else None, 'owner_pid':os.getpid(),
|
||||
'default_before':protected, 'command':command, 'engine_sha256':r.sha(engine),
|
||||
'controller_sha256':r.sha(root/'tools/run_p23_life_observation.py'),
|
||||
'recorder_sha256':r.sha(recorder), 'formal_acceptance':False,
|
||||
'timeout_seconds':OS_CHILD_TIMEOUT_SECONDS,
|
||||
'inputs':checker.input_map(root), 'spec_sha256':checker.SPEC_SHA,
|
||||
'settings_sha256':hashlib.sha256(public).hexdigest(),
|
||||
'environment':{k:v for k,v in env.items() if k.startswith(('P23_', 'THE_TREE_'))},
|
||||
'environment_sha256':hashlib.sha256(json.dumps(env,sort_keys=True).encode()).hexdigest()}
|
||||
r.write(folder/'started.json', base)
|
||||
error = None; children = []; guards = []; exit_code = None; owner_proof = None
|
||||
try:
|
||||
(stage/'project.godot').write_bytes(r.private_project(public, name))
|
||||
with (folder/'engine-native.log').open('x') as log:
|
||||
process = subprocess.Popen(command, cwd=stage, env=env, stdout=log, stderr=subprocess.STDOUT)
|
||||
children.append(process)
|
||||
r.write(folder/'process-started.json', dict(base, actual_pid=process.pid,
|
||||
monotonic_ns=time.monotonic_ns(), state='waiting_for_root_cua_requests'))
|
||||
print(json.dumps({'actual_pid':process.pid, 'request_directory':str(pair),
|
||||
'state':'waiting_for_root_cua', 'role':role}), flush=True)
|
||||
exit_code = process.wait(timeout=base['timeout_seconds'])
|
||||
r.require(exit_code == 0, 'ordinary native OS exit0')
|
||||
raw = (folder/'engine-native.log').read_text()
|
||||
r.require('ERROR:' not in raw and 'SCRIPT ERROR:' not in raw, 'OS native diagnostics')
|
||||
matches = re.findall(r'(?m)^P23 passive OS observation: ([0-9]+) checks, 0 failures$', raw)
|
||||
r.require(len(matches) == 1 and int(matches[0]) > 0, 'passive observer completion')
|
||||
observed = r.read(pair/(role+'-profile.json'))
|
||||
r.require(observed['pid'] == process.pid and Path(observed['user_data_dir']).resolve() == profile.resolve()
|
||||
and observed['display'] != 'headless', 'actual OS private profile/display')
|
||||
collect_screenshots(out, pair, role)
|
||||
owners = r.module('p23_os_owner_gate', r.SUPPORT/'ordinary_proof_gate.py')
|
||||
owner_proof = owners.ordinary_gate(proof, process.pid, folder, ['single'])
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
try:
|
||||
cleanup, cleanup_errors = outer.finish_captured(children)
|
||||
finally:
|
||||
(stage/'project.godot').write_bytes(public)
|
||||
try:
|
||||
r.require(r.source_refs(root) == refs, 'OS source unchanged')
|
||||
r.require(r.inventory(stage, stage=True) == before, 'OS stage unchanged')
|
||||
r.require(r.default_player(root) == protected, 'OS default player unchanged')
|
||||
if formal_source:
|
||||
r.require(r.inventory(formal_source) == formal_before, 'formal input unchanged')
|
||||
r.require(r.sha(recorder) == base['recorder_sha256'], 'recorder unchanged')
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__+': '+str(caught))
|
||||
passed = not (error or guards or cleanup_errors) and len(children) == 1 and exit_code == 0
|
||||
r.write(folder/'ended.json', dict(base, actual_pid=children[0].pid if children else None,
|
||||
exit_code=exit_code, engine_ended=bool(cleanup) and all(v['engine_ended'] for v in cleanup),
|
||||
error=error, guard_errors=guards, cleanup_errors=cleanup_errors, owned_cleanup=cleanup,
|
||||
ordinary_owner_proof=owner_proof, stage_after=r.inventory(stage,stage=True),
|
||||
engine_sha256_after=r.sha(engine), inputs_after=checker.input_map(root),
|
||||
settings_sha256_after=hashlib.sha256((stage/'project.godot').read_bytes()).hexdigest(),
|
||||
environment_after={k:v for k,v in env.items() if k.startswith(('P23_', 'THE_TREE_'))},
|
||||
environment_sha256_after=hashlib.sha256(json.dumps(env,sort_keys=True).encode()).hexdigest(),
|
||||
default_after=r.default_player(root), source_after=r.source_refs(root),
|
||||
profile_after=r.inventory(profile), passed=passed))
|
||||
phases.append('os-runs/'+label+'/ended.json')
|
||||
r.require(passed, 'native OS collection failed: '+str(error or guards or cleanup_errors))
|
||||
return phases
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
obj = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(obj)
|
||||
return obj
|
||||
|
||||
def absolute(value):
|
||||
path = Path(value)
|
||||
if not path.is_absolute():
|
||||
raise argparse.ArgumentTypeError('absolute path required')
|
||||
return path.resolve()
|
||||
|
||||
def collect(args):
|
||||
root, out = args.project_root, args.output
|
||||
if out.is_relative_to(root):
|
||||
raise ValueError('evidence output must be outside the frozen source checkout')
|
||||
r = module('p23_standard_runner', root/'tools/run_all_tests.py')
|
||||
commit = subprocess.check_output(['git', '-C', str(root), 'rev-parse', 'HEAD'], text=True).strip()
|
||||
r.require(commit == args.commit, 'actual tested commit Q required')
|
||||
r.require(not subprocess.check_output(['git','-C',str(root),'status','--porcelain'], text=True), 'frozen clean worktree required')
|
||||
r.require(not out.exists(), 'fresh output; never overwrite earlier proof')
|
||||
refs = r.source_refs(root)
|
||||
checker = module('p23_initial_inputs', root/'tools/check_p23_life_observation_evidence.py')
|
||||
inputs_before = checker.input_map(root)
|
||||
root_roles = checker.strict_json(args.root_composition.read_bytes())
|
||||
composition = checker.verify_composition(root, root_roles,
|
||||
{name: pin['sha256'] for name, pin in refs.items()}, inputs_before)
|
||||
outer = r.module('p23_owned_outer', r.SUPPORT/'owned_outer.py')
|
||||
r.require(not outer.native.helper['live_engines'](), 'one native engine')
|
||||
engine = shutil.which(args.godot)
|
||||
r.require(engine is not None, 'native engine exists')
|
||||
engine = str(Path(engine).resolve())
|
||||
out.mkdir(parents=True)
|
||||
stage = out/'stage'; stage.mkdir()
|
||||
(out/'starters').mkdir(); (out/'bootstrap').mkdir()
|
||||
for name, pin in r.read(r.SUPPORT/'bootstrap-scripts.json').items():
|
||||
data = pin['text'].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == pin['sha256'], 'bootstrap pin')
|
||||
(out/'bootstrap'/name).write_bytes(data)
|
||||
for name, pin in refs.items():
|
||||
target = stage/name; target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(pin['path']), target)
|
||||
r.require(r.sha(target) == pin['sha256'], 'whole copied source '+name)
|
||||
expected = {n: {'bytes':v['bytes'], 'sha256':v['sha256']} for n,v in refs.items()}
|
||||
selected = {'root_before':refs, 'map':{n:v['sha256'] for n,v in refs.items()}, 'timeout':180}
|
||||
r.require(r.inventory(stage, stage=True) == expected, 'complete private source')
|
||||
protected = r.default_player(root); public = (stage/'project.godot').read_bytes()
|
||||
paths = ['res://'+n for n in sorted(refs) if n.endswith('.gd')]
|
||||
r.write(out/'parse-paths.json', paths)
|
||||
markers = r.module('p23_strict_markers', root/'tools/strict_test_markers.py')
|
||||
contracts = markers.load_contracts(root/CONTRACT)
|
||||
policy = r.read(r.SUPPORT/'suite-policy.json')
|
||||
diagnostics = r.module('p23_diagnostics', r.SUPPORT/'diagnostic_gate.py')
|
||||
owners = r.module('p23_owners', r.SUPPORT/'ordinary_proof_gate.py')
|
||||
audio = r.module('p23_audio', r.SUPPORT/'audio_evidence_gate.py')
|
||||
r.write(out/'runner-input.json', {'protocol':'p23-normal-observation-v1', 'commit':commit,
|
||||
'selected_source_map':selected['map'], 'engine':engine, 'engine_sha256':r.sha(engine),
|
||||
'owner_pid':os.getpid(), 'default_player':protected, 'diagnostic_seed':806042,
|
||||
'inputs':inputs_before, 'provenance':checker.input_provenance(root),
|
||||
'pixel_decoder':{'interpreter':sys.executable,
|
||||
'PIL_available':importlib.util.find_spec('PIL') is not None,
|
||||
'raw_screenshots':'preserve bytes; Root lossless conversion required for JPEG without PIL'}})
|
||||
phases = []; previous = {}; cases = []; error = None; os_proof = None
|
||||
deadline = time.monotonic()+900
|
||||
saved_env = {k:os.environ.get(k) for k in ('P23_LAYOUT','P23_OUTPUT')}
|
||||
def phase(label, test):
|
||||
remaining = int(deadline-time.monotonic())
|
||||
r.require(remaining >= (180 if label == 'import' else 1), '900s collection deadline')
|
||||
selected['timeout'] = min(180, remaining)
|
||||
phase_out = out/'runs'/label
|
||||
phase_out.mkdir(parents=True)
|
||||
(phase_out/'starters').mkdir(); (phase_out/'bootstrap').mkdir()
|
||||
for original in (out/'bootstrap').iterdir():
|
||||
r.copy_source(original,phase_out/'bootstrap'/original.name)
|
||||
r.require(r.sha(original)==r.sha(phase_out/'bootstrap'/original.name),'bootstrap full copy')
|
||||
r.write(phase_out/'parse-paths.json',paths)
|
||||
row = r.execute_phase(engine, stage, phase_out, label, test, selected, expected, previous,
|
||||
policy, contracts, paths, public, protected, root, outer, markers, diagnostics, owners, audio)
|
||||
# Supplement the existing runner receipt; preserve it verbatim.
|
||||
r.write(phase_out/'p23-inputs.json', dict(source_map=selected['map'], source_after=r.source_refs(root),
|
||||
engine_sha256=r.sha(engine), inputs=inputs_before, inputs_after=checker.input_map(root),
|
||||
spec_sha256=checker.SPEC_SHA, settings_sha256=hashlib.sha256(public).hexdigest(),
|
||||
stage_before=expected, stage_after=r.inventory(stage,stage=True),
|
||||
default_before=protected, default_after=r.default_player(root),
|
||||
receipt_sha256=r.sha(phase_out/(label+'-receipt.json'))))
|
||||
phases.append('runs/'+label+'/'+label+'-receipt.json')
|
||||
r.require(not row['failed'], 'strict phase failed: '+label)
|
||||
try:
|
||||
phase('import', None); phase('parse', None)
|
||||
for layout in ('A', 'B'):
|
||||
directory = out/('layout-'+layout)
|
||||
os.environ.update(P23_LAYOUT=layout, P23_OUTPUT=str(directory))
|
||||
phase('layout-'+layout.lower(), 'tests/ui/test_p23_life_observation.gd')
|
||||
cases.append({'directory':directory.name, 'phase':phases[-1], 'files':r.inventory(directory)})
|
||||
for layout in ('A', 'B'):
|
||||
checker.inspect_stream(out/('layout-'+layout), root)
|
||||
if args.input_mode == 'native_os':
|
||||
os_phases = collect_os(r, root, out, stage, engine, commit, refs, protected, outer)
|
||||
checker = module('p23_os_input_map', root/'tools/check_p23_life_observation_evidence.py')
|
||||
os_proof = 'os-proof.json'
|
||||
r.write(out/os_proof, {'source_map':selected['map'], 'inputs':checker.input_map(root),
|
||||
'input_method':'native_os', 'observer_mutations':[], 'phases':os_phases, 'directory':'os-pair'})
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
for key,value in saved_env.items():
|
||||
if value is None: os.environ.pop(key, None)
|
||||
else: os.environ[key] = value
|
||||
if r.source_refs(root) != refs or r.default_player(root) != protected:
|
||||
error = (error+'; ' if error else '')+'protected root/profile changed'
|
||||
if checker.input_map(root) != inputs_before:
|
||||
error = (error+'; ' if error else '')+'spec/fixture/controller inputs changed'
|
||||
r.write(out/'runner-ended.json', {'error':error, 'cleanup':cleanup, 'cleanup_errors':cleanup_errors,
|
||||
'phases':phases, 'formal_acceptance':False, 'native_Windows':'pending'})
|
||||
checker = module('p23_observation_checker', root/'tools/check_p23_life_observation_evidence.py')
|
||||
r.write(out/'index.json', {'schema':'p23-life-observation-v2', 'commit':commit,
|
||||
'composition':composition,
|
||||
'source_map':selected['map'], 'input_map':inputs_before,
|
||||
'provenance':checker.input_provenance(root),
|
||||
'marker_contract_sha256':r.sha(root/CONTRACT),
|
||||
'spec_sha256':hashlib.sha256((root/'docs/spec/acceptance/P23-03.json').read_text().strip().encode()).hexdigest(),
|
||||
'cases':cases, 'phases':phases, 'conditions':list(CONDITIONS),
|
||||
'artifacts':{str(p.relative_to(out)):r.sha(p)
|
||||
for p in out.rglob('*') if p.is_file() and 'stage' not in p.relative_to(out).parts},
|
||||
'os_proof':os_proof, 'regression':None,
|
||||
'acceptance':'pending', 'collection_error':error})
|
||||
return 1 if error or cleanup_errors else 0
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--project-root', type=absolute, required=True)
|
||||
parser.add_argument('--output', type=absolute, required=True)
|
||||
parser.add_argument('--commit', required=True)
|
||||
parser.add_argument('--root-composition', type=absolute, required=True,
|
||||
help='Root external S/M/T/Q and complete tested-tree blob bindings')
|
||||
parser.add_argument('--godot', default='godot')
|
||||
parser.add_argument('--input-mode', choices=('handler', 'native_os'), default='handler')
|
||||
parser.add_argument('--collect', action='store_true', required=True)
|
||||
return collect(parser.parse_args(argv))
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||