Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
79 lines
6.4 KiB
Python
79 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Evidence inventory for P22; file presence never certifies production readiness."""
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import struct
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
OUT = ROOT / 'docs/plans/original-completion-2026-09-29/execution/assets'
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
def digest(p):
|
|
return hashlib.sha256(p.read_bytes()).hexdigest()
|
|
def save(name, data):
|
|
(OUT/name).write_text(json.dumps(data, ensure_ascii=False, indent=2)+'\n')
|
|
|
|
references = {}
|
|
for folder in ('scripts', 'scenes', 'autoload', 'data', 'assets'):
|
|
for source in (ROOT/folder).rglob('*'):
|
|
if source.suffix not in ('.gd', '.tres', '.tscn') or not source.is_file():
|
|
continue
|
|
for path in set(re.findall(r'res://([^"\s]+\.(?:png|svg|jpg|jpeg|webp|ogg|wav|mp3|ttf|otf))', source.read_text())):
|
|
references.setdefault(path, []).append(str(source.relative_to(ROOT)))
|
|
media = []
|
|
for p in sorted((ROOT/'assets').rglob('*')):
|
|
if not p.is_file() or p.suffix.lower() not in ('.png','.svg','.jpg','.jpeg','.webp','.ogg','.wav','.mp3','.ttf','.otf'):
|
|
continue
|
|
path = str(p.relative_to(ROOT))
|
|
row = {'path':path, 'bytes':p.stat().st_size, 'sha256':digest(p), 'literal_references':references.get(path, []), 'acceptance':'not_reviewed', 'provenance':'unverified'}
|
|
if p.suffix.lower() == '.png':
|
|
header = p.read_bytes()[:24]
|
|
if header[:8] == b'\x89PNG\r\n\x1a\n':
|
|
row['width'],row['height'] = struct.unpack('>II',header[16:24])
|
|
if path.startswith('assets/mockups/'):
|
|
row['purpose']='reference_mockup_not_shipping_acceptance'
|
|
elif path.startswith('assets/kenney/'):
|
|
pack = p.relative_to(ROOT/'assets/kenney').parts[0]
|
|
license_path = ROOT/'assets/kenney'/pack/'License.txt'
|
|
row.update(provenance='Kenney', creator='Kenney', license=str(license_path.relative_to(ROOT)), license_present=license_path.exists())
|
|
if license_path.exists():
|
|
row['license_sha256']=digest(license_path)
|
|
row['license_declares_cc0']='CC0' in license_path.read_text()
|
|
else:
|
|
row['purpose']='existing_theme_or_icon_requires_source_and_quality_review'
|
|
media.append(row)
|
|
save('media_inventory.json', media)
|
|
|
|
facility_mapping = ROOT/'docs/plans/original-completion-2026-09-29/execution/original_facility_mapping.json'
|
|
facilities = json.loads(facility_mapping.read_text())
|
|
renderer = ROOT/'scripts/ui/pixel_tower_art.gd'
|
|
text = renderer.read_text()
|
|
slots = []
|
|
for index,f in enumerate(facilities,1):
|
|
key = f['definition_id'] or f'original_facility_{index:02}'
|
|
for level in range(1,4):
|
|
for layer in ('front','back'):
|
|
for surface in ('exterior','interior'):
|
|
slots.append({'slot_id':f'{key}.lv{level}.{layer}.{surface}', 'requirement':['B-13.3',f"B-{f['section']}"], 'facility_name':f['name'], 'definition_id':f['definition_id'], 'level':level, 'layer':layer, 'surface':surface, 'original_placement':f['original_layer_zone'], 'creator':'unassigned', 'status':'unverified_required_slot', 'approved_asset':None, 'existing_candidate':str(renderer.relative_to(ROOT)) if f['definition_id'] and ('&"'+f['definition_id']+'"') in text else None, 'note':'Code token is only a search lead; it does not prove a dedicated level/layer/surface variant. These are usage slots, not a committed image count.'})
|
|
save('facility_usage_slots.json',slots)
|
|
|
|
people = [('anri','アンリ'),('fine','フィーネ'),('tomari','トマリ'),('amelia','アメリア'),('liz','リズ'),('mimi','ミミ'),('satsuki','サツキ')]
|
|
character_slots = []
|
|
for key,name in people:
|
|
for kind in ('portrait_base','portrait_expression','portrait_costume','portrait_damage','portrait_dirt','sd_idle','sd_walk','sd_work','sd_collapsed','sd_carry','sd_rescue','sd_rest'):
|
|
character_slots.append({'slot_id':f'heroine.{key}.{kind}', 'character':name, 'requirement':['B-6.2','B-13.1' if kind.startswith('portrait') else 'B-13.2'], 'kind':kind, 'required_variant_count':1 if kind=='portrait_base' else None, 'creator':'unassigned', 'status':'unverified_required_slot', 'approved_asset':None, 'candidate':'scripts/ui/pixel_people.gd' if kind.startswith('sd_') else None, 'note':'Generic procedural poses do not prove character-specific appearance or role-specific animation.'})
|
|
save('character_usage_slots.json',character_slots)
|
|
|
|
other = [
|
|
('background.front','B-13.3','scenes/main/world_preview.gd'),('background.back','B-13.3','scenes/main/world_preview.gd'),
|
|
('ui.front_skin','B-13.4','assets/themes/japanese_fantasy/visual-theme-v1.tres'),('ui.back_skin','B-13.4','assets/themes/japanese_fantasy/visual-theme-v1.tres'),
|
|
('ui.emotion_graph','B-13.4',None),('ui.camera_overlay','B-13.4',None),
|
|
('effect.shield','B-13.2',None),('effect.smoke','B-13.2',None),('effect.mana_light','B-13.2',None),
|
|
('audio.front_bgm','B-13.5',None),('audio.back_arrangement','B-13.5',None),('audio.front_ambience','B-13.5',None),('audio.back_ambience','B-13.5',None),('audio.ui','B-13.5',None),('audio.character_voice','B-13.5',None),
|
|
('other_characters','B-13.1',None),('restricted_content_unproduced','B-13.1',None)]
|
|
save('other_usage_slots.json',[{'slot_id':k,'requirement':(['B-12.3','B-13.1','B-13.2','B-13.5'] if k=='restricted_content_unproduced' else [req]),'candidate':candidate,'status':'not_produced_outside_current_content_scope' if k=='restricted_content_unproduced' else 'unverified_required_slot','creator':'unassigned','required_variant_count':None,'approved_asset':None} for k,req,candidate in other])
|
|
summary = {'facility_mapping':str(facility_mapping.relative_to(ROOT)), 'facility_mapping_sha256':digest(facility_mapping), 'baseline_sha256':digest(ROOT/'docs/traceability/original/BASELINE.txt'), 'media_files':len(media), 'literal_referenced_media_files':sum(bool(x['literal_references']) for x in media), 'audio_files':sum(Path(x['path']).suffix in ('.wav','.ogg','.mp3') for x in media), 'facility_count':len(facilities), 'facility_usage_slots':len(slots), 'named_heroines':len(people), 'character_usage_slot_groups':len(character_slots), 'other_slot_groups':len(other), 'approved_assets':0, 'missing_literal_references':[p for p in references if not (ROOT/p).exists()], 'scope':'Inventory and candidate mappings only. No asset is promoted to complete by file existence, procedural fallback, or a passing gameplay test. Variant counts and production ownership remain unresolved.'}
|
|
save('inventory_summary.json',summary)
|
|
print(json.dumps(summary,ensure_ascii=False))
|