P23-03: 生活の観察

This commit is contained in:
Agent Control Runner
2026-10-15 12:08:24 +09:00
parent 336a8cd0b3
commit 49445d4a4d
8 changed files with 528 additions and 6 deletions
@@ -71,6 +71,35 @@ def require(value, 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):
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-b', type=Path)
args = parser.parse_args(argv)
check_setup_budget_source((args.project_root / 'tests/fixtures/p23-03/observation.gd').read_text())
if args.offline_mirror:
require(not any((args.verify_only, args.diagnostics, args.layout_a, args.layout_b)),
'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
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):
def setUp(self):
fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/queued_elevator.json').read_text())