Fix inventory layout integration and qualify ordinary UI and radio guidance

This commit is contained in:
2026-09-19 18:17:51 +02:00
parent 5c7719c19c
commit 67de3752fb
125 changed files with 18310 additions and 42 deletions
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""Reproduce stock inventory UI behavior in a diagnostic-only vanilla-dependent addon.
Never loads successor addons or edits installed/original content. Native errors remain failed.
"""
import argparse
import hashlib
import json
from pathlib import Path
import re
import shutil
import subprocess
import sys
import tempfile
from native_report import analyze
ROOT = Path(__file__).resolve().parents[1]
FIXTURE = ROOT/'tests/native/inventory_control'
HELPERS = ('headless_workbench.py','headless_game.py','private_prefix_cleanup.py','native_provenance.py','source_manifest.py','native_report.py','archive_native.py')
EXPECTED_ERROR = 'OverlayWidget Frame0 has invalid slot GridWidgetSlot for slot manipulation function used, expected: LayoutSlot'
def fixture_inventory(folder):
if folder.is_symlink() or not folder.is_dir():
raise ValueError('Control fixture must be a regular directory')
result = {}
for path in sorted(folder.rglob('*')):
if path.is_symlink():
raise ValueError('Control fixture must not contain symlinks')
if path.is_file():
if path.suffix == '.gproj' and path != folder/'addon.gproj':
raise ValueError('Control fixture must contain only its root addon project')
if path.suffix not in {'.c','.gproj','.et','.ent','.layer','.meta','.conf','.md'}:
raise ValueError('Unexpected control fixture input: '+str(path))
result[str(path.relative_to(folder))] = hashlib.sha256(path.read_bytes()).hexdigest()
elif not path.is_dir():
raise ValueError('Control fixture must contain only regular source files/directories')
if not result or 'addon.gproj' not in result:
raise ValueError('Missing diagnostic fixture')
project = (folder/'addon.gproj').read_text()
guids = re.findall(r'\bGUID\s+"([A-F0-9]{16})"', project)
dependencies = re.findall(r'\bDependencies\s*\{([^{}]*)\}', project)
if (len(guids) != 1 or len(re.findall(r'\bGUID\b', project)) != 1
or len(dependencies) != 1 or len(re.findall(r'\bDependencies\b', project)) != 1
or not re.fullmatch(r'\s*"58D0FB3206B6F859"\s*', dependencies[0])):
raise ValueError('Control must depend only on installed vanilla')
guid = guids[0]
if guid in {'5614BBCCBB55ED1C','58D0FB3206B6F859','2528466DE220D91C','5192138CB9D5B911','D31039C7C53E348A','FA804F411BDDF2C6','9AAEE9041D79809E'}:
raise ValueError('Control must use its own addon identity')
return result,guid
def harness_source(source):
replacements = {
'ERR_ValidateBaseWorldPlugin':'ERRIC_InventoryControlPlugin',
'Worlds/ERR_Everon.ent':'Worlds/ERRIC_Inventory.ent',
"'-errPlay', '1'":"'-errPlay', '1', '-erricInventoryControl', '1'",
}
for old,new in replacements.items():
if old not in source:
raise ValueError('Known hidden harness entry changed: '+old)
source = source.replace(old,new)
return source
def control_evidence(content,guid):
report = analyze(content, 'console.log')
blocks = []
for section in content.split('Loaded addons:')[1:]:
block = []
for line in section.splitlines()[1:]:
match = re.search(r"ENGINE\s*:\s*gproj: .*? guid: '([A-F0-9]{16})'\s*$", line)
if not match: break
block.append(match[1])
blocks.append(block)
guids = blocks[0] if blocks else []
isolation = bool(blocks) and all(len(block)==3 and set(block)=={'5614BBCCBB55ED1C','58D0FB3206B6F859',guid} for block in blocks)
errors = report['engine_errors']
target_errors = [error for error in errors if re.search(r'\bGUI\s+\(E\)', error) and EXPECTED_ERROR in error]
other_errors = [error for error in errors if error not in target_errors]
target_pair = (isolation and report['smoke_completions'] == 1
and not report['script_or_runtime_failures'] and not report['missing_end_screen']
and len(target_errors) == 2)
reproduced = (isolation and report['smoke_completions']==1 and not report['script_or_runtime_failures'] and not report['missing_end_screen']
and len(errors)==2 and all(EXPECTED_ERROR in error for error in errors))
return {'loaded_addon_guids':guids,'loaded_addon_blocks':blocks,'only_vanilla_and_control_loaded':isolation,
'target_gui_error_count':len(target_errors),'target_gui_pair_observed':target_pair,
'other_engine_errors':other_errors,
'stock_slot_error_reproduced':reproduced,'native_clean':not errors and not report['script_or_runtime_failures'],
'native_report':report,'successor_or_release_qualified':False}
def main():
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--compat',type=Path,default=Path('/tmp/erri-headless-compat'))
p.add_argument('--profile',type=Path,default=Path('/tmp/erri-inventory-control-profile'))
p.add_argument('--timeout',type=int,default=150)
p.add_argument('--archive-name',required=True)
a=p.parse_args()
if a.compat != Path('/tmp/erri-headless-compat') or a.compat.resolve()!=a.compat:
p.error('Only the established disposable test prefix is supported')
if not 90<=a.timeout<=300 or not re.fullmatch(r'inventory-control-[a-z0-9-]+',a.archive_name):
p.error('Use a90..300 second bound and a unique inventory-control-* archive name')
dest=ROOT/'docs/evidence'/a.archive_name
if dest.exists() or dest.is_symlink():p.error('Archive must not preexist')
profile=a.profile
if profile != profile.resolve() or not profile.is_relative_to('/tmp') or profile==Path('/tmp') or profile.is_relative_to(a.compat) or a.compat.is_relative_to(profile):
p.error('Use a separate canonical disposable /tmp profile')
marker=profile/'.erri-inventory-control-profile.json';marker_value={'kind':'erri-vanilla-inventory-control','version':1}
if profile.exists() and (not marker.is_file() or marker.is_symlink() or json.loads(marker.read_text())!=marker_value):
p.error('Existing profile is not owned by this control')
original,guid=fixture_inventory(FIXTURE)
bundle=Path(tempfile.mkdtemp(prefix='erri-inventory-control-run-'))
(bundle/'addons').mkdir();shutil.copytree(FIXTURE,bundle/'addons/core');(bundle/'tools').mkdir()
for name in HELPERS:shutil.copyfile(ROOT/'tools'/name,bundle/'tools'/name)
runner=bundle/'tools/headless_workbench.py';runner.write_text(harness_source(runner.read_text()))
profile.mkdir(parents=True,exist_ok=True);marker.write_text(json.dumps(marker_value)+'\n')
tool_hashes={name:hashlib.sha256((bundle/'tools'/name).read_bytes()).hexdigest() for name in HELPERS}
command=[sys.executable,str(runner),'--compat',str(a.compat),'--profile',str(profile),'--project','core','--smoke','victory','--rounds','1','--timeout',str(a.timeout)]
before=set((profile/'logs').glob('logs_*')) if (profile/'logs').exists() else set()
with (bundle/'control-launch.log').open('w') as output:
result=subprocess.run(command,stdout=output,stderr=subprocess.STDOUT)
fresh=set((profile/'logs').glob('logs_*'))-before
if len(fresh)!=1:
print((bundle/'control-launch.log').read_text(),end='');raise RuntimeError('Control did not produce exactly one session; retained bundle '+str(bundle))
session=next(iter(fresh));runner_report=json.loads((session/'runner.json').read_text())
if runner_report.get('cleanup_confirmed') is not True or runner_report.get('source_unchanged_after_run') is not True:
raise RuntimeError('Control cleanup/source seal incomplete; retained bundle '+str(bundle))
subprocess.run([sys.executable,str(bundle/'tools/archive_native.py'),str(session),a.archive_name],check=False)
staged=bundle/'docs/evidence'/a.archive_name
if not staged.is_dir():raise RuntimeError('Native archive was not created; retained bundle '+str(bundle))
for capture in sorted([*session.glob('erric-*.png'), *session.glob('erric-*.bmp')]):
if capture.is_file() and not capture.is_symlink(): shutil.copyfile(capture,staged/capture.name)
after,_=fixture_inventory(FIXTURE)
if original!=after:raise RuntimeError('Authored control fixture changed during run')
review=control_evidence((session/'console.log').read_text(errors='replace'),guid)
review.update(scope='Scripted stock-character inventory control with vanilla plus one diagnostic addon only; no normal-input, successor gameplay or release qualification.',
native_runner_exit=result.returncode,session=str(session),bundle=str(bundle),command=command,
fixture_sha256=original,harness_sha256=tool_hashes,cleanup_confirmed=True,source_unchanged_after_run=True)
(staged/'control-review.json').write_text(json.dumps(review,indent=2)+'\n')
shutil.copyfile(bundle/'control-launch.log',staged/'control-launch.log.txt')
# Preserve the actual remapped harness which launched this isolated input.
(staged/'harness.py.txt').write_bytes(runner.read_bytes())
(staged/'control-extra-sha256.json').write_text(json.dumps({name:hashlib.sha256((staged/name).read_bytes()).hexdigest() for name in ('control-review.json','control-launch.log.txt','harness.py.txt')},indent=2)+'\n')
shutil.copytree(staged,dest)
print(json.dumps({'archive':str(dest),**review},indent=2))
return 0 if review['stock_slot_error_reproduced'] else 1
if __name__=='__main__':raise SystemExit(main())