Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
122 lines
7.0 KiB
Python
122 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Archive the two stopped observer runs; verify launch sources and never overwrite."""
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import shutil
|
|
import sys
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
ROOT = HERE.parents[2]
|
|
sys.path.insert(0, str(ROOT / 'tools'))
|
|
from native_provenance import archive_source, encoded, read_launch
|
|
from native_report import analyze
|
|
|
|
RELEVANT = [
|
|
'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c',
|
|
'addons/core/Scripts/Game/ERR/00_Core/ERR_ObserverTargets.c',
|
|
'addons/core/Scripts/Game/ERR/00_Core/ERR_ObserverPlayerView.c',
|
|
'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c',
|
|
'addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c',
|
|
'addons/core/Prefabs/ERR/SpectatorCamera.et',
|
|
'addons/core/Configs/ERR/ObserverInput.conf',
|
|
'addons/core/Configs/ERR/Tests/LegacySites.conf',
|
|
]
|
|
|
|
|
|
def digest(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def copy(source, destination):
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
with destination.open('xb') as stream:
|
|
stream.write(source.read_bytes())
|
|
assert digest(source) == digest(destination)
|
|
|
|
|
|
def normalized(errors):
|
|
return Counter(re.sub(r'\s+', ' ', re.sub(r'^\d\d:\d\d:\d\d\.\d+\s+', '', line)) for line in errors)
|
|
|
|
|
|
runs = {}
|
|
for tag in ('a01', 'a02'):
|
|
profile = Path('/tmp/erri-observer-orbit-run-' + tag)
|
|
source = Path('/tmp/erri-observer-orbit-source-' + tag)
|
|
sessions = list((profile / 'logs').glob('logs_*'))
|
|
assert len(sessions) == 1
|
|
session = sessions[0]
|
|
manifest, metadata, runner = read_launch(session)
|
|
assert runner['cleanup_confirmed'] is True
|
|
assert runner['external_addons_unchanged_after_run'] is True
|
|
manifest_bytes, provenance, launched = archive_source(session, source)
|
|
assert launched and metadata['source_root'] == str(source)
|
|
destination = HERE / tag
|
|
destination.mkdir(exist_ok=False)
|
|
for name in ('console.log', 'script.log', 'error.log', 'runner.json',
|
|
'external-addons-inputs.json', 'headless-launch.log'):
|
|
copy(session / name, destination / (name + '.txt'))
|
|
for name in ('launch-source-sha256.json', 'launch-source.json'):
|
|
copy(session / name, destination / name)
|
|
(destination / 'source-sha256.json').write_bytes(manifest_bytes)
|
|
(destination / 'source-provenance.json').write_bytes(encoded(provenance))
|
|
for capture in sorted([*session.glob('err-*.png'), *session.glob('err-*.bmp')]):
|
|
copy(capture, destination / capture.name)
|
|
if (profile / 'fixture-inputs.json').exists():
|
|
copy(profile / 'fixture-inputs.json', destination / 'fixture-inputs.json')
|
|
runner_output = Path('/tmp/erri-observer-orbit-' + tag + '-runner.log')
|
|
if runner_output.exists():
|
|
copy(runner_output, destination / 'runner-output.log.txt')
|
|
source_matches = {}
|
|
for name in RELEVANT:
|
|
assert digest(source / name) == manifest[name]
|
|
copy(source / name, destination / 'source' / name)
|
|
source_matches[name] = {
|
|
'launch_sha256': manifest[name],
|
|
'archived_sha256': digest(destination / 'source' / name),
|
|
'current_repository_matches_launch': digest(ROOT / name) == manifest[name],
|
|
}
|
|
report = analyze((destination / 'console.log.txt').read_text(errors='replace'),
|
|
str((destination / 'console.log.txt').relative_to(ROOT)))
|
|
report['scope'] = 'Local Workbench; native action values and production frame output, then actual camera application. No physical mouse, remote streaming or multiplayer qualification.'
|
|
(destination / 'report.json').write_bytes(encoded(report))
|
|
(destination / 'relevant-source-matches.json').write_bytes(encoded(source_matches))
|
|
runs[tag] = {'manifest': manifest, 'report': report, 'runner': runner}
|
|
|
|
baseline = Path('/tmp/err-observer-orbit-before.c')
|
|
copy(baseline, HERE / 'baseline/ERR_Observer.c')
|
|
copy(ROOT / 'tests/test_observer_orbit.py', HERE / 'regression/test_observer_orbit.py')
|
|
copy(Path('/tmp/erri-observer-orbit-check-unsandboxed.log'), HERE / 'full-check.log.txt')
|
|
|
|
one, two = runs['a01'], runs['a02']
|
|
changed = sorted(path for path in set(one['manifest']) | set(two['manifest'])
|
|
if one['manifest'].get(path) != two['manifest'].get(path))
|
|
assert changed == ['addons/core/Configs/ERR/Tests/LegacySites.conf']
|
|
before, after = normalized(one['report']['engine_errors']), normalized(two['report']['engine_errors'])
|
|
review = {
|
|
'finding': 'Orbit input and pose calculation were inside the selected-target branch, freezing the fallback anchor when alone or all surviving players were unconscious.',
|
|
'fix': 'Keep yaw, pitch and zoom around the authorized fallback anchor without requiring a locally resolved target; preserve collision, input and player-view restrictions.',
|
|
'native_a01': {'result': one['runner']['result'], 'assertions': one['report']['assertions_passed'],
|
|
'reason': 'Historical hillside extraction test marker has no second US rescue landing footprint; mission preparation fails before camera regression.'},
|
|
'native_a02': {'result': two['runner']['result'], 'assertions': two['report']['assertions_passed'],
|
|
'completions': two['report']['smoke_completions'],
|
|
'script_or_runtime_failure_count': len(two['report']['script_or_runtime_failures']),
|
|
'engine_error_count': len(two['report']['engine_errors'])},
|
|
'a01_to_a02_changed_launch_sources': changed,
|
|
'a02_setup_only_arrangement': 'Move only the legacy fixture extraction marker from [7000,0,7500] to previously surveyed flat airport grounds [4776.55,0,11528.9]. No camera or casualty source changes between native runs.',
|
|
'a02_engine_error_signatures_absent_from_a01': sorted(set(after) - set(before)),
|
|
'a02_additional_occurrences_of_existing_engine_errors': dict(after - before),
|
|
'baseline_source': {'path': 'baseline/ERR_Observer.c', 'sha256': digest(baseline),
|
|
'qualification': 'Pre-fix source retained for host regression reproduction; neither a01 nor a02 is a native pre-fix run.'},
|
|
'regression_snapshot': {'path': 'regression/test_observer_orbit.py',
|
|
'sha256': digest(ROOT / 'tests/test_observer_orbit.py'),
|
|
'qualification': 'Host regression copied at archive time, outside native launch manifest.'},
|
|
'full_check_limitation': 'Full check stops at the existing ERRM_AtlasLeaf.et missing saved root ID. Exact failure output retained in full-check.log.txt.',
|
|
'cleanup_source_and_external_inputs': 'Both runners confirm cleanup and unchanged launched addon and external sources; source manifests and exact raw logs verified at archive time.',
|
|
'limitations': 'Synthetic native action/frame inputs, not physical mouse delivery. One local player and a physical stand-in; no dedicated multiplayer target streaming or live-server deployment.',
|
|
}
|
|
(HERE / 'review.json').write_bytes(encoded(review))
|
|
print(json.dumps(review, indent=2))
|