Files
EscapistsReintroduced/docs/evidence/source-refresh-2026-10-02-gameplay-fixes/package-helpers/finalize_docs.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

102 lines
13 KiB
Python

#!/usr/bin/env python3
"""Scratch draft by default. Parent may use --verified, then --verified --apply."""
import argparse,hashlib,json,shutil,subprocess,sys
from pathlib import Path
HERE=Path(__file__).resolve().parent;BASE=Path('/tmp/erri-playtest-1631')
ROOT=Path('/home/jakub/devel/escapistsrereintroduced');FROZEN=ROOT/'.build/playtest-source-2026-10-02-gameplay-fixes'
BUILD=ROOT/'.build/playtest-2026-10-02-gameplay-fixes';EVIDENCE=ROOT/'docs/evidence/playtest-refresh-2026-10-02-gameplay-fixes'
PE=ROOT/'docs/evidence/package-2026-10-02-gameplay-fixes';NE=ROOT/'docs/evidence/native-2026-10-02-gameplay-fixes-normal';SE=ROOT/'docs/evidence/packed-2026-10-02-gameplay-fixes'
REFUSAL=ROOT/'docs/evidence/native-2026-10-02-gameplay-fixes-cleanup-refusal'
load=lambda p:json.loads(p.read_text())
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
def write(p,v):p.parent.mkdir(parents=True,exist_ok=True);p.write_text(json.dumps(v,indent=2)+'\n')
def copy(a,b):
b.parent.mkdir(parents=True,exist_ok=True)
if a.is_dir():shutil.copytree(a,b,dirs_exist_ok=True)
else:shutil.copyfile(a,b)
def mismatches(root,m):return {n:{'expected':h,'actual':sha(root/n) if (root/n).is_file() else None} for n,h in m.items() if not (root/n).is_file() or sha(root/n)!=h}
def main():
ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--verified',action='store_true');ap.add_argument('--apply',action='store_true')
ap.add_argument('--review',type=Path,default=Path('/tmp/erri-playtest-review-1631/review.json'))
ap.add_argument('--approved-concurrent',type=Path,help='Optional independently reviewed flat path-to-current-hash map for inherited concurrent changes; never docs')
a=ap.parse_args();assert not a.apply or a.verified,'--apply requires --verified'
baseline=load(BASE/'baseline.json');frozen=load(BASE/'frozen-source.json');approved=load(a.approved_concurrent) if a.approved_concurrent else {}
docs={'README.md':HERE/'docs-baseline/README.md','docs/current-status.md':HERE/'docs-baseline/current-status.md','docs/playtest-build.md':HERE/'docs-baseline/playtest-build.md'}
assert not set(docs)&set(approved),'Concurrent documentation requires explicit rebase; no hash allowlist bypass'
assert all((ROOT/n).read_bytes()==p.read_bytes() for n,p in docs.items()),'Live docs changed; rebase scratch and review before applying'
sys.path.insert(0,str(FROZEN/'tools'));from source_manifest import addon_manifest
current=addon_manifest(ROOT);source_delta={n:{'frozen':frozen.get(n),'live':current.get(n)} for n in set(frozen)|set(current) if frozen.get(n)!=current.get(n)}
check={'index_unchanged':hashlib.sha256(subprocess.check_output(['git','diff','--cached','--binary'],cwd=ROOT)).hexdigest()==baseline['index_diff_sha256'],
'inherited_paths':len(baseline['working_modified']),'inherited_mismatches':mismatches(ROOT,{n:approved.get(n,h) for n,h in baseline['working_modified'].items()}),
'frozen_source_mismatches':mismatches(FROZEN,frozen),'live_frozen_delta':source_delta,'concurrent_approval':approved,
'docs_before':{n:sha(p) for n,p in docs.items()}}
prior=ROOT/'.build/playtest-2026-10-02-hq-memory-menu-fix';priorseal=ROOT/'docs/evidence/playtest-refresh-2026-10-02-hq-memory-menu-fix/candidate-sha256.json';pm=load(priorseal)
check['previous_package_mismatches']=mismatches(prior,pm);check['previous_package_extra_files']=sorted(str(p.relative_to(prior)) for p in prior.rglob('*') if p.is_file() and str(p.relative_to(prior)) not in pm)
reference=load(Path('/tmp/erri-heartbeat-20260930-1542-reference.json'));check['original_reference_mismatches']=mismatches(Path('/home/jakub/devel/escapists'),reference);check['original_reference_files']=len(reference)
suite=load(BASE/'suite-result.json') if (BASE/'suite-result.json').exists() else None
count=suite['regression_programs'] if suite else 'pending';normal=None;packed=None;handoff=None
if a.verified:
assert a.review.is_file(),'Independent final review missing'
assert check['index_unchanged'] and not check['inherited_mismatches'] and not check['frozen_source_mismatches'],'Input preservation failed'
assert not check['previous_package_mismatches'] and not check['previous_package_extra_files'] and not check['original_reference_mismatches'],'Original/prior package changed'
assert all(approved.get(n)==v['live'] for n,v in source_delta.items()),'Concurrent live source changed without reviewed exact hashes'
assert suite and suite['exit']==0 and suite['inputs_unchanged'] and not suite.get('resumed'),'Need fresh full frozen suite'
assert len(suite['programs'])==count==len({p['path'] for p in suite['programs']}) and all(p['exit']==0 for p in suite['programs'])
inputs=load(BASE/'suite-inputs.json');assert not mismatches(FROZEN,inputs) and all(inputs.get(n)==h for n,h in frozen.items())
handoff=load(BUILD/'handoff.json');assert handoff==load(PE/'handoff.json')
assert handoff['authored_files']==len(frozen) and handoff['normal_mission_ready'] and handoff['local_participant_verified']
assert handoff['repository_regression_programs']==count and not handoff['release_qualified'] and not handoff['published']
assert handoff['qualification_source_root']==str(FROZEN)
assert load(PE/'authored-before.json')==frozen
normal=load(NE/'report.json');runner=load(NE/'runner.json.txt');assert load(NE/'source-sha256.json')==frozen==load(NE/'launch-source-sha256.json')
assert normal['smoke_completions']==1 and not normal['script_or_runtime_failures'] and len(normal['engine_errors'])==13
assert runner['cleanup_confirmed'] and runner['source_unchanged_after_run'] and runner['external_addons_unchanged_after_run'] and sha(NE/'console.log.txt')==runner['console_sha256']
packed=load(SE/'packed-startup-report.json');pr=load(SE/'packed-runner.json');assert not packed['script_or_runtime_failures'] and len(packed['engine_errors'])==17
assert packed['normal_mission_ready'] and packed['local_participant_verified'] and pr['cleanup_confirmed'] and pr['packed_inputs_unchanged_after_run'] and pr['packed_external_inputs_unchanged_after_run']
from packed_external import inventory
assert inventory(BUILD/'packages')==handoff['runtime_files'] and len(handoff['runtime_files'])==3
from source_bundle import inventory as authored_inventory
assert authored_inventory(BUILD/'source')==frozen and sha(BUILD/'source.zip')==handoff['source_zip_sha256']
assert (REFUSAL/'archive-review.json').is_file() and (REFUSAL/'runner.json').is_file(),'Initial cleanup refusal must remain retained'
assert load(REFUSAL/'runner.json').get('cleanup_confirmed') is False,'Initial refusal must not be relabeled as confirmed cleanup'
check['independent_review_sha256']=sha(a.review)
source_count=len(frozen);payload_count=handoff['pak_payload_files'] if handoff else 'pending';runtime_count=len(handoff['runtime_files']) if handoff else 'pending'
features='This frozen snapshot includes the rifle-supply callback cancellation guard, bounded HQ road-survey fairness, and the inherited terrain-selection, presentation and admin-item integration. These are included source changes; this handoff does not claim manual UI, physical resupply or physical interception qualification.'
limits='It remains an **unqualified development candidate**. Complete human escape, real multiplayer/JIP/reconnect/twelve-player play, physical interception/rearming, comparative FPS and a clean release remain unverified. The normal route targets Everon. Later live edits are outside this sealed snapshot; existing packages and original missions are preserved.'
if a.verified:
validation=f'The exact frozen source passes **{count} registered regression programs**. Its normal route completes **{normal["assertions_passed"]} assertions and {normal["smoke_completions"]} round**, with zero script/runtime failures and **13 retained engine errors**. Ordinary startup of the exact packaged bytes verifies the normal mission and local prisoner with zero script/runtime failures and **17 retained engine errors**. Strict packaging/startup failures remain recorded.'
counts=f'**{source_count} authored files**, **{payload_count} exact PAK payloads** and **{runtime_count} runtime files** match the editable source and `handoff.json`.'
else:
validation=f'Final package/startup qualification is pending. The frozen snapshot contains {source_count} authored files; recorded suite count: {count}. No new packaged-playability result is claimed by this draft.';counts='Package payload/runtime counts remain pending verification.'
refusal='The first normal-route attempt reached its scripted result but its runner refused cleanup confirmation after observing a compiler process (`cc1plus`) and an empty-argv process. That failed attempt remains archived separately; the later confirmed run does not erase it.'
summary=f'{counts}\n\n{features}\n\n{validation}\n\n{refusal}\n\n{limits}'
out=HERE/'proposed-docs';out.mkdir(exist_ok=True)
newreadme='# Gameplay-fixes development candidate — 2 October 2026\n\n'+summary+'\n\nUse `source/addons/core/addon.gproj` with the separately installed RHS, WCS, BMP and Parachute dependency closure. Select `Missions/ERR_Everon.conf`. Runtime files are in `packages/core/`; editable source is in `source/` and `source.zip`. Keep ordinary human play free of smoke/input/flight fixture flags. See [the player route](../../docs/player-route.md), [build guide](../../docs/playtest-build.md) and [sealed evidence](../../docs/evidence/playtest-refresh-2026-10-02-gameplay-fixes/review.json). Nothing was committed or published.\n'
(out/'candidate-README.md').write_text(newreadme)
status=(HERE/'docs-baseline/current-status.md').read_text();title,tail=status.split('\n',1)
newstatus=title+'\n\n**2 October 2026 — refreshed gameplay-fixes handoff:** [separate package and editable source](../.build/playtest-2026-10-02-gameplay-fixes/README.md).\n\n'+summary+' [Evidence and independent review](evidence/playtest-refresh-2026-10-02-gameplay-fixes/review.json).\n\n'+tail.lstrip('\n')
(out/'current-status.md').write_text(newstatus)
guide=(HERE/'docs-baseline/playtest-build.md').read_text();start=guide.index('The latest retained handoff is ');end=guide.index('## Identities and required dependencies')
old_intro=guide[start:end];new_intro='The latest retained handoff is [2 October — gameplay fixes](../.build/playtest-2026-10-02-gameplay-fixes/README.md).\n\n'+summary+'\n\n'
newguide=guide[:start]+new_intro+guide[end:]+'\n## Historical prior latest-handoff description\n\n'+old_intro
(out/'playtest-build.md').write_text(newguide)
readme=(HERE/'docs-baseline/README.md').read_text();paragraph=next(p for p in readme.split('\n\n') if p.startswith('The [local development playtest handoff]'))
replacement='The [local development playtest handoff](.build/playtest-2026-10-02-gameplay-fixes/README.md) contains the 2 October gameplay-fixes frozen snapshot, as one combined addon package plus matching editable source and the authorized external dependency graph. '+validation+' '+limits+' See [current status](docs/current-status.md) for exact source-bound evidence and limitations.'
assert readme.count(paragraph)==1;(out/'README.md').write_text(readme.replace(paragraph,replacement,1))
review={'state':'verified proposal' if a.verified else 'pending draft','source_count':source_count,'pak_payload_files':payload_count,'runtime_files':runtime_count,'suite':suite,'normal':normal,'packed':packed,'preservation':check,'qualification':'development only; no manual UI/physical interception/MP/FPS/release claim','proposed_sha256':{p.name:sha(p) for p in out.iterdir() if p.is_file()}}
write(HERE/'docs-proposal.json',review)
if a.apply:
assert not EVIDENCE.exists(),'Never overwrite existing evidence'
expected='# Development candidate preparation\n\nMatching source and package hashes are verified in handoff.json. Ordinary packed startup is still pending. This is not a release or Workshop publication.\n'
assert (BUILD/'README.md').read_text()==expected,'Candidate README changed since assembly; review before overwrite'
EVIDENCE.mkdir()
for n in ['baseline.json','frozen-source.json','suite-inputs.json','suite-result.json','regressions.log','source-bundle.log','concurrent-source-delta.json','normal-native.log','normal-native-retry.log']:
if (BASE/n).exists():copy(BASE/n,EVIDENCE/n)
copy(REFUSAL,EVIDENCE/'initial-normal-cleanup-refusal');copy(a.review,EVIDENCE/'independent-review.json');copy(Path(__file__),EVIDENCE/'finalize-docs.py')
copy(HERE/'template-provenance.json',EVIDENCE/'helper-template-provenance.json');write(EVIDENCE/'review.json',review)
for n,dest in [('candidate-README.md',BUILD/'README.md'),('README.md',ROOT/'README.md'),('current-status.md',ROOT/'docs/current-status.md'),('playtest-build.md',ROOT/'docs/playtest-build.md')]:copy(out/n,dest)
write(EVIDENCE/'candidate-sha256.json',{str(p.relative_to(BUILD)):sha(p) for p in sorted(BUILD.rglob('*')) if p.is_file()})
write(EVIDENCE/'docs-after.json',{n:sha(ROOT/n) for n in docs})
print(json.dumps({'state':review['state'],'applied':a.apply,'proposal':str(out),'source_files':source_count,'suite_count':count},indent=2))
if __name__=='__main__':main()