#!/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))