P23-03: 生活の観察
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"action": "request-repair",
|
||||||
|
"task_id": "P23-03",
|
||||||
|
"reason": "Same-task repair for P23-03. Keep submitted 336a8cd and all earlier work, and keep every failure record (R10 B: 484 checks / 11 failures; validate 4958d8f55b6b458694bf158769ce3bff). Root cause, confirmed in the code: in tests/fixtures/p23-03/observation.gd, prepare() at lines 48-77 adds up only the registered construction_cost of the builds, including B's EV, to set a common diagnostic budget. Lines 93-99 then hire 5 candidates (staff_akane/gina/chika/iris/juno) and buy 40 RAW_FOOD without including either cost in that budget. Construction uses up all of B's cash, so all 5 hires fail with VALIDATION_FAILED (available 0) and the purchase fails with INSUFFICIENT_FUNDS. A succeeds. This is an omission in the fixture's budget, not a gap in production values.\n\nRepair, inside allowed paths only (tests/fixtures/p23-03/**, tools/check_p23_life_observation_evidence.py, tools/test_p23_life_observation_evidence.py, docs/plans/.../execution/p23-03/** metadata):\n(1) In prepare(), raise the common A/B diagnostic `needed` by:\n- the actual signing_cost of each of the 5 candidates, read at runtime from the registered candidate definitions (the catalog's signing_cost field, scripts/data/mvp_content_catalog.gd:323);\n- the gold cost of buying 40 RAW_FOOD, taken from the existing quote path (SupplyCatalog unit_price via PurchaseQuoteV2.make / the supply_management_service quote). Use effective_total if the actual quote applies a modifier.\nRead these values from the runtime registry/quote at the time they are needed. Do not hard-code numbers, add a margin, change default resources, or change product/runtime data. Record each cost source (definition/candidate/item id with its value), the totals, and the resulting deltas in the budget record. A and B must get the same deltas. The improvement EV cost stays in both budgets, as it is now.\n(2) Have the fixture check the receipt of each setup command (build, set_facility_conditions, hire, assignment, purchase, kitchen production) and expect() it to succeed, so a failed setup step shows up as a named failure and is not silently skipped.\n(3) Keep everything else the same: the common A/B inputs, the declared demand/arrival cohort, the native comparison's 48-demand, the 7 conditions and 6 categories, staff/goods coverage, normal OS plus distinct-PID load, full177, the independent Opus review, integration/final, 30/88 with no partial credit, the OS child timeout of 3600, worker/native 900 and the null cap.\n(4) Add a cheap local Python check, in the checker or its test, that statically or via the recorded budget confirms every planned spend (construction, signing, purchase) is counted in needed/deltas. Include a negative test for a missing spend category, so this mistake is caught before an expensive OS replay.\n(5) Update the p23-03 execution metadata to describe the finding and the repair honestly.\n\nThe worker runs only Python syntax checks and the checker's local tests, then makes a genuine submit. No Godot, native, OS or full regression runs, and Root evidence is expected to be missing at submission. Afterwards Root runs the exact final proof on the frozen source, reusing only evidence whose applicability matches. No new UI, product/runtime changes, broad hardening, billing or permission changes.",
|
||||||
|
"codex_model": "gpt-6.1-sol",
|
||||||
|
"codex_model_reason": "The repair is small and well understood: one fixture function needs signing and purchase costs added from existing catalog fields and the quote API, plus receipt assertions and a local Python preflight with a negative test. The root cause is already confirmed and nothing here needs new design or deep cross-system reasoning, so the default Sol is enough on quality and cheaper and faster than Astra."
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"action": "run",
|
||||||
|
"task_id": "P23-03",
|
||||||
|
"reason": "The Runner shows P23-03 in repair_ready with 19 repairs, and the owner has dispatched the recorded R10 budget repair decision. run is a valid action and it is the next step, so this is not a repeat of a completed transition. The run keeps the R10 direction exactly as narrow as recorded and starts from the clean submitted base 336a8cd0b30db1add11869ebceab847db693f531. The worker stays inside the task contract's allowed_paths. It may only run local Python checks: syntax, the checker, and the checker's negative tests. It does not run Godot, native, OS, or full regression; Root runs those only after a genuine submission. The worker must say that Root evidence is missing at submission rather than mark anything PASS. Root's capacity recovery runs separately and must finish before proof replay; Root does not edit source. This run does not change the spec, the acceptance denominator, the budget or caps, or approval, and it does not touch main integration. Independent Opus review and Root validation are still required before acceptance.",
|
||||||
|
"codex_model": "gpt-6-astra",
|
||||||
|
"codex_model_reason": "I chose gpt-6-astra over the default because the difficulty justifies it. (1) The frozen spec's worker_instructions name one Codex Astra worker, and the recorded commander direction (run 83e836e2...) set codex_model gpt-6-astra. Changing the model during a narrow repair would break the recorded direction. (2) There have been 19 repair cycles, which shows the remaining defects are subtle consistency problems across the observation report, the strict checker that recomputes fields, the evidence manifests, and the same-demand layout A/B budget and seed setup. These need careful reasoning across files more than speed. (3) The worker only runs local Python, so per-run usage is bounded. A stronger model that gets it right in one pass should cost less overall than more failed cycles on the default model."
|
||||||
|
}
|
||||||
+45
@@ -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.
|
||||||
+335
@@ -0,0 +1,335 @@
|
|||||||
|
{
|
||||||
|
"candidate": "336a8cd0b30db1add11869ebceab847db693f531",
|
||||||
|
"r10_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-03-native-os-atomic-336a8cd-r10",
|
||||||
|
"terminal_error": "AssertionError: native OS collection failed: AssertionError: ordinary native OS exit0",
|
||||||
|
"cleanup_errors": [],
|
||||||
|
"A_writer_pid": 56892,
|
||||||
|
"A_reader_pid": 60176,
|
||||||
|
"B_writer_pid": 61335,
|
||||||
|
"B_reader": "not run; B writer failed",
|
||||||
|
"all_conditions_accepted": false,
|
||||||
|
"formal_credit": 0,
|
||||||
|
"confirmed_failure": "B construction-only diagnostic budget leaves zero cash; 5 hires and raw-food purchase rejected. Do not change production values or weaken conditions.",
|
||||||
|
"actual_command_objects": {
|
||||||
|
"A": [
|
||||||
|
{
|
||||||
|
"object_sha256": "0d360907ad9289b7db5bc759a220cf274184f18cff3fe98c912941fed22fb1a9",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_akane"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "OK",
|
||||||
|
"command_id": 24,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"candidate_id": "staff_akane",
|
||||||
|
"staff_id": "staff_akane",
|
||||||
|
"role_id": "receptionist",
|
||||||
|
"daily_wage": 120,
|
||||||
|
"employment_kind": "FREE_HIRE",
|
||||||
|
"signing_cost": 180,
|
||||||
|
"shift_id": "shift_00000001_d00000",
|
||||||
|
"player_cash": 1320
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "6dfd11522dee4b5ac9e1eefad4c2a27f01265cd46f1bb3e364665a85cd78769a",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_gina"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "OK",
|
||||||
|
"command_id": 26,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"candidate_id": "staff_gina",
|
||||||
|
"staff_id": "staff_gina",
|
||||||
|
"role_id": "porter",
|
||||||
|
"daily_wage": 110,
|
||||||
|
"employment_kind": "FREE_HIRE",
|
||||||
|
"signing_cost": 150,
|
||||||
|
"shift_id": "shift_00000003_d00000",
|
||||||
|
"player_cash": 1170
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "e87798f0030e97a630c76ed3ad9ecdc4e032a090efc5fbb4a39ac606857828dd",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_chika"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "OK",
|
||||||
|
"command_id": 28,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"candidate_id": "staff_chika",
|
||||||
|
"staff_id": "staff_chika",
|
||||||
|
"role_id": "cook",
|
||||||
|
"daily_wage": 145,
|
||||||
|
"employment_kind": "FREE_HIRE",
|
||||||
|
"signing_cost": 250,
|
||||||
|
"shift_id": "shift_00000005_d00000",
|
||||||
|
"player_cash": 920
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "00c167c28d271fbe133642650ab17b1240e73abd1d5fbb1bcea683f62e603c26",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_iris"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "OK",
|
||||||
|
"command_id": 30,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"candidate_id": "staff_iris",
|
||||||
|
"staff_id": "staff_iris",
|
||||||
|
"role_id": "attendant",
|
||||||
|
"daily_wage": 130,
|
||||||
|
"employment_kind": "FREE_HIRE",
|
||||||
|
"signing_cost": 210,
|
||||||
|
"shift_id": "shift_00000007_d00000",
|
||||||
|
"player_cash": 710
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "969d0116e1bcc57e28019273e401ff1bbed54ca5ab22455d677889e2375f45f5",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_juno"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "OK",
|
||||||
|
"command_id": 32,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"candidate_id": "staff_juno",
|
||||||
|
"staff_id": "staff_juno",
|
||||||
|
"role_id": "attendant",
|
||||||
|
"daily_wage": 140,
|
||||||
|
"employment_kind": "FREE_HIRE",
|
||||||
|
"signing_cost": 240,
|
||||||
|
"shift_id": "shift_00000009_d00000",
|
||||||
|
"player_cash": 470
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "092af99c4a2f84786641430e6c1cebce62f813b69dbac5d5ac397a3017fe6a84",
|
||||||
|
"decoded": {
|
||||||
|
"command": "purchase_supplies",
|
||||||
|
"payload": {
|
||||||
|
"warehouse_id": "facility_00000004",
|
||||||
|
"item_id": "ingredient/raw_food",
|
||||||
|
"quantity": 40
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "OK",
|
||||||
|
"command_id": 34,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"ok": true,
|
||||||
|
"reason": "",
|
||||||
|
"total_cost": 120,
|
||||||
|
"cash_after": 350,
|
||||||
|
"quantity_after": 40,
|
||||||
|
"max_quantity": 100,
|
||||||
|
"warehouse_id": "facility_00000004",
|
||||||
|
"item_id": "ingredient/raw_food",
|
||||||
|
"quantity": 40,
|
||||||
|
"transaction_id": "ledger_00000060"
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"B": [
|
||||||
|
{
|
||||||
|
"object_sha256": "bcb27db495ae9e94a20578e1aadcb64e0fa316bb1d4a6e954e581f4bdb34b296",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_akane"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "VALIDATION_FAILED",
|
||||||
|
"command_id": 25,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "An account does not permit a negative balance.",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"account_id": "PLAYER_CASH",
|
||||||
|
"available": 0,
|
||||||
|
"resulting_balance": -180
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "81f9484c3fcce64e084a7e2ead8bf84dd8c56e144860624845eccb8c4218417a",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_gina"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "VALIDATION_FAILED",
|
||||||
|
"command_id": 27,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "An account does not permit a negative balance.",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"account_id": "PLAYER_CASH",
|
||||||
|
"available": 0,
|
||||||
|
"resulting_balance": -150
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "16f4a094baa4204d4e254b6d52d824f32aa33e914e6bc1698aa933a8fa51207a",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_chika"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "VALIDATION_FAILED",
|
||||||
|
"command_id": 29,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "An account does not permit a negative balance.",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"account_id": "PLAYER_CASH",
|
||||||
|
"available": 0,
|
||||||
|
"resulting_balance": -250
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "303e545649bf1c84c736293a6e1b54491befbc376b9a051d3f3c75659e2b147e",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_iris"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "VALIDATION_FAILED",
|
||||||
|
"command_id": 31,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "An account does not permit a negative balance.",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"account_id": "PLAYER_CASH",
|
||||||
|
"available": 0,
|
||||||
|
"resulting_balance": -210
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "de1717d64250c9ae721fc38230d185b0621a066e01890f9fb4020b4c7e36e7c0",
|
||||||
|
"decoded": {
|
||||||
|
"command": "hire_staff_candidate",
|
||||||
|
"payload": {
|
||||||
|
"candidate_id": "staff_juno"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "VALIDATION_FAILED",
|
||||||
|
"command_id": 33,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "An account does not permit a negative balance.",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"account_id": "PLAYER_CASH",
|
||||||
|
"available": 0,
|
||||||
|
"resulting_balance": -240
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object_sha256": "c257c14418959e558b88d4f47591c14726fff4763a30177275d8725cb6639b9f",
|
||||||
|
"decoded": {
|
||||||
|
"command": "purchase_supplies",
|
||||||
|
"payload": {
|
||||||
|
"warehouse_id": "facility_00000004",
|
||||||
|
"item_id": "ingredient/raw_food",
|
||||||
|
"quantity": 40
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"code": "VALIDATION_FAILED",
|
||||||
|
"command_id": 35,
|
||||||
|
"target_id": "",
|
||||||
|
"message": "Supply command rejected: INSUFFICIENT_FUNDS",
|
||||||
|
"resolution_hints": [],
|
||||||
|
"details": {
|
||||||
|
"ok": false,
|
||||||
|
"reason": "INSUFFICIENT_FUNDS",
|
||||||
|
"total_cost": 120,
|
||||||
|
"cash_after": -120,
|
||||||
|
"quantity_after": 40,
|
||||||
|
"max_quantity": 0
|
||||||
|
},
|
||||||
|
"is_replay": false
|
||||||
|
},
|
||||||
|
"minute": 910
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"next": "Actual Opus diagnosis and narrow same-task repair before full regression"
|
||||||
|
}
|
||||||
Vendored
+35
-6
@@ -2,6 +2,7 @@ extends "res://tests/fixtures/p08-03/placement.gd"
|
|||||||
## Real Runtime commands; inherited formal codec/save-operation helpers unchanged.
|
## Real Runtime commands; inherited formal codec/save-operation helpers unchanged.
|
||||||
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
||||||
const Comparison := preload("res://tests/fixtures/p23-03/layout_comparison.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 stream: RefCounted
|
||||||
var layout := "A"
|
var layout := "A"
|
||||||
var facility_ids: Dictionary = {}
|
var facility_ids: Dictionary = {}
|
||||||
@@ -28,7 +29,7 @@ func setup() -> void:
|
|||||||
|
|
||||||
func command(id: StringName, payload: Dictionary) -> bool:
|
func command(id: StringName, payload: Dictionary) -> bool:
|
||||||
var result: CommandResult = runtime.submit_command(id, payload)
|
var result: CommandResult = runtime.submit_command(id, payload)
|
||||||
expect(result.is_success(), String(id) + ": " + str(result.code))
|
expect(result.is_success(), String(id) + " " + str(payload) + ": " + str(result.code))
|
||||||
expect(stream.record("command", {&"command": id, &"payload": payload,
|
expect(stream.record("command", {&"command": id, &"payload": payload,
|
||||||
&"result": result.to_dictionary(), &"minute": clock.absolute_minute}), "record public command")
|
&"result": result.to_dictionary(), &"minute": clock.absolute_minute}), "record public command")
|
||||||
return result.is_success()
|
return result.is_success()
|
||||||
@@ -46,7 +47,7 @@ func observe_progress() -> void:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
func prepare() -> void:
|
func prepare() -> void:
|
||||||
# Both layouts receive the same definition-derived diagnostic construction budget.
|
# 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.
|
# Cafeteria/kitchen move together to x=6, leaving x=4..5 for the declared B EV.
|
||||||
var builds: Array = [[&"reception", Vector3i(-8, 0, 0)],
|
var builds: Array = [[&"reception", Vector3i(-8, 0, 0)],
|
||||||
[&"cafeteria", Vector3i(6, 0, 0)], [&"warehouse", Vector3i(-8, 0, 1)],
|
[&"cafeteria", Vector3i(6, 0, 0)], [&"warehouse", Vector3i(-8, 0, 1)],
|
||||||
@@ -59,6 +60,9 @@ func prepare() -> void:
|
|||||||
var improvement: Dictionary = Comparison.new().declaration()[&"improvement"]
|
var improvement: Dictionary = Comparison.new().declaration()[&"improvement"]
|
||||||
var budget_builds := builds.duplicate(true)
|
var budget_builds := builds.duplicate(true)
|
||||||
budget_builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
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 needed := {&"gold": 0, &"materials": 0}
|
||||||
var costs: Array = []
|
var costs: Array = []
|
||||||
for row: Array in budget_builds:
|
for row: Array in budget_builds:
|
||||||
@@ -66,13 +70,36 @@ func prepare() -> void:
|
|||||||
costs.append({&"definition": row[0], &"cost": definition.construction_cost.duplicate(true)})
|
costs.append({&"definition": row[0], &"cost": definition.construction_cost.duplicate(true)})
|
||||||
for key: StringName in needed:
|
for key: StringName in needed:
|
||||||
needed[key] += int(definition.construction_cost.get(key, 0))
|
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 state: Variant = tree.root.get_node("GameState")
|
||||||
var deltas := {}
|
var deltas := {}
|
||||||
for key: StringName in needed:
|
for key: StringName in needed:
|
||||||
if needed[key] > state.get_resource(key):
|
if needed[key] > state.get_resource(key):
|
||||||
deltas[key] = needed[key] - state.get_resource(key)
|
deltas[key] = needed[key] - state.get_resource(key)
|
||||||
budget = {"setup": "diagnostic", "costs": costs, "needed": needed,
|
budget = {"setup": "diagnostic", "costs": costs, "needed": needed,
|
||||||
"initial": state.resources.duplicate(true), "deltas": deltas}
|
"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})
|
if not deltas.is_empty(): command(&"change_resources", {&"deltas": deltas})
|
||||||
expect(stream.record("budget", budget), "common diagnostic budget")
|
expect(stream.record("budget", budget), "common diagnostic budget")
|
||||||
if layout == "B": builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
if layout == "B": builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
||||||
@@ -90,13 +117,15 @@ func prepare() -> void:
|
|||||||
facility_ids[facility.definition_id] = facility.instance_id
|
facility_ids[facility.definition_id] = facility.instance_id
|
||||||
command(&"set_facility_conditions", {&"facility_id": facility_ids[&"cafeteria"],
|
command(&"set_facility_conditions", {&"facility_id": facility_ids[&"cafeteria"],
|
||||||
&"conditions": {&"related_facility_id": facility_ids[&"kitchen"]}})
|
&"conditions": {&"related_facility_id": facility_ids[&"kitchen"]}})
|
||||||
for assignment: Array in [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"],
|
for assignment: Array in assignments:
|
||||||
[&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]:
|
|
||||||
command(&"hire_staff_candidate", {&"candidate_id": assignment[0]})
|
command(&"hire_staff_candidate", {&"candidate_id": assignment[0]})
|
||||||
command(&"set_staff_work_assignment", {&"staff_id": assignment[0],
|
command(&"set_staff_work_assignment", {&"staff_id": assignment[0],
|
||||||
&"facility_id": facility_ids[assignment[1]], &"template_id": &"DAY"})
|
&"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"],
|
command(&"purchase_supplies", {&"warehouse_id": facility_ids[&"warehouse"],
|
||||||
&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 40})
|
&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": purchase_quantity})
|
||||||
command(&"set_kitchen_production", {&"facility_id": facility_ids[&"kitchen"],
|
command(&"set_kitchen_production", {&"facility_id": facility_ids[&"kitchen"],
|
||||||
&"enabled": true, &"target_stock": 8})
|
&"enabled": true, &"target_stock": 8})
|
||||||
# The OS observation uses the already declared transport cohort, plus
|
# The OS observation uses the already declared transport cohort, plus
|
||||||
|
|||||||
@@ -71,6 +71,35 @@ def require(value, message):
|
|||||||
raise ValueError(message)
|
raise ValueError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def check_setup_budget_source(source):
|
||||||
|
"""Cheap preflight for the fixture's shared spend plan, before OS replay."""
|
||||||
|
prepare = source.split('func prepare() -> void:', 1)[1].split('\nfunc ', 1)[0]
|
||||||
|
categories = {
|
||||||
|
'construction': ('budget_builds := builds.duplicate(true)',
|
||||||
|
'budget_builds.append([improvement[',
|
||||||
|
'for row: Array in budget_builds:',
|
||||||
|
'needed[key] += int(definition.construction_cost.get(key, 0))'),
|
||||||
|
'signing': ('for assignment: Array in assignments:',
|
||||||
|
'runtime.mvp_content_catalog.get_recruitment_entry(assignment[0])',
|
||||||
|
'signing_total += signing_cost', 'needed[&"gold"] += signing_total'),
|
||||||
|
'purchase': ('SupplyCatalog.get_item(SupplyCatalog.RAW_FOOD)',
|
||||||
|
'PurchaseQuoteV2.make(SupplyCatalog.RAW_FOOD, purchase_quantity,',
|
||||||
|
'PurchaseQuoteV2.eligible(runtime)', 'purchase_quote[&"effective_total"]',
|
||||||
|
'needed[&"gold"] += purchase_total'),
|
||||||
|
}
|
||||||
|
budget_end = prepare.index('\n\tvar state:')
|
||||||
|
for category, expressions in categories.items():
|
||||||
|
require(all(expression in prepare[:budget_end] for expression in expressions),
|
||||||
|
'setup budget missing spend category: ' + category)
|
||||||
|
require(prepare.count('for assignment: Array in assignments:') == 2,
|
||||||
|
'budget and hire must share assignments')
|
||||||
|
require('&"quantity": purchase_quantity}' in prepare,
|
||||||
|
'budget and purchase must share quantity')
|
||||||
|
require('deltas[key] = needed[key] - state.get_resource(key)' in prepare,
|
||||||
|
'setup budget deltas must cover needed')
|
||||||
|
return set(categories)
|
||||||
|
|
||||||
|
|
||||||
def sha(path):
|
def sha(path):
|
||||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||||
|
|
||||||
@@ -1597,6 +1626,7 @@ def main(argv=None):
|
|||||||
parser.add_argument('--layout-a', type=Path)
|
parser.add_argument('--layout-a', type=Path)
|
||||||
parser.add_argument('--layout-b', type=Path)
|
parser.add_argument('--layout-b', type=Path)
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
check_setup_budget_source((args.project_root / 'tests/fixtures/p23-03/observation.gd').read_text())
|
||||||
if args.offline_mirror:
|
if args.offline_mirror:
|
||||||
require(not any((args.verify_only, args.diagnostics, args.layout_a, args.layout_b)),
|
require(not any((args.verify_only, args.diagnostics, args.layout_a, args.layout_b)),
|
||||||
'offline preparation must not be combined with acceptance checks')
|
'offline preparation must not be combined with acceptance checks')
|
||||||
|
|||||||
@@ -18,6 +18,30 @@ sys.dont_write_bytecode = True
|
|||||||
import check_p23_life_observation_evidence as check
|
import check_p23_life_observation_evidence as check
|
||||||
|
|
||||||
|
|
||||||
|
class SetupBudgetTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.source = (check.ROOT / 'tests/fixtures/p23-03/observation.gd').read_text()
|
||||||
|
|
||||||
|
def test_all_planned_spends_enter_common_needed_and_deltas(self):
|
||||||
|
self.assertEqual(check.check_setup_budget_source(self.source),
|
||||||
|
{'construction', 'signing', 'purchase'})
|
||||||
|
|
||||||
|
def test_missing_spend_category_fails_before_os_replay(self):
|
||||||
|
expressions = {
|
||||||
|
'construction': 'needed[key] += int(definition.construction_cost.get(key, 0))',
|
||||||
|
'signing': 'needed[&"gold"] += signing_total',
|
||||||
|
'purchase': 'needed[&"gold"] += purchase_total',
|
||||||
|
}
|
||||||
|
for category, expression in expressions.items():
|
||||||
|
with self.subTest(category=category), self.assertRaisesRegex(ValueError, category):
|
||||||
|
check.check_setup_budget_source(self.source.replace(expression, 'pass'))
|
||||||
|
|
||||||
|
def test_missing_delta_coverage_fails(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'deltas'):
|
||||||
|
check.check_setup_budget_source(self.source.replace(
|
||||||
|
'deltas[key] = needed[key] - state.get_resource(key)', 'pass'))
|
||||||
|
|
||||||
|
|
||||||
class QueuedElevatorTests(unittest.TestCase):
|
class QueuedElevatorTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/queued_elevator.json').read_text())
|
fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/queued_elevator.json').read_text())
|
||||||
|
|||||||
Reference in New Issue
Block a user