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.
523 lines
42 KiB
Python
523 lines
42 KiB
Python
#!/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))
|