Merge commit 'd2581afd26d1976cdfe4f44c89df49a772524d1e' into HEAD
This commit is contained in:
+165
@@ -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
|
||||
+102
@@ -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
|
||||
Vendored
+193
@@ -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()
|
||||
+1
@@ -0,0 +1 @@
|
||||
uid://dmoi8je5hse38
|
||||
+266
@@ -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
|
||||
+27
@@ -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"
|
||||
}]
|
||||
}
|
||||
}
|
||||
+15
@@ -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]}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
+2011
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
Reference in New Issue
Block a user