Files
TheTree_mock/docs/plans/original-completion-2026-10-02/build_facility_value_contracts.py
T
Takeshi b84bade045 Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
2026-10-11 15:58:00 +09:00

523 lines
42 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Static, source-anchored P09 audit. Never loads Godot or edits production files."""
from __future__ import annotations
import ast
import hashlib
import json
import re
import subprocess
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
OUT = Path(__file__).resolve().parent
BASE = "docs/traceability/original/BASELINE.txt"
ANNEX = "artifacts/original-annex-A-drive-retrieval-2026-09-30-r01/retrieved-readable-annex-A.md"
DECISIONS = "docs/plans/original-completion-2026-10-02/decisions.json"
ADDITION = "docs/traceability/original/audit-2026-09-29/TRACEABILITY.md"
PLAN = "docs/plans/original-completion-2026-10-02/execution/IMPLEMENTATION_PLAN.initial.md"
DEF = "scripts/data/facility_definition.gd"
CATALOG = "scripts/data/facility_catalog.gd"
INSTANCE = "scripts/facility/facility_instance.gd"
SERVICE = "scripts/facility/facility_service.gd"
PRICING = "scripts/facility/facility_pricing.gd"
RUNTIME = "autoload/simulation_runtime.gd"
UPKEEP = "docs/plans/original-completion-2026-09-29/execution/UPKEEP_IMPLEMENTATION.md"
PINS = {}
def pin(path):
if path not in PINS:
PINS[path] = hashlib.sha256((ROOT / path).read_bytes()).hexdigest()
return PINS[path]
def anchor(path, lines, authority):
return {"path": path, "lines": lines, "sha256": pin(path), "authority": authority}
def findline(path, literal):
matches = [i for i, line in enumerate((ROOT / path).read_text().splitlines(), 1) if literal in line]
assert matches, (path, literal)
return matches[0]
def literal(raw):
"""Decode only data literals used by the retained .tres files; no eval."""
raw = raw.strip()
enum = {"MapZone.ANY": 0, "ViewLayer.FRONT": 0, "Vector2i.ONE": [1, 1], "LEGACY_SCHEMA_VERSION": 1}
if raw in enum:
return enum[raw]
wrapped = re.fullmatch(r"Array\[[^]]+\]\((.*)\)", raw)
if wrapped:
raw = wrapped[1]
raw = raw.replace('&"', '"')
raw = re.sub(r"Vector2i\(([-\d]+),\s*([-\d]+)\)", r"[\1, \2]", raw)
raw = re.sub(r"\btrue\b", "True", raw)
raw = re.sub(r"\bfalse\b", "False", raw)
raw = re.sub(r"\bnull\b", "None", raw)
return ast.literal_eval(raw)
DEFAULTS = {}
EXPORTED_KEYS = set()
for n, line in enumerate((ROOT / DEF).read_text().splitlines(), 1):
name = re.match(r"@export(?:_range\([^)]*\)|_multiline)?\s+var\s+(\w+)", line)
if name:
EXPORTED_KEYS.add(name[1])
m = re.match(r"@export(?:_range\([^)]*\)|_multiline)?\s+var\s+(\w+)(?::[^=]+)?\s*:?=\s*(.+)$", line)
if m:
DEFAULTS[m[1]] = {"raw": m[2], "value": literal(m[2]), "line": n}
DEFINITIONS = {}
for p in sorted((ROOT / "data/facilities").rglob("*.tres")):
rel = str(p.relative_to(ROOT))
raw_fields = {}
duplicates = []
for n, line in enumerate(p.read_text().splitlines(), 1):
m = re.match(r"(\w+) = (.+)$", line)
if not m or m[1] == "script":
continue
if m[1] in raw_fields:
duplicates.append({"field": m[1], "first_line": raw_fields[m[1]]["line"], "duplicate_line": n,
"same_raw_value": raw_fields[m[1]]["raw"] == m[2]})
raw_fields[m[1]] = {"raw": m[2], "value": literal(m[2]), "line": n}
DEFINITIONS[raw_fields["id"]["value"]] = {"path": rel, "fields": raw_fields, "duplicate_assignments": duplicates,
"unknown_export_fields": [key for key in raw_fields if key not in EXPORTED_KEYS]}
pin(rel)
catalog_text = (ROOT / CATALOG).read_text()
NORMAL_PATHS = set()
for key in ["FACILITY_PATHS", "INFRASTRUCTURE_FACILITY_PATHS", "RESCUE_FACILITY_PATHS", "ORIGINAL_FACILITY_PATHS"]:
chunk = re.search(r"const " + key + r": Array\[String\] = \[(.*?)\]", catalog_text, re.S)[1]
NORMAL_PATHS.update(re.findall(r'"res://([^"]+)"', chunk))
# Annex rows explicitly describe prior implementation, not original/individual approval.
ANNEX_ROWS = {}
ANNEX_CARDS = {}
annex_id = None
for n, line in enumerate((ROOT / ANNEX).read_text().splitlines(), 1):
m = re.match(r"### .*(([a-z_]+))", line)
if m:
annex_id = m[1]
ANNEX_CARDS[annex_id] = n
ANNEX_ROWS[annex_id] = {}
cells = [c.strip() for c in line.strip().strip("|").split("|")]
if annex_id and line.startswith("|") and len(cells) == 4 and re.fullmatch(r"`\w+`", cells[1]):
ANNEX_ROWS[annex_id][cells[1].strip("`")] = {"reported_value": cells[2], "reported_origin": cells[3],
"source": anchor(ANNEX, [n], "retained_legacy_implementation_reference")}
def current(fid, field):
if fid is None or fid not in DEFINITIONS:
return {"value": None, "raw": None, "origin": "no_definition", "source": None}
d = DEFINITIONS[fid]
if field in d["fields"]:
f = d["fields"][field]
return {"value": f["value"], "raw": f["raw"], "origin": "explicit_tres",
"source": anchor(d["path"], [f["line"]], "current_implementation_only")}
if field in DEFAULTS:
f = DEFAULTS[field]
return {"value": f["value"], "raw": f["raw"], "origin": "inherited_class_default",
"source": anchor(DEF, [f["line"]], "current_implementation_only")}
return {"value": None, "raw": None, "origin": "not_a_definition_field", "source": None}
decision_data = json.loads((ROOT / DECISIONS).read_text())
APPROVALS = {d["id"]: {**d, "source": anchor(DECISIONS, list(range(findline(DECISIONS, '"id": "' + d["id"] + '"'), findline(DECISIONS, '"id": "' + d["id"] + '"') + 3)),
"confirmed_user_decision")} for d in decision_data["confirmed"]}
APPROVED_VALUES = {
"chapel": {"footprint": [4, 3], "layer": 0, "construction_cost": {"gold": 1200, "materials": 12},
"build_minutes": 180, "customer_capacity": 8, "staff_min": 1, "staff_max": 2,
"required_roles": ["cleric"], "base_service_minutes": 60, "base_price": 20,
"faith_per_service": 1, "maintenance_gold": 6},
"cleaning_room": {"footprint": [2, 1], "layer": 1, "construction_cost": {"gold": 350, "materials": 4},
"build_minutes": 60, "customer_capacity": 0, "staff_min": 1, "staff_max": 2,
"required_roles": ["cleaner"], "base_price": 0, "maintenance_gold": 2},
}
F_FIELDS = {
"F01": ["construction_cost", "build_minutes"],
"F02": ["maintenance_gold", "maintenance_mana"],
"F03": ["base_price"],
"F04": ["footprint", "layer", "zone"],
"F05": ["required_roles", "primary_skill", "staff_min", "service_types", "default_service_type", "service_profiles", "base_service_minutes"],
"F06": [], "F07": ["customer_capacity", "capacity", "queue_capacity"],
"F08": ["staff_max"], "F09": [], "F10": ["service_attribute"],
}
FIELD_DECISIONS = {"customer_capacity": "D02", "staff_max": "D02", "service_attribute": "D01",
"maintenance_mana": "D04"}
GROUP_LINES = {"10.1": 199, "10.2": 201, "10.3": 203, "10.4": 205, "10.5": 207, "10.6": 209, "10.7": 211}
def parameter(fid, field, original=None, original_source=None):
c = current(fid, field)
approved = APPROVED_VALUES.get(fid, {}).get(field)
approval = None
if field in APPROVED_VALUES.get(fid, {}):
aid = "A04" if fid == "chapel" else "A05"
approval = {"value": approved, "decision_id": aid, "certainty": "approved_initial_trial_value", "source": APPROVALS[aid]["source"]}
state = "approved_initial_trial_value"
comparison = "match" if c["value"] == approved else "mismatch_or_missing"
elif original is not None:
state = "original_explicit_value"
comparison = "match" if c["value"] == original else "mismatch_or_missing"
elif field == "maintenance_gold":
gold = current(fid, "construction_cost")["value"]
calculation = max((gold["gold"] + 199) // 200, 1) if isinstance(gold, dict) else None
approval = {"formula": "0.5% of construction Gold per day; preserved integer trial rule: ceil, minimum 1G",
"conditional_value_using_current_build_cost": calculation, "decision_id": "A02",
"construction_cost_is_individually_approved": fid in APPROVED_VALUES,
"source": APPROVALS["A02"]["source"],
"integer_rule_source": anchor(UPKEEP, [58], "approved_trial_implementation_record"),
"certainty": "approved_formula_current_basis_may_be_legacy"}
state = "approved_formula_with_legacy_basis" if calculation is not None else "approved_formula_basis_missing"
comparison = "conditional_formula_match" if calculation is not None and c["value"] == calculation else "conditional_formula_mismatch_or_missing"
elif c["origin"] == "no_definition":
state = "not_set_no_definition"
comparison = "no_authoritative_value"
elif field == "customer_capacity" and c["value"] == -1 or field == "service_attribute" and c["value"] == "":
state = "explicitly_unset_required_contract"
comparison = "no_authoritative_value"
else:
state = "current_value_without_individual_approval"
comparison = "no_authoritative_value"
return {"field": field, "value_status": state,
"original": {"value": original, "source": original_source} if original is not None else None,
"individual_or_formula_approval": approval, "current": c,
"annex_A_legacy": ANNEX_ROWS.get(fid, {}).get(field),
"comparison": comparison, "decision_id": FIELD_DECISIONS.get(field),
"acceptance_state": "unverified_static_audit_only"}
def source_function(name):
facts = {
"一般客室": ("睡眠欲解消・宿泊", [79, 167]),
"上級客室": ("上級宿泊", [167]),
"VIPスイート": ("VIP宿泊・覗き対応・要高景観", [167, 199]),
"ドミトリー": ("地下の宿泊施設", [199]),
"大衆食堂": ("食欲解消・厨房とセット運用", [79, 168]),
"レストラン": ("飲食・厨房とセット運用", [168]),
"酒場": ("飲食・厨房とセット運用・騒音発生", [168, 201]),
"屋台": ("飲食施設", [200, 201]),
"大浴場": ("衛生・覗き対応", [169, 203]),
"カジノ": ("娯楽欲解消・イカサマ率設定をLvUPで解放", [79, 169, 179]),
"闘技場": ("娯楽施設", [169, 203]),
"礼拝堂": ("礼拝・寄付・完了時信仰加算", [169, 203]),
"風俗個室": ("地下サービス・覗き対応・LvUPでハードプレイ設定", [170, 179, 205]),
"高級ソープ": ("地下アングラ施設", [170, 205]),
"SM拷問室": ("地下アングラ施設", [170, 205]),
"闇市場": ("地下アングラ施設", [170, 205]),
"厨房": ("表層飲食店の裏で供給", [168, 173]),
"倉庫": ("物流施設", [173]),
"待機室": ("非番従業員の体力回復・待機", [109, 172]),
"従業員寮": ("従業員管理・夜間イベント対応", [172]),
"警備員詰所": ("従業員管理・警備員の拠点", [107, 172]),
"清掃員室": ("清掃員の裏層拠点", [106, 173]),
"医務室": ("物流・救護に関わる裏層施設", [110, 173]),
"覗き部屋": ("VIP/浴場/風俗の裏限定・裏通路寸断・記録係配置で脅迫ネタ収集", [52, 53, 174]),
"地下牢": ("地下特殊施設", [174, 209]),
"マナ炉": ("地下特殊施設・奴隷消費", [174, 209]),
"蔓の昇降機": ("客用・定員あり・垂直移動", [86, 176]),
"業務用EV": ("裏層・大容量・高速の垂直移動", [86, 176]),
"ワープ": ("瞬間移動・マナコスト大", [86, 176]),
"魔法砲台": ("外壁の防衛設備", [210, 211]),
}
text, lines = facts[name]
return {"condition": text, "source": anchor(BASE, lines, "specified_original")}
def relation(name):
if name in ["大衆食堂", "レストラン", "酒場"]:
return {"required": True, "condition": "裏層の厨房とセット運用。関連先は配置の存在だけでなく実稼働を評価する。",
"target_kind": "kitchen", "source": anchor(BASE, [168, 173], "specified_original")}
if name == "厨房":
return {"required": True, "condition": "表層飲食店の裏に配置。供給先との対応を保持。", "target_kind": "front_food_facility",
"source": anchor(BASE, [173], "specified_original")}
if name == "覗き部屋":
return {"required": True, "condition": "VIP/浴場/風俗の裏限定。複数関連条件のAND/ORではなく対象種別の許可集合。",
"target_kind": ["VIP", "bath", "adult_room"], "source": anchor(BASE, [52, 174], "specified_original")}
return {"required": None, "condition": "個別の関連先は原本/A01〜A05に指定なし。空文字を正式な不要判定としない。",
"target_kind": None, "source": None}
mapping = json.loads((ROOT / "docs/plans/original-completion-2026-09-29/execution/original_facility_mapping.json").read_text())
records = []
for ix, mapped in enumerate(mapping, 1):
fid, name = mapped["definition_id"], mapped["name"]
source = anchor(BASE, [GROUP_LINES[mapped["section"]]], "specified_original")
size = [int(v) for v in mapped["original_size"].split("x")] if "x" in mapped["original_size"] else None
layer = 1 if mapped["original_layer_zone"].startswith("BACK") else 0 if mapped["original_layer_zone"].startswith("FRONT") else None
zone = 3 if "地下" in mapped["original_layer_zone"] else None
original_values = {"footprint": size, "layer": layer, "zone": zone}
parms = {field: parameter(fid, field, original_values.get(field), source if field in original_values else None)
for fields in F_FIELDS.values() for field in fields}
parms.update({field: parameter(fid, field) for field in ["max_level", "upgrade_costs", "unlock_rank", "research_requirements", "policy_schema", "faith_per_service"]})
# Lv1 data is recorded as current base data, never assumed approved for every level.
levels = []
for lv in [1, 2, 3]:
levels.append({"level": lv,
"authoritative_level_specific_price": None,
"authoritative_level_specific_customer_capacity": None,
"authoritative_level_specific_staff_min": None,
"authoritative_level_specific_staff_max": None,
"approved_base_trial_values_initial_level_candidate": APPROVED_VALUES.get(fid) if lv == 1 else None,
"base_approval_has_explicit_level_scope": False,
"base_approval_use_note": "A04/A05初期試験値をLv1初期値候補に結び付ける。承認本文にLv別変更はない。Lv2/3への複写や不変性の承認は主張しない。" if fid in APPROVED_VALUES else None,
"current_level_specific_definition": None,
"current_base_parameters_reference": list(parms) if lv == 1 and fid else [],
"upgrade_cost_from_previous_current_legacy": current(fid, "upgrade_costs")["value"][lv-2] if lv > 1 and fid and len(current(fid, "upgrade_costs")["value"]) >= lv-1 else None,
"unlock_condition": None, "policy_unlock_assignment": None,
"status": "no_approved_level_ladder", "decision_ids": ["D02", "D04"],
"source": anchor(PLAN, [207], "user_requested_implementation_plan")})
r = {"id": f"FVC.original.{ix:02d}", "name": name, "source_section": mapped["section"],
"definition_id": fid, "definition_path": DEFINITIONS[fid]["path"] if fid else None,
"definition_exists": bool(fid), "normal_registered_static": bool(fid and DEFINITIONS[fid]["path"] in NORMAL_PATHS),
"registration_state": "normal_registered" if fid and DEFINITIONS[fid]["path"] in NORMAL_PATHS else "definition_only" if fid else "no_definition",
"original_value_source": source,
"source_function": source_function(name),
"major_items": {group: {"source": anchor(ADDITION, [984 + int(group[1:]) - 1], "retained_user_addition_record"),
"parameter_fields": fields, "acceptance_state": "unverified"} for group, fields in F_FIELDS.items()},
"parameters": parms,
"placement_conditions": {"height": "高層" if "高層" in mapped["original_layer_zone"] else None,
"height_threshold": None, "high_beauty_required": name == "VIPスイート", "beauty_threshold": None,
"outer_wall_required": name == "魔法砲台", "source": source,
"access_and_support": anchor(BASE, [164], "specified_original")},
"related_facility": {"source_required_relation": relation(name), "current_definition_field": "not_present",
"current_instance_default": {"id": "", "operating": True,
"source": anchor(INSTANCE, [49, 50, 84, 85], "current_implementation_only")},
"runtime_creation_sets_related_id": False,
"runtime_creation_source": anchor(RUNTIME, [findline(RUNTIME, 'func _register_operational_facility('), findline(RUNTIME, '&"customer_capacity": definition.customer_capacity')], "current_implementation_only"),
"single_dependency_contract_reference": "common_contracts.related_facility"},
"operation_state_contract_reference": "common_contracts.operation_state",
"surcharge_contract_reference": "common_contracts.need_match_surcharge",
"need_match_inputs": {
"attribute": parms["service_attribute"],
"deficit_threshold": {"value": None, "status": "required_before_dependent_charging", "decision_id": "D01"},
"surcharge_amount_or_rate": {"value": None, "status": "required_before_dependent_charging", "decision_id": "D01"},
"source": anchor(DECISIONS, [findline(DECISIONS, '"id": "D01"')], "pending_required_user_value"),
"deficit_threshold_not_inferred_from_need_effects": True},
"generic_capacity_contract": {
"is_customer_capacity": False,
"current": parms["capacity"]["current"],
"profile_capacity_units": {key: value.get("capacity_units") for key, value in (current(fid, "service_profiles")["value"] or {}).items()},
"queue_capacity_is_separate": True,
"current_runtime_use": "service slots (definition.capacity clamped to at least 1), not unique active guest count",
"source": anchor(INSTANCE, [247, 248, 253, 261, 270], "current_implementation_only")},
"level_contracts": levels,
"unresolved_value_fields": [field for field, p in parms.items() if p["value_status"] in ["explicitly_unset_required_contract", "not_set_no_definition", "approved_formula_basis_missing"]],
"unapproved_current_fields": [field for field, p in parms.items() if p["value_status"] == "current_value_without_individual_approval"],
"definition_anomalies": {key: DEFINITIONS[fid][key] for key in ["duplicate_assignments", "unknown_export_fields"]} if fid else None,
"runtime_acceptance_state": "unverified_no_engine_run_in_this_audit"}
records.append(r)
supplemental = []
matched = {r["definition_id"] for r in records if r["definition_id"]}
for fid, definition in DEFINITIONS.items():
if fid in matched:
continue
supplemental.append({"id": f"FVC.supplemental.{fid}", "definition_id": fid,
"display_name": definition["fields"]["display_name"]["value"], "definition_path": definition["path"],
"normal_registered_static": definition["path"] in NORMAL_PATHS,
"chapter_10_mapping": None,
"major_item_applicability": "not_finalized_for_facilities_outside_chapter_10",
"parameters": {field: parameter(fid, field) for fields in F_FIELDS.values() for field in fields},
"annex_A_card": anchor(ANNEX, [ANNEX_CARDS[fid]], "retained_legacy_implementation_reference") if fid in ANNEX_CARDS else None})
missing_blueprints = []
for r in records:
if r["definition_exists"]:
continue
missing_blueprints.append({"contract_id": r["id"], "name": r["name"], "production_id": None,
"safe_original_values": {key: r["parameters"][key]["original"] for key in ["footprint", "layer", "zone"] if r["parameters"][key]["original"]},
"safe_original_conditions": [r["source_function"], r["placement_conditions"], r["related_facility"]["source_required_relation"]],
"unknowns": ["production_id", "construction_cost", "build_minutes", "maintenance_mana", "base_price", "customer_capacity",
"staff_min", "staff_max", "service_attribute", "Lv1_to_Lv3_values", "unlock_conditions"],
"register_and_allow_build_now": False,
"reason": "原本で確定する寸法・層・地域・機能だけを設計用blueprintへ抽出可能。FacilityDefinition既定値で空欄を埋めて営業可能とする承認はない。"})
common = {
"operation_state": {"authoritative_states": ["INACTIVE", "OPERATING", "SUSPENDED"],
"persistent_suspension": "破壊/摘発後の休止は関連施設復旧だけでは解除しない。",
"source": anchor(ADDITION, [989], "retained_user_addition_record"),
"current_code": anchor(INSTANCE, [8, 186, 198, 219, 225, 229], "current_implementation_only"),
"mana_zero": {"value": 0, "behavior": "全館機能停止", "source": anchor(BASE, [183], "specified_original")},
"resumption_conditions": {"value": None, "status": "explicitly_unset_optional_extension", "decision_id": "D03",
"is_mandatory_missing": False}, "acceptance_state": "unverified"},
"related_facility": {"cardinality": "single", "flag": "actual_operation_state", "cycles": "reject",
"source": anchor(ADDITION, [992], "retained_user_addition_record"),
"current_code": anchor(SERVICE, [141, 146, 151, 155, 359, 365], "current_implementation_only"),
"multi_relation_AND_OR": {"value": None, "status": "explicitly_unset_optional_extension", "decision_id": "D03", "is_mandatory_missing": False},
"individual_assignment": "本文明示の厨房連携や覗き対象を汎用単一関連APIが自動割当済みとは扱わない。", "acceptance_state": "unverified"},
"need_match_surcharge": {"attribute_vocabulary": ["appetite", "relaxation", "sexual", "gambling"],
"source": anchor(ADDITION, [986, 993, 995], "retained_user_addition_record"),
"deficit_threshold": {"value": None, "status": "required_before_dependent_charging", "decision_id": "D01"},
"surcharge_amount_or_rate": {"value": None, "status": "required_before_dependent_charging", "decision_id": "D01"},
"per_facility_attribute": {"value": None, "status": "required_before_dependent_charging", "decision_id": "D01"},
"multiple_attributes_and_duplicate_surcharge": {"value": None, "status": "explicitly_unset_optional_extension", "is_mandatory_missing": False},
"nonmatching_utilization": "allowed", "quote_api_supplied_amount_sentinel": -1,
"current_quote_code": anchor(PRICING, [findline(PRICING, 'static func quote_service(')], "current_implementation_only"),
"current_normal_pricing": "pending_service_readmodel returns unresolved inputs; real surcharge not connected",
"current_runtime": anchor(RUNTIME, [findline(RUNTIME, 'func get_facility_service_pricing_readmodel(')], "current_implementation_only"),
"guest_need_effects_are_attribute_assignment": False,
"guest_need_0_100_is_deficit_threshold": False,
"Anri_multiplier": {"percent": 20, "scope": "assigned floor fees; guest payment equals facility revenue", "source": APPROVALS["A03"]["source"]},
"composition_order_with_Anri": {"value": None, "status": "requires_contract_before_surcharge_connection", "source": anchor(PLAN, [206], "user_requested_implementation_plan")},
"acceptance_state": "unverified"},
"generic_capacity": {"is_customer_capacity": False, "waiting_is_customer_count": False,
"definition_default": 0, "runtime_minimum_slots": 1,
"source": anchor(DEF, [61, 63, 64], "current_implementation_only"),
"runtime_source": anchor(RUNTIME, [findline(RUNTIME, '&"capacity": maxi(definition.capacity, 1)')], "current_implementation_only"),
"customer_enforcement": anchor(INSTANCE, [247, 248, 253, 261, 270], "current_implementation_only"),
"examples": {"kitchen": "generic20 / profile batch4", "warehouse": "generic100; inventory/handling meaning", "staff_dormitory": "generic12 staff support / current customer0", "cleaning_room": "generic0 / approved customer0 / runtime slots clamped1"}},
"levels": {"planned_levels": [1, 2, 3], "original_max_level_number": None,
"original": anchor(BASE, [165, 179], "specified_original"), "plan": anchor(PLAN, [207], "user_requested_implementation_plan"),
"current_max_level_default": 3, "default_is_approved_ladder": False,
"current_level_specific_price_staff_customer_data": "none in FacilityDefinition",
"unapproved_upgrade_costs": "retained per-facility raw data, never treated as approved prices/capacities",
"unlock_rank_default_1_is_approved": False,
"normal_build_code_checks_unlock_rank_or_research": False,
"normal_build_source": anchor(RUNTIME, [findline(RUNTIME, 'func _execute_build_facility('), findline(RUNTIME, 'definition.build_minutes,')], "current_implementation_only"),
"acceptance_state": "unverified"},
"upkeep": {"A01": APPROVALS["A01"], "A02": APPROVALS["A02"],
"individual_overrides": ["chapel:6G/day", "cleaning_room:2G/day"],
"formula_is_approval_of_build_cost": False,
"annex_zero_maintenance_values_are_current": False,
"acceptance_state": "unverified_in_this_value_audit"},
"cleaning_A05": {"cleanliness_range": [0, 100], "initial": 100, "reserve_at_or_below": 75,
"onsite_minutes": 8, "restore": 25, "use_only_defined_dirt": True, "source": APPROVALS["A05"]["source"],
"not_service_attribute_or_matching_deficit_threshold": True},
}
count_status = Counter(p["value_status"] for r in records for p in r["parameters"].values())
summary = {"original_facilities": len(records), "defined": sum(r["definition_exists"] for r in records),
"normal_registered_static": sum(r["normal_registered_static"] for r in records),
"definition_only": [r["definition_id"] for r in records if r["registration_state"] == "definition_only"],
"undefined": len(missing_blueprints), "all_current_definition_files": len(DEFINITIONS), "supplemental_defined_outside_chapter10": len(supplemental),
"original_facility_parameter_records": sum(len(r["parameters"]) for r in records),
"parameter_value_status_counts": dict(sorted(count_status.items())),
"customer_capacity": dict(Counter(r["parameters"]["customer_capacity"]["value_status"] for r in records)),
"staff_max": dict(Counter(r["parameters"]["staff_max"]["value_status"] for r in records)),
"service_attribute": dict(Counter(r["parameters"]["service_attribute"]["value_status"] for r in records)),
"all_current_definition_service_attributes_empty": all(current(fid, "service_attribute")["value"] == "" for fid in DEFINITIONS),
"all_original_defined_dimensions_match": all(r["parameters"]["footprint"]["comparison"] == "match" for r in records if r["definition_exists"]),
"all_original_defined_layers_match": all(r["parameters"]["layer"]["comparison"] == "match" for r in records if r["definition_exists"]),
"approved_runtime_acceptances_counted": 0,
"is_final_all_facility_catalog": False,
"optional_unset_D03_counted_as_mandatory_missing": 0,
"counts_are_field_observations_not_atomic_completion_denominator": True}
recommendations = [
{"id": "P09-safe-01", "action": "A04/A05の承認済み人数・最少/最大職員・料金の契約を通常UIと保存へつなぐ境界検証を先行。current値を変更する前にsource/value確定と表示を区別。",
"new_numeric_decision_needed": False, "dependencies": ["existing approved facilities"], "acceptance": "customer_count excludes queued guests; over-limit refusal preserves state; staff_min and staff_max remain distinct; read-only pricing inspection"},
{"id": "P09-safe-02", "action": "customer_capacity=-1とservice_attribute空文字をUIで未設定と表示し、現在明示2/0も承認由来の別表示にする。generic容量・人数・待機人数を分ける。",
"new_numeric_decision_needed": False, "dependencies": ["existing readmodels"], "acceptance": "unset is never presented as 0/unlimited/approved; fields and save values unchanged"},
{"id": "P09-safe-03", "action": "13未定義施設のsource-only blueprint/設計カタログを追加。確定寸法/層/地下/高層/外壁/厨房/覗き制約だけを保持し、未確定の値はnullとする。通常建設へ登録しない。",
"new_numeric_decision_needed": False, "dependencies": [], "acceptance": "no fabricated costs/capacities/IDs; cannon footprint remains null; no FacilityDefinition default leakage into playable construction"},
{"id": "P09-safe-04", "action": "食堂・酒場の厨房セット運用を現単一関連APIと実稼働判定へ接続する契約/候補検証を用意。厨房停止・破壊・未配置・保存復元時の動作を検証。",
"new_numeric_decision_needed": False, "dependencies": ["normal source-confirmed kitchen relationship", "placement selection contract"],
"acceptance": "cannot operate solely because a kitchen exists; do not invent multi dependency or automatic suspension recovery"},
{"id": "P09-safe-05", "action": "Lv1–3の明示tableと未設定guardを作り、Lv2/3料金や人数をbase値の複写で確定しない。既存upgrade_costsとrank1は旧実装欄に保全。",
"new_numeric_decision_needed": False, "dependencies": ["save schema/version policy before persisted model changes"],
"acceptance": "unknown level action rejected before charging or mutation; existing saves preserved; known A04/A05 base values reused without claiming level scope"},
{"id": "P09-safe-06", "action": "欲求加算はD01確定まで請求を接続せず、nonmatch利用可・未解決見積拒否・財布/収入一致の計算契約と比較例を準備する。",
"new_numeric_decision_needed": False, "dependencies": ["D01 only before actual surcharge"], "acceptance": "no threshold copied from cleaning/guest needs; no attribute inferred from need_effects; A03 composition explicitly specified before charge"},
]
for p in [BASE, ANNEX, DECISIONS, ADDITION, PLAN, DEF, CATALOG, INSTANCE, SERVICE, PRICING, RUNTIME, UPKEEP]:
pin(p)
document = {"schema_version": 1, "audit_id": "P09-facility-value-static-2026-10-02-v1",
"source_head": subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip(),
"source_is_working_tree": True, "method": "read-only text parsing and semantic source audit; no engine, no production mutation",
"authority_order": ["specified original", "confirmed user A01-A05 and facility addition", "annex A legacy implementation reference", "current implementation"],
"status_definitions": {"original_explicit_value": "原本の明示値。現値一致は静的比較であり受入ではない。",
"approved_initial_trial_value": "個別承認の初期試験値。最終バランスや全Lv値の承認を意味しない。",
"approved_formula_with_legacy_basis": "率・整数則は承認済み。建築費の現値は個別承認値とは限らない。",
"current_value_without_individual_approval": "旧/現在コードに値あり。原本/個別承認の根拠は未確認。勝手に採用値へ格上げしない。",
"explicitly_unset_required_contract": "当該機能の本番接続前に必要な未設定値。",
"not_set_no_definition": "独立した定義なし。原本の確定項目は別欄へ抽出。",
"explicitly_unset_optional_extension": "未採用/未設定維持が許される拡張。必須欠損に数えない。"},
"summary": summary, "source_pins": PINS, "approvals": APPROVALS,
"common_contracts": common, "original_facilities": records, "missing_definition_blueprints": missing_blueprints,
"supplemental_current_definitions": supplemental,
"scope_pending": {"named_original_outside_chapter10": ["受付/精算所", "金庫室", "購買部", "闇銀行", "通路", "階段", "従業員ドア", "リネンシュート", "隠し扉"],
"source": anchor(BASE, [49, 50, 86, 176, 177, 190, 191, 192, 193], "specified_original"),
"contract": "F各施設とLv素材の適用範囲はatomic open expansion registerと同じ未確定状態。30施設/37定義の件数を最終全施設数にしない。"},
"safe_followup_candidates": recommendations,
"acceptance": {"completed": False, "runtime_runs_in_this_audit": 0, "source_comparison_is_runtime_acceptance": False}}
(OUT / "facility_value_contracts.json").write_text(json.dumps(document, ensure_ascii=False, indent=2) + "\n")
def brief(v):
return "未定義" if v is None else "未設定" if v == -1 else str(v)
table = ["| 原本施設 | 定義/通常登録 | 原本サイズ・層 | min/max現値 | 人数専用/汎用capacity現値 | 料金現値 | 根拠 |",
"|---|---|---|---|---|---|---|"]
for r in records:
p = r["parameters"]
root_value = mapped_value = "×".join(map(str, p["footprint"]["original"]["value"])) if p["footprint"]["original"] else "未指定"
layer = p["layer"]["original"]["value"] if p["layer"]["original"] else None
place = root_value + (" 裏" if layer == 1 else " 表" if layer == 0 else " 外壁")
approved_label = "A04初期値" if r["definition_id"] == "chapel" else "A05初期値" if r["definition_id"] == "cleaning_room" else "数値は旧/現実装・個別根拠未確認" if r["definition_exists"] else "原本の寸法等のみ確定"
table.append(f"| {r['name']} | {r['registration_state']} | {place} | {brief(p['staff_min']['current']['value'])}/{brief(p['staff_max']['current']['value'])} | {brief(p['customer_capacity']['current']['value'])}/{brief(p['capacity']['current']['value'])} | {brief(p['base_price']['current']['value'])} | {approved_label} |")
blueprint_table = ["| 未定義施設 | 確定できる項目 | 原本行 |",
"|---|---|---|"]
for bp in missing_blueprints:
r = next(r for r in records if r["id"] == bp["contract_id"])
dimension = bp["safe_original_values"].get("footprint", {}).get("value")
dims = "×".join(map(str, dimension)) if dimension else "寸法未指定"
blueprint_table.append(f"| {bp['name']} | {dims}; {r['source_function']['condition']}; {mapping[int(r['id'].split('.')[-1])-1]['original_layer_zone']} | {GROUP_LINES[r['source_section']]} |")
readme = f"""# P09 施設必須値の再監査(2026-10-02)
原本30施設について、定義あり{summary['defined']}、通常登録{summary['normal_registered_static']}、定義のみ1(black_market)、未定義{summary['undefined']}を静的に確認した。現在の全`.tres`は{summary['all_current_definition_files']}件で、そのうち原本第10章の対応外は{len(supplemental)}件。これは最終の全施設数でも、実装・受入完了率でもない。今回のエンジン実行は0件、本体変更は0件。
機械可読台帳は `facility_value_contracts.json`。各値は原本文、A01〜A05、別冊A作成時の値と由来、現`.tres`明示値またはクラス既定値を別欄に保存し、出典行とSHAを保持する。生成は `python3 docs/plans/original-completion-2026-10-02/build_facility_value_contracts.py`。検証は `validate_facility_value_contracts.py`。承認差異の静的一致からRuntime/UI/保存のPASSを作らない。
原本を最上位とし、A01〜A05と会話追加10項目を重ねる。別冊Aは旧実装の完全一覧であり、数値承認書ではない。冒頭3・10・14〜19・76・106行が既定値/旧状態/仮値/異なる処理系を区別している。参照先 `docs/FACILITY_UNIT_PARAMETERS.md` は現ローカルに存在しないため、その内容を推定補完しなかった。既存の保存版TRACEABILITY 978〜995行を追加指定の記録に用いた。
## 30施設の現値と根拠
表のmin/max、人数専用、capacity、料金は現実装の観測値である。礼拝堂・清掃員室以外の数値を承認済みに扱わない。未設定は人数専用-1、未定義は独立定義なしを表す。
{chr(10).join(table)}
原本で寸法のある現定義17施設は寸法と層が静的一致する。一般的な「表」は地上専用を意味しない。地下や高層の指定は個別に保存し、魔法砲台の幅・高さ・層は捏造しない。premium_room、staff_dormitory、cleaning_room、chapelの通常登録は今回変更ではなく現基準の既存成果である。
## 10項目と必須値の区別
1. 配置コスト: 原本は資金/資材と建設ルールを指定するが、施設別コストと工期の数値はA04/A05以外に個別承認を確認できない。現値は保存しても正式値へ格上げしない。
2. 運用コスト: A01の保有按分、休業/休止中計上、00:00精算、不足繰越とA02の0.5%初期試験率は確定。既存ceil/最低1Gを出典付きで保持する。礼拝堂6G/日、清掃員室2G/日は個別上書き。率の承認は建築費自体の個別承認を意味しない。闇市場の現maintenance_gold=既定0は、現1600Gに率を当てた条件付き8Gと異なる。未登録定義を登録する前にこの差異を解消する契約が必要。施設別マナ量は原本では未指定。原本183行のマナ0で全停止を別に保持する。
3. 利用料金: 礼拝堂20G寄付、清掃員室利用料なしを承認初期値として保持。他施設の単価や5段階60/80/100/150/250%は旧実装値。D01の不足基準、加算額/率、属性割当は未設定。元の四欲求0〜100や清掃予約75を不足基準に流用しない。A03の20%補正は承認済みだが、新しい一致加算との順序/丸めはP09-02接続前に固定する。
4. サイズ: 29施設の原本寸法と30対象の層/地下/高層/外壁指定を追跡。魔法砲台寸法は未指定のまま。
5. 役割: 職種とサービス機能の原文、required_rolesとstaff_minを分離。A04聖職者min1/max2、A05清掃員min1/max2は確定。原本の記録係配置などの役割と人数の数値は同一視しない。
6. 稼働状態: INACTIVE/OPERATING/SUSPENDEDと破壊/摘発後の持続休止は会話追加の確定契約。復帰条件D03はユーザー指定の未設定拡張であり、必須値欠損に数えない。
7. 顧客同時利用最大人数: 承認値は礼拝堂8、清掃員室0のみ。上級客室2・従業員寮0は現明示値だが根拠未確認。残る定義13件は-1継承、未定義13件には値なし。待機者は利用人数とは別。厨房capacity20/profile生産4、倉庫capacity100、従業員寮capacity12などをcustomer_capacityへ転記しない。
8. 配置可能従業員最大数: staff_maxはstaff_minと別。2施設の個別承認、15施設の旧/現値、13施設の未定義を保持する。Lv別変更もD02の未確定に残す。
9. 関連施設/稼働フラグ: 原本168・173行の食堂/レストラン/酒場と厨房セット、52・174行の覗き対象などを個別追跡。現汎用単一依存APIは循環拒否と実稼働評価を持つが、建設からのcreate_instanceにはrelated_facility_idを渡していない。原本の対応を自動設定済みと認定しない。D03の複数依存AND/ORは未設定維持が許され、必須欠損へ数えない。
10. 属性: 許可型appetite/relaxation/sexual/gamblingは確定、施設への割当は全37定義で空文字。Runtimeのhunger/sleep/hygiene/medical/leisure/intimacy効果と提供属性は別の名前空間・用途。効果タグから勝手に属性を選ばない。不一致の利用は許可する。
## Lv1〜3と解放
全30施設にLv1/2/3を別レコードとして置き、価格・人数・staff_min/max・解放条件・Policy解放割当を未確定として追跡する。A04/A05の初期値をLv1候補へ参照するが、承認本文にLv指定はなく、Lv2/3で不変とも自動増加とも承認されていない。原本165行のLvMAXに数値はない。計画P09-03のLv1〜3と現既定max_level3を混同しない。
現FacilityDefinitionにレベル別料金/人数の表はない。厨房などのupgrade_costsは旧実装の費用列であり、レベル別営業値ではない。unlock_rank既定1は正式な個別解放承認ではなく、現通常建設コマンドにunlock_rank/research_requirementsの評価もない。別冊Aの世界樹ランク表や初回Day案内は派生計画/実装欄に保全し、原本施設別解放条件へ昇格しない。原本179行の価格、食材、混浴、ハード設定、イカサマ率のLv解放は機能要件として残り、どのLvでどの値にするかを捏造しない。
## 未定義13施設から抽出できる範囲
{chr(10).join(blueprint_table)}
これらはsource-only blueprintとして先行定義できる。費用・人数・職員上限・マナ量・属性・個別解放・Lv2/3の値が未確定のまま、FacilityDefinitionの既定100G/1資材/60分などを適用して通常建設へ公開しない。既存地下客室をドミトリー、地下VIPサロンをVIPスイート、魔力制御室をマナ炉の完成代替と扱わない。
## 次に進める低依存作業
{chr(10).join('- ' + r['action'] for r in recommendations)}
この監査は値の出典と欠損を整理したもので、P09や各施設の受入完了ではない。通常操作、グループ利用中変更、日跨ぎ、途中保存、修正後同一ソース回帰の証拠は引き続き必要である。第9章経済施設、通路/階段/ドア/リネンシュート/隠し扉へのF各施設/Lv素材適用範囲はatomicのopen expansion registerと同じ未確定状態を維持する。
静的データの追加観測として、医務室定義には同値のstaff_max/capacity二重代入と、FacilityDefinitionに宣言がないqueue_staff_minがある。台帳のdefinition_anomaliesへ保存し、今回Runtimeへの影響を推測したり本体を修正したりしない。validatorは49出典ファイルのSHA、30施設/780値/90Lvの構造、全出典行、D03除外、架空値拒否を確認する。PASSは台帳の整合性だけを意味する。
"""
(OUT / "P09_VALUE_CONTRACTS.md").write_text(readme)
print(json.dumps(summary, ensure_ascii=False, indent=2))