399 lines
26 KiB
Python
399 lines
26 KiB
Python
#!/usr/bin/env python3
|
||
"""Verify P13-03 records only. Never writes receipts or accepts game content.
|
||
|
||
Runner: python -B tools/check_p13_content_difference.py --verify-only
|
||
Optional --ledger permits read-only verification of a candidate ledger.
|
||
"""
|
||
import argparse
|
||
import copy
|
||
import hashlib
|
||
import json
|
||
from pathlib import Path
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
OUT = 'docs/plans/original-completion-2026-10-02/execution/p13-03/'
|
||
PLAN = 'docs/plans/original-completion-2026-10-02/'
|
||
MAPPING = 'docs/plans/original-completion-2026-09-29/execution/original_facility_mapping.json'
|
||
SLOTS = PLAN + 'asset_inventory_reaudit/facility_usage_slots.json'
|
||
SPEC = 'docs/spec/acceptance/P13-03.json'
|
||
BASE = 'e1a1651e15bbc9a0526ef31d0b2786feb20d8cd1'
|
||
SPEC_RAW = '917cf1504bbfc700e0da42b2db496b42486fe3c36c680f301f58cf34c95267b6'
|
||
SPEC_RUNNER = 'e1255835c1227e02bac42eb99b026b0fcbe69c272f9ed0b36199a2daf8d55c53'
|
||
VIEWS = ('normal_entry', 'runtime', 'event_state', 'save', 'test')
|
||
CATEGORIES = {
|
||
'event': ['B-1.6', 'B-2.5', 'B-8.2', 'B-8.3'],
|
||
'overlay': ['B-2.5'],
|
||
'history': ['B-1.6'],
|
||
'story': ['B-1.6'],
|
||
'voice': ['B-1.6'],
|
||
'scene': ['B-1.6'],
|
||
}
|
||
|
||
INTEGRATION_NOTES = [
|
||
'Read-only integration audit: contract base e1a1651e15bbc9a0526ef31d0b2786feb20d8cd1 versus main 57131ec. This is a future refresh instruction, not a relabeling of any recorded source/game/asset hash.',
|
||
'autoload/simulation_runtime.gd adds preview_build_facility and extracts _build_facility_precheck shared by preview and execution. scenes/main/world_preview.gd routes previews through Runtime with a fallback and adds demolition mode. scenes/main/main.gd adds demolition confirmation/command handling, preview error messages and stricter interface-selection version checks.',
|
||
'At normal integration Root must refresh normal_entry observations for original_facility_13, original_facility_14, original_facility_15, original_facility_16, original_facility_24, original_facility_25 and original_facility_26 against the integrated main.gd and preview path; refresh runtime observations against the integrated Runtime. Refresh event_state and save search evidence too because their Runtime source hash changes, even though these changed hunks do not establish dedicated original facility events or save round-trips. Recheck mapping/catalog/definitions without promoting missing identities or black_market eligibility.',
|
||
'The selected test sources in this ledger, facility catalog, event service and save service are unchanged between these commits. Added tests/transport/test_runtime_build_preview.gd and tests/ui/test_p08_normal_placement_ops.gd concern generic preview/placement/demolition; tests/ui/test_selection_departure.gd and tests/ui/fixtures/selection_cross_process.gd concern selection restoration. Root should assess their bounded relevance when refreshing each facility test observation; none accepts original content. Guest observation selection changes and P08 migration fixtures likewise provide no original facility content acceptance.',
|
||
'Minimal handling: retain this base-version evidence, explicitly re-search changed integrated source paths and rebuild affected technical observations, source pins and output receipt as a reviewed integration refresh; retain all unknown/unverified content states and the 30/88 denominator. Do not merely replace old hashes or claim old tests ran against new code. No rebase, overwrite or Git write is performed here.',
|
||
'If P13 integrates first, P23 9a full tools map gains tools/check_p13_content_difference.py. P23 proof remains bound to its own version and requires future final source verification; this audit does not extend that proof.',
|
||
]
|
||
|
||
|
||
def raw(path):
|
||
p = Path(path)
|
||
if p.is_absolute() or '..' in p.parts:
|
||
raise ValueError('source must be checkout-relative')
|
||
resolved = (ROOT / p).resolve()
|
||
if not resolved.is_relative_to(ROOT):
|
||
raise ValueError('source escapes checkout')
|
||
return resolved.read_bytes()
|
||
|
||
|
||
def sha(data):
|
||
return hashlib.sha256(data).hexdigest()
|
||
|
||
|
||
def load(path):
|
||
def unique(pairs):
|
||
result = {}
|
||
for key, value in pairs:
|
||
if key in result:
|
||
raise ValueError('duplicate JSON key: ' + key)
|
||
result[key] = value
|
||
return result
|
||
return json.loads(raw(path), object_pairs_hook=unique)
|
||
|
||
|
||
def observation(paths, tokens, note):
|
||
"""Exact token matches are search observations, never accepted links."""
|
||
evidence = []
|
||
for path in paths:
|
||
lines = raw(path).decode('utf-8').splitlines()
|
||
matches = [{'line': i + 1, 'text': line} for i, line in enumerate(lines)
|
||
if any(token in line for token in tokens)]
|
||
evidence.append({'path': path, 'sha256': sha(raw(path)),
|
||
'searched_tokens': tokens, 'matches': matches,
|
||
'scope': 'Static search of this file only; no execution or acceptance.'})
|
||
return {'status': 'unverified', 'accepted': False, 'note': note, 'evidence': evidence}
|
||
|
||
|
||
def expected():
|
||
if sha(raw(SPEC)) != SPEC_RAW or sha(raw(SPEC).decode().strip().encode()) != SPEC_RUNNER:
|
||
raise ValueError('specification hash mismatch')
|
||
spec = load(SPEC)
|
||
sources = []
|
||
for path, pin in spec['source_pins'].items():
|
||
if path == PLAN + 'work_packages.json':
|
||
frozen = subprocess.check_output(['git', 'show', BASE + ':' + path], cwd=ROOT)
|
||
if sha(frozen) != pin:
|
||
raise ValueError('frozen work package pin mismatch')
|
||
sources.append({'path': path, 'sha256': pin, 'applicability': 'base_git_blob', 'commit': BASE})
|
||
sources[-1]['scope'] = 'Frozen contract input. Live plan state is maintained separately and is not content completion evidence.'
|
||
else:
|
||
if sha(raw(path)) != pin:
|
||
raise ValueError('stale required source: ' + path)
|
||
sources.append({'path': path, 'sha256': pin, 'applicability': 'current_checkout'})
|
||
mapping = load(MAPPING)
|
||
slots = load(SLOTS)
|
||
if len({s['slot_id'] for s in slots}) != len(slots):
|
||
raise ValueError('duplicate source slot IDs')
|
||
definitions = sorted(p.relative_to(ROOT).as_posix() for p in (ROOT / 'data/facilities').rglob('*.tres'))
|
||
definitions_text = {p: raw(p).decode() for p in definitions}
|
||
catalog = 'scripts/data/facility_catalog.gd'
|
||
entry = 'scenes/main/main.gd'
|
||
runtime = 'autoload/simulation_runtime.gd'
|
||
event = 'scripts/events/event_service.gd'
|
||
save = 'scripts/core/save_service.gd'
|
||
test = 'tests/s6/test_runtime_snapshot_serialization.gd'
|
||
search_paths = [catalog] + definitions
|
||
facilities = []
|
||
for index, original in enumerate(mapping, 1):
|
||
if original['section'] not in ('10.4', '10.6'):
|
||
continue
|
||
identity = 'original_facility_%02d' % index
|
||
# Names and original stable definition IDs are the bounded search basis.
|
||
tokens = [original['name']]
|
||
if original['definition_id']:
|
||
tokens.append(original['definition_id'])
|
||
matching = [p for p, text in definitions_text.items()
|
||
if any(t in text for t in tokens)]
|
||
ids = []
|
||
for path in matching:
|
||
found = re.search(r'^id = &"([^"]+)"', definitions_text[path], re.M)
|
||
if found:
|
||
ids.append(found.group(1))
|
||
# No mana_control_room or generic proxy may fill an original identity.
|
||
if 'mana_control_room' in ids:
|
||
raise ValueError('proxy mapping is prohibited')
|
||
facility_slots = [s for s in slots if s['facility_name'] == original['name']]
|
||
facilities.append({
|
||
'original_id': identity, 'original': original,
|
||
'mapping_observation': observation(search_paths, tokens,
|
||
'Current catalog and all data/facilities definitions searched. No match means missing in this bounded set; historical null is not treated as proof.'),
|
||
'current_definition_ids': ids,
|
||
'current_definition_paths': matching,
|
||
'connections': {
|
||
'normal_entry': observation([entry, catalog], tokens,
|
||
'adult_private_room is in the UI list and baseline catalog; black_market is expansion-only. No other original identity match. Static eligibility only; actual construction is unverified.'),
|
||
'runtime': observation([runtime, catalog] + matching, tokens + ['FacilityCatalog.FACILITY_PATHS'],
|
||
'Runtime loads baseline/infrastructure/rescue/original paths, not expansion paths at this load site. Generic services do not prove original special behavior.'),
|
||
'event_state': observation([runtime, event], tokens,
|
||
'No facility-specific transition established by this search. Generic EventService is not a dedicated content catalog.'),
|
||
'save': observation([runtime, save], tokens + ['event_service.create_snapshot()', 'save_snapshot'],
|
||
'Generic snapshot plumbing is a technical lead only. Facility event round-trip and original semantics remain unverified.'),
|
||
'test': observation([test, 'tests/s2/test_facility_service.gd', 'tests/s4/test_runtime_relationship_events.gd'], tokens,
|
||
'Selected generic suite sources searched; no formal suite run or dedicated original facility acceptance inferred.')},
|
||
'media_usage_slots': [{'slot_id': s['slot_id'], 'source_path': SLOTS,
|
||
'source_sha256': sha(raw(SLOTS)), 'source_record': s,
|
||
'status': 'unverified_required_slot', 'approved_asset': None,
|
||
'content_complete': False} for s in facility_slots],
|
||
'actual_required_asset_count': None, 'actual_required_asset_count_status': 'unknown',
|
||
'completed_asset_acceptance': 'unverified', 'original_content_completion': 'unverified',
|
||
'technical_as_content_credit': False})
|
||
if len(facilities) != 7:
|
||
raise ValueError('original facility coverage must be seven')
|
||
nonfacility = []
|
||
for category, requirements in CATEGORIES.items():
|
||
nonfacility.append({'difference_id': 'P13-03.' + category, 'category': category,
|
||
'requirement_ids': requirements, 'authority_path': spec['authority'],
|
||
'authority_sha256': sha(raw(spec['authority'])),
|
||
'related_original_sections': ['1.6', '2.5', '8.2', '8.3', '10.4', '10.6', '13', '14'],
|
||
'content_ids': None, 'content_ids_status': 'unknown', 'quantity': None,
|
||
'quantity_status': 'unknown', 'completed_asset_acceptance': 'unverified',
|
||
'original_content_completion': 'unverified', 'technical_as_content_credit': False,
|
||
'decisions': ['D08', 'D09'],
|
||
'difference': 'Required original presentation/content is not accepted by generic state processing. Final catalog, variants, production quantities and ownership remain unknown.',
|
||
'technical_observation': observation([event, 'scripts/events/event_definition.gd', 'scripts/ui/event_log_dialog.gd'],
|
||
[category], 'Neutral metadata search only; matches are not finished media, history semantics, story, voice or scenes.')})
|
||
additional = sorted(set(search_paths + [entry, runtime, event, save, test,
|
||
'tests/s2/test_facility_service.gd', 'tests/s4/test_runtime_relationship_events.gd',
|
||
'scripts/events/event_definition.gd', 'scripts/ui/event_log_dialog.gd']))
|
||
sources += [{'path': p, 'sha256': sha(raw(p)), 'applicability': 'current_checkout',
|
||
'scope': 'Bounded static observation; no executed connection/content acceptance.'} for p in additional]
|
||
return {'schema_version': 1, 'task_id': 'P13-03', 'acceptance_ids': ['P13-03'],
|
||
'requirement_ids': spec['package_requirement_ids'], 'validation_ids': ['p13-difference'],
|
||
'base_commit': BASE, 'spec_raw_sha256': SPEC_RAW, 'spec_runner_text_sha256': SPEC_RUNNER,
|
||
'scope': spec['scope'], 'sources': sources, 'facilities': facilities,
|
||
'nonfacility_differences': nonfacility,
|
||
'unknowns': {'D08': 'unknown', 'D09': 'unknown', 'final_content_ids': None,
|
||
'final_content_quantity': None, 'final_actual_asset_count': None},
|
||
'accounting': {'packages': 30, 'delivery_units': 88, 'no_partial_credit': True,
|
||
'package_completion_inferred': False, 'original_views_edited': False,
|
||
'content_completion_inferred': False, 'technical_as_content_credit': False},
|
||
'asset_policy': 'Usage slots are not image counts. Fallback, Kenney pack, mockup, candidate token, file existence and technical tests confer no content credit.',
|
||
'test_applicability': {name: {'status': 'not_applicable',
|
||
'reason': 'Records-only change; no product code, assets, save schema or values changed. No game/platform acceptance is claimed.'}
|
||
for name in ['runtime', 'native', 'Windows', 'engine', 'full_game_regression']},
|
||
'formal_validation': 'pending_runner', 'formal_claude_review': 'pending_runner',
|
||
'integration_gate': 'Required p13-difference validator and separate formal Claude review must pass before integration.'}
|
||
|
||
|
||
def verify(candidate, canonical):
|
||
# Compare every field, including pending/unknown fields: these must stay
|
||
# explicit and are not skipped as if they were content PASS.
|
||
if json.dumps(candidate, sort_keys=True, ensure_ascii=False) != json.dumps(canonical, sort_keys=True, ensure_ascii=False):
|
||
raise ValueError('coverage, source, observation, unknown or accounting mismatch')
|
||
|
||
|
||
def compact(document):
|
||
"""Lossless shared long-string table, with exact checked references."""
|
||
strings, identities = {}, {}
|
||
evidence, evidence_ids = {}, {}
|
||
contexts, context_ids = {}, {}
|
||
|
||
def context(record):
|
||
signature = json.dumps(record, sort_keys=True, ensure_ascii=False)
|
||
if signature not in context_ids:
|
||
identity = 'context_%04d' % (len(contexts) + 1)
|
||
context_ids[signature] = identity
|
||
contexts[identity] = visit(record)
|
||
return context_ids[signature]
|
||
|
||
def visit(value):
|
||
if isinstance(value, str) and len(value) >= 64:
|
||
if value not in identities:
|
||
identity = 'text_%04d' % (len(strings) + 1)
|
||
identities[value] = identity
|
||
strings[identity] = value
|
||
return {'text_ref': identities[value]}
|
||
if isinstance(value, list):
|
||
return [visit(item) for item in value]
|
||
if isinstance(value, dict):
|
||
result = {}
|
||
for key, item in value.items():
|
||
if key == 'evidence':
|
||
refs = []
|
||
for record in item:
|
||
signature = json.dumps(record, sort_keys=True, ensure_ascii=False)
|
||
if signature not in evidence_ids:
|
||
identity = 'evidence_%04d' % (len(evidence) + 1)
|
||
evidence_ids[signature] = identity
|
||
provenance = {k: record[k] for k in ('path', 'sha256', 'scope')}
|
||
evidence[identity] = {
|
||
'source_ref': context(provenance),
|
||
'search_ref': context({'searched_tokens': record['searched_tokens']}),
|
||
'matches': visit(record['matches'])}
|
||
refs.append({'evidence_ref': evidence_ids[signature]})
|
||
result[key] = refs
|
||
else:
|
||
result[key] = visit(item)
|
||
return result
|
||
return value
|
||
|
||
result = visit(document)
|
||
result['schema_version'] = 2
|
||
result['shared_text'] = strings
|
||
result['shared_evidence'] = evidence
|
||
result['shared_context'] = contexts
|
||
return result
|
||
|
||
|
||
def expand(document):
|
||
def visit(value):
|
||
if isinstance(value, dict):
|
||
if set(value) == {'text_ref'}:
|
||
return document['shared_text'][value['text_ref']]
|
||
if set(value) == {'evidence_ref'}:
|
||
return visit(document['shared_evidence'][value['evidence_ref']])
|
||
if set(value) == {'source_ref', 'search_ref', 'matches'}:
|
||
return {**visit(document['shared_context'][value['source_ref']]),
|
||
**visit(document['shared_context'][value['search_ref']]),
|
||
'matches': visit(value['matches'])}
|
||
return {key: visit(item) for key, item in value.items()
|
||
if key not in ('shared_text', 'shared_evidence', 'shared_context')}
|
||
if isinstance(value, list):
|
||
return [visit(item) for item in value]
|
||
return value
|
||
return visit(document)
|
||
|
||
|
||
def negative_checks(canonical):
|
||
cases = {}
|
||
mutations = {
|
||
'facility_omission': lambda d: d['facilities'].pop(),
|
||
'slot_omission': lambda d: d['facilities'][0]['media_usage_slots'].pop(),
|
||
'stale_pin': lambda d: d['sources'][0].update(sha256='0' * 64),
|
||
'duplicate_facility': lambda d: d['facilities'].append(copy.deepcopy(d['facilities'][0])),
|
||
'duplicate_slot': lambda d: d['facilities'][0]['media_usage_slots'].append(copy.deepcopy(d['facilities'][0]['media_usage_slots'][0])),
|
||
'false_content_completion': lambda d: d['facilities'][0].update(original_content_completion='completed'),
|
||
'false_asset_acceptance': lambda d: d['facilities'][0]['media_usage_slots'][0].update(approved_asset='fallback.png', content_complete=True),
|
||
'technical_as_content': lambda d: d['accounting'].update(technical_as_content_credit=True),
|
||
'unknown_to_zero': lambda d: d['unknowns'].update(final_content_quantity=0),
|
||
'nonfacility_omission': lambda d: d['nonfacility_differences'].pop(),
|
||
'proxy_mapping': lambda d: d['facilities'][-1].update(current_definition_ids=['mana_control_room']),
|
||
'connection_promotion': lambda d: d['facilities'][0]['connections']['runtime'].update(accepted=True),
|
||
'false_runner_pass': lambda d: d.update(formal_validation='pass'),
|
||
'missing_shared_text': lambda d: d['shared_text'].pop(next(iter(d['shared_text']))),
|
||
'missing_shared_evidence': lambda d: d['shared_evidence'].pop(next(iter(d['shared_evidence']))),
|
||
'missing_shared_context': lambda d: d['shared_context'].pop(next(iter(d['shared_context']))),
|
||
'altered_shared_text': lambda d: d['shared_text'].update({next(iter(d['shared_text'])): 'omitted'}),
|
||
'dangling_reference': lambda d: d['facilities'][0]['connections']['runtime'].update(note={'text_ref': 'missing'}),
|
||
}
|
||
for name, mutate in mutations.items():
|
||
changed = copy.deepcopy(canonical)
|
||
mutate(changed)
|
||
try:
|
||
verify(changed, canonical)
|
||
except ValueError:
|
||
cases[name] = 'rejected'
|
||
else:
|
||
raise ValueError('negative case was accepted: ' + name)
|
||
return cases
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description=__doc__)
|
||
parser.add_argument('--ledger', default=OUT + 'difference-ledger.json')
|
||
parser.add_argument('--negative-checks', action='store_true')
|
||
parser.add_argument('--verify-only', action='store_true',
|
||
help='Read-only verification; negative checks are always mandatory.')
|
||
args = parser.parse_args()
|
||
try:
|
||
canonical = compact(expected())
|
||
verify(load(args.ledger), canonical)
|
||
receipt = load(OUT + 'validation.json')
|
||
if receipt != receipt_expected():
|
||
raise ValueError('receipt mismatch or false formal acceptance')
|
||
if raw(OUT + 'DIFFERENCE_LEDGER.md').decode() != readable(expand(canonical)):
|
||
raise ValueError('readable ledger differs from machine ledger')
|
||
result = {'record_integrity': 'pass', 'content_acceptance': 'not_claimed',
|
||
'formal_review': 'external_runner_required'}
|
||
result['negative_checks'] = negative_checks(canonical)
|
||
print(json.dumps(result, ensure_ascii=False))
|
||
return 0
|
||
except (ValueError, OSError, KeyError, TypeError, subprocess.CalledProcessError) as error:
|
||
print(json.dumps({'record_integrity': 'fail', 'error': str(error)}))
|
||
return 1
|
||
|
||
|
||
def receipt_expected():
|
||
ledger = expand(load(OUT + 'difference-ledger.json'))
|
||
return {'task_id': 'P13-03', 'acceptance_ids': ['P13-03'], 'validation_ids': ['p13-difference'],
|
||
'requirement_ids': ledger['requirement_ids'],
|
||
'facility_ids': [f['original_id'] for f in ledger['facilities']],
|
||
'slot_ids': [s['slot_id'] for f in ledger['facilities'] for s in f['media_usage_slots']],
|
||
'difference_ids': [n['difference_id'] for n in ledger['nonfacility_differences']],
|
||
'source_applicability_records': ledger['sources'],
|
||
'output_pins': {p: sha(raw(p)) for p in [OUT + 'difference-ledger.json',
|
||
OUT + 'DIFFERENCE_LEDGER.md', 'tools/check_p13_content_difference.py']},
|
||
'status': 'blocked', 'formal_tests': 'not_run', 'formal_claude_review': 'not_run',
|
||
'worker_local_checks': {
|
||
'python3_B_integrity_and_mandatory_negatives': 'pass',
|
||
'ast_parse': 'pass',
|
||
'registered_argv': 'blocked: python executable is unavailable in this worker environment',
|
||
'git_diff_check': 'pass',
|
||
'acceptance': 'not_claimed'},
|
||
'command': 'python -B tools/check_p13_content_difference.py --verify-only',
|
||
'purpose': 'Verify record integrity and negative rejection only; not content acceptance.',
|
||
'source_applicability': 'Exact IDs, source SHA256 and scopes are in difference-ledger.json. Work package frozen base Git blob and current checkout are separately identified.',
|
||
'required_before_integration': ['Runner p13-difference pass', 'separate formal Claude review pass'],
|
||
'worker_tests_run': True,
|
||
'test_applicability': {name: 'Not applicable to records-only changes; no game or platform acceptance.'
|
||
for name in ['runtime', 'native', 'Windows', 'engine', 'full_game_regression']}}
|
||
|
||
|
||
def readable(d):
|
||
lines = ['# P13-03 内容差異台帳', '',
|
||
'記録の提出であり、ゲーム内容・P13パッケージの完成認定ではない。原30パッケージ/88成果、部分点なし。原子・状態台帳は変更しない。', '',
|
||
'通常入口、Runtime、イベント状態、保存、試験はすべて静的観測・未検証。汎用処理は原施設固有の接続や完成素材を証明しない。', '',
|
||
'機械台帳のshared_text/text_refとshared_evidence/evidence_refは重複証拠の共有参照。検索語、一致行番号・全文、相対出典パス、SHAを省略せず保持し、チェッカーは全参照と本文を照合する。作業計画は契約baseのGit blobを固定入力として検証する。別管理の現行進捗は内容完成の証拠にしない。', '',
|
||
'施設の原典IDは旧対応表の1始まりの位置を保持する。現行の data/facilities 全定義とカタログを名前・既存IDで再検索した。マナ制御室をマナ炉に代用しない。', '',
|
||
'|原典ID|章|原典名|現行定義候補|使用スロット数|完成素材・原内容|',
|
||
'|---|---|---|---|---|---|']
|
||
for f in d['facilities']:
|
||
lines.append('|%s|%s|%s|%s|%d|未検証|' % (f['original_id'], f['original']['section'],
|
||
f['original']['name'], ', '.join(f['current_definition_ids']) or '対象検索範囲で欠落', len(f['media_usage_slots'])))
|
||
lines += ['', '風俗個室は通常UIリストとRuntime読込元に候補がある。闇市場は拡張定義のみで通常読込元に含まれない。他の5施設は対象定義集合で対応が見つからない。実行・保存再開・施設固有イベントの受入は未検証。', '',
|
||
'## 施設別の観測と使用スロット', '']
|
||
lines += ['共有観測範囲(各施設の具体的検索結果は機械台帳に保持):', '']
|
||
for view in VIEWS:
|
||
lines += ['- ' + view + ': ' + d['facilities'][0]['connections'][view]['note']]
|
||
lines += ['', '共有出典・検索語はshared_contextのsource_ref/search_refから復元する。各施設の各観測は個別に参照し、合格接続を意味しない。', '']
|
||
for f in d['facilities']:
|
||
lines += ['### ' + f['original_id'] + ' / ' + f['original']['name'], '']
|
||
for view in VIEWS:
|
||
o = f['connections'][view]
|
||
lines += ['- ' + view + ': ' + o['status'] + '(検索語・一致行本文・出典SHAは機械台帳参照)']
|
||
lines += ['', '使用スロット(すべて unverified_required_slot、approved_asset=null):', '']
|
||
lines += ['- `' + s['slot_id'] + '`' for s in f['media_usage_slots']]
|
||
lines += ['', '実制作資産数はunknown/null。スロット数を画像数に転用しない。', '']
|
||
lines += ['## 施設外の内容差異', '']
|
||
for n in d['nonfacility_differences']:
|
||
lines += ['- `' + n['difference_id'] + '` (' + ', '.join(n['requirement_ids']) + '): ' + n['difference']]
|
||
lines += ['', 'event/overlay/history/story/voice/scene の内容ID・数量・受入はunknownまたは未検証。D08(内容範囲)/D09(品質・量・担当・権利)は未決定。中立メタデータのみを記録し、場面は制作・描写しない。', '',
|
||
'## 出典・適用範囲', '', '機械台帳に各観測の検索語・一致行・本文・実SHAを保持する。旧対応表は原典識別に利用し、現行対応は再検索で記録する。素材台帳は使用スロットと候補の証拠のみ。inventory_summaryの290 media files、0 audio files、360 slots、0 approved assetsは既存監査の限定集計であり、最終必要量を示さない。', '']
|
||
lines += ['- `' + s['path'] + '` / ' + s['applicability'] + ' / SHA256 `' + s['sha256'] + '`' for s in d['sources']]
|
||
lines += ['', '## 統合監査注記', '']
|
||
lines += ['- ' + note for note in INTEGRATION_NOTES]
|
||
lines += ['', '## 検証・統合条件', '',
|
||
'`python -B tools/check_p13_content_difference.py --verify-only` は記録整合性と欠落・重複・古いpin・誤完成・技術から内容への昇格・unknownのゼロ化・共有参照改変等の拒否を必ず検証する。--negative-checksも互換引数として保持する。workerのローカル診断は正式受入ではない。Runner正式validatorと別セッションの正式Claudeレビュー合格後にのみ統合できる。', '',
|
||
'Runtime/native/Windows/engine/full game regressionは記録だけの変更に適用外(製品コード・資産・保存形式・値の変更なし)。この適用外をゲームの合格に転用しない。Kenney・fallback・mockup・コード候補・ファイル存在・状態試験から完成資産を推定しない。', '']
|
||
return '\n'.join(lines)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
sys.exit(main())
|