from pathlib import Path import hashlib,json root=Path('Client/Assets/Scripts'); out=Path('CodexOutput/upserver/ReviewLifecycleTests') def method(path, signature): text=(root/path).read_text(encoding='utf-8-sig'); start=text.index(signature); opening=text.index('{',start); depth=1; end=opening+1 while depth: depth+=(text[end]=='{')-(text[end]=='}'); end+=1 return text[start:end] parts=[('GetUiSubject','Stages/FishingAct.cs',['async void GetUI()','async void NewEnterMap(PlayableDirector camera_timeline)','async Task OnCameraSpeedEvent()']),('ManagerSubject','Core/SoundMgr.cs',['public async Task Init(System.Action progress)','public void Release()']),('ServiceSubject','Sound/SoundService.cs',['public async Task InitAsync(string mixerAddr','public void Release()'])] generated='using System;using System.Linq;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;\n' evidence=[] for name,path,signatures in parts: generated+=f'public partial class {name} {{\n' for signature in signatures: # SoundService also has an interface Release; select class suffix. if name=='ServiceSubject' and signature=='public void Release()': text=(root/path).read_text(encoding='utf-8-sig'); text=text[text.index('public class SoundService'):] start=text.index(signature); opening=text.index('{',start); depth=1; end=opening+1 while depth: depth+=(text[end]=='{')-(text[end]=='}'); end+=1 raw=text[start:end] else: raw=method(path,signature) extracted=raw.replace('async void GetUI()', 'public async Task GetUI()').replace('async void NewEnterMap(', 'public async Task NewEnterMap(').replace('async Task OnCameraSpeedEvent()', 'public async Task OnCameraSpeedEvent()') generated+=extracted+'\n' evidence.append({'source':str(root/path),'signature':signature,'source_sha256':hashlib.sha256((root/path).read_bytes()).hexdigest(),'method_sha256':hashlib.sha256(raw.encode()).hexdigest(),'transformation':'async void -> public async Task only' if name=='GetUiSubject' else 'none'}) generated+='}\n' stage_path='Stages/FishingStage.cs'; stage_text=(root/stage_path).read_text(encoding='utf-8-sig') prefix=stage_text[stage_text.index('public override async Task StartAsync()'):stage_text.index('//GContext.Publish()')].replace('public override async Task StartAsync()', 'public async Task RunPrefix()') stop=method(stage_path,'public override async Task Stop()') destroy=method(stage_path,'protected override void OnDestroy()') catch_start=stage_text.index('try { await base.StartAsync(); }'); catch_end=stage_text.index('//静默加载后续场景',catch_start); guard=stage_text[catch_start:catch_end] generated+='public partial class StageSubject : StageBase {\n'+prefix+'Applied=true; }\n'+stop+'\n'+destroy+'\npublic async Task RunCatch(){ var loginLifetime=UserService.LoginLifetime;\n'+guard+'CatchCompleted=true; }\n}\n' evidence.append({'source':str(root/stage_path),'source_sha256':hashlib.sha256((root/stage_path).read_bytes()).hexdigest(),'transformation':'StartAsync prefix ends before //GContext.Publish(); Stop and OnDestroy complete; actual base.StartAsync catch segment through next-scene comment; only wrapper names/assertion markers added'}) (out/'Generated.cs').write_text(generated,encoding='utf-8') (out/'extraction.json').write_text(json.dumps(evidence,ensure_ascii=False,indent=2),encoding='utf-8')