Something went wrong. Try again.
Native control, inspection, UI automation and evidence capture for a running Witcher 3 process.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376"""Discoverable CLI facade over the reconnectable debugger client."""import argparseimport jsonimport mathimport secrets
from . import catalogfrom . import debuggerfrom . import debug_conditionsfrom .debug_journal import verifyfrom .debug_runtime import validate
SPECS={ 'open': ('Launch in lab mode and create a persistent owner.', 'debug open'), 'attach': ('Explicitly adopt a live compatible game without an owner.', 'debug attach --adopt'), 'status': ('Read cached owner status without contacting the game.', 'debug status'), 'control': ('Inspect or switch human/agent control of Geralt.', 'debug control status'), 'heal': ('Refill living Geralt to full native maximum health and verify it.', 'debug heal'), 'overlay': ('Show a passive debug HUD and publish model annotations.', 'debug overlay status'), 'inspect': ('Read a fresh world, combat, profile, UI, water or capability view.', 'debug inspect --view world'), 'events': ('Read a generation-scoped event cursor with explicit gaps.', 'debug events'), 'step': ('Deliver one bounded action, then collect paused state and events.', 'debug step --ticks 30'), 'run': ('Start a finite neutral run and return its operation ID immediately.', 'debug run --max-ticks 600'), 'until': ('Step until an observable condition, terminal or finite budget.', 'debug until --condition \'{"kind":"player_dead"}\' --max-ticks 120'), 'sample': ('Step once with in-game periodic combat-state samples and coverage checks.', 'debug sample --ticks 60 --every 6'), 'observe-teleport': ('Observe one owned actor during a finite input or neutral step.', "debug observe-teleport --reference '10|0' --ticks 60"), 'request-algorithm': ('Observe or replace positive natural Approach outputs for 1..8 owned actors during a finite neutral step.', "debug request-algorithm --actor '10|1' --ticks 60"), 'request-algorithm-stop': ('Disarm an identified request-algorithm window without changing ticket sources.', 'debug request-algorithm-stop --generation 10 --window-id 1'), 'quickslot': ('Explicitly select an equipped crossbow, clear selection, or restore a captured selection.', 'debug quickslot crossbow --generation 10'), 'capture': ('Capture the visible desktop plus a separately timestamped game snapshot.', 'debug capture'), 'skip-intro': ('Skip the observed native startup or recap movie once.', 'debug skip-intro'), 'pause': ('Cancel active work or release input and verify a pause.', 'debug pause'), 'cancel': ('Request cancellation without creating a competing native request.', 'debug cancel OPERATION_ID'), 'operation': ('Look up a durable operation outcome without replaying it.', 'debug operation OPERATION_ID'), 'load': ('Verify a fixture hash, load once and wait for a new generation.', 'debug load manualsave_52586_7ea4bc00_5c355be --sha256 ce88a0d43329d98482de458fbc416f75de86d81f8460b101dce10bb8775df973'), 'teleport': ('Request navigation placement and verify arrival within bounded steps.', "debug teleport '400|-500|7.871582'"), 'spawn': ('Spawn owned actors and verify stable native initialization.', 'debug spawn characters/npc_entities/monsters/hag_water_lvl1.w2ent --level 5'), 'cleanup-actors': ('Destroy owned test actors, release input and confirm pause.', 'debug cleanup-actors'), 'controls': ('Set explicit native lab controls in a paused world.', "debug controls '4|1|0'"), 'record': ('Mark recording boundaries; durable operation recording is always on.', 'debug record start'), 'close': ('Exit the unsaved game, restore personal settings and audit saves.', 'debug close'), 'recover': ('Restart a lost owner using the original journal and native receipt.', 'debug recover'), 'handoff': ('Rotate the owner token at a freshly verified paused boundary.', 'debug handoff'), 'ui-tree': ('Read a fresh instrumented UI tree through the session owner.', 'debug ui-tree'), 'ui-find': ('Wait for an exact unique UI selector without dispatching input.', 'debug ui-find label Inventory'), 'ui-dispatch': ('Dispatch one strict UI selector and optionally verify its next label.', 'debug ui-dispatch label Inventory'), 'export': ('Export an immutable journal snapshot as versioned Rust JSONL.', 'debug export /path/to/manifest.json --output /path/to/new-export --format rust-jsonl'), 'experiment': ('Run the bounded primitive throughput gate; full batch expansion is gated.', 'debug experiment --plan plans/hag-pilot.json --launch'), 'evidence-verify': ('Verify a finalized journal digest, sequence and operation accounting.', 'debug evidence-verify /path/to/manifest.json'),}
DETAILS={ 'overlay': ('status|on|off|clear|global|actor|remove|list|list-clear. on accepts --x 0..1600 --y 0..900 (HUD units, defaults 24/210; visually clamped to the left edge safe area). ' 'global/actor accept --title (<=32), up to three --row strings (<=40 each), --accent normal|warning|critical. ' 'list accepts --title (<=32, default empty), --profile (<=64, default empty), zero to twelve --row strings (<=64 each, default []), ' '--accent normal|warning|critical (default normal). Empty rows are retained. ' 'actor/remove/list require --reference GEN|ID. list-clear accepts no reference or publication payload. ' 'CLI text is safe ASCII, no quotes/backslashes/pipes/tildes; simple native publishers support Unicode, native lists require the same safe ASCII.', 'Independent of human/agent control. No pause, stepping, focus, input or gameplay changes. ' 'Off by default; without a list, global/player plus up to 8 selected actors. Simple cards use native health, model rows prefixed M:, ' 'published_tick/generation in status; annotations are not inferred native AI state. ' 'Rows persist until replaced/cleared or world change; no freshness claim from persistent text. ' 'Load/respawn clears publications and selection; enabled state recreates default global/player cards. ' 'Details dock in two edge columns; small #ID tags follow actors. Dead/missing actors hide; off-screen actors retain docked details. ' 'Off-screen/colliding tags hide rather than move across bodies; * on a health row denotes a hidden tag, not actor state. ' 'Removing a selection compacts later slots. Simple titles/body rows visually elide at 20/24 characters including prefixes/ellipsis; status retains full text. ' 'One expanded list at a time, only for an already selected alive actor (Geralt counts); never auto-selects. ' 'Native rejects a different actor until explicit list-clear, or a list when more than 3 enemies are selected; ' 'new selections beyond 3 reject while a list is active. Rejected updates leave the previous publication unchanged. ' 'list-clear clears only the list; off/clear/reload also clear it, and removing its actor clears its list. ' 'Right list panel has a separate MODEL badge, title/profile and native HP row. All 12 rows appear simultaneously, ' 'each on two physical lines of 32 characters retaining all 64, without M: prefix, cycling, dropping or ellipsis. ' 'List requires renderer version 3; only 1920x1080 is targeted for qualification, not a live qualification claim. ' 'No wall-occlusion test. Menus/cutscenes hide the layer. ' 'Requires the matching owned HUD asset (python3 -m witcher_devkit.ui_assets --overlay; install-ui) and cold restart for added fields. ' 'attached/render_frames report renderer activity, not a screenshot or visual-quality proof.'), 'heal': ('No payload. Optional --expected-generation GEN guards against stale world decisions.', 'Requires an alive loaded Witcher in explicit agent mode, devkit-paused with no-save protection ' 'and zero pending input/ticks. Refills current health to the native maximum, then verifies ' 'before/after health and unchanged maximum, generation, tick and paused boundary in a fresh snapshot. ' 'Already full is a verified no-op. Does not revive, advance simulation, switch control, add buffs, ' 'change maximum health/difficulty or write a save. Busy/uncertain delivery rejects as usual. ' 'Post-dispatch verification failure is uncertain, not success; inspect the operation ID, never blindly retry. ' 'To return control afterward, explicitly use debug control human.'), 'control': ('status|human|agent. Default on a fresh game process is human.', 'status reads fresh native state. human cancels native input/tick budgets, releases held agent input, ' 'restores physical-controller pass-through, disarms finite interventions, removes devkit time scale ' 'and only the devkit pause. Other game/menu pauses and native action locks are untouched. ' 'agent enables virtual-controller input and pauses a loaded world for bounded operations; it does not ' 'block physical keyboard/mouse input. Busy rejects: cancel the active operation and verify its result first. ' 'Load/spawn/inspection remain available in human mode without stepping or automatic pause. ' 'The hook remains installed; human input goes directly to the original XInput function, without a lock, ' 'sleep or extra polling. End-to-end physical latency requires separate measurement. No keys, UI or focus changes.'), 'quickslot': ('crossbow|clear|restore --generation GEN [--selection-id ID for restore].', 'Alive paused/no-save Witcher; one active generation-scoped receipt. Selects ONLY an already equipped crossbow or explicitly clears selection. No inventory add/equip/remove, no automatic ammo setup. Captures native prior item ID/slot without exporting opaque IDs; restore verifies that same item is still on its captured slot, or restores empty selection. Changed equipment/receipt/generation reject, not guess. Exact selection restoration does not undo shots, ammo consumption, holster state or queued actions; exact-world rollback is a hash-verified fixture reload or unsaved closure. Read debug inspect --view crossbow and CROSSBOW_INPUT.md.'), 'request-algorithm': ('--actor GEN|ID repeated 1..8 times --ticks 1..600 --algorithm approach --mode observe|score [--score VALUE exactly once per actor in actor order]; --wall-seconds >0..60 including cleanup.', 'Finite neutral advance; 1..8 distinct alive cleanup-owned actors in one generation. Original natural algorithm runs once; nonpositive outputs stay unchanged. Score mode replaces positive outputs only, never count fields or source capacity/floor. Native monotonic lease plus tick cap, explicit disarm and lifecycle guards. Separate buffer <=128 records of <=1 KiB; overflow disables intervention, drops/gaps invalidate completeness. More actors share the same buffer: shorten windows rather than assuming full coverage. Output is not final allocator importance or admission. Instrumented class and per-window instance identity only; concrete class/runtime node/category/source binding unavailable. Count mode unsupported. Matching bridge/native adapter required; no fields or native adapter changed by the 1..8 extension. See REQUEST_ALGORITHM.md for qualification limits.'), 'request-algorithm-stop': ('--generation GEN --window-id ID.', 'Disarms only the matching window and verifies active=false/armed=false. No instance count or source fields were written. Does not reverse prior allocator decisions or stop other work; cancel the original operation first if busy. Never blindly re-arm after uncertain delivery.'), 'open': ('--startup-seconds >0..180 (default 120).', 'Asynchronous broker_pid/state=starting plus startup_folder, then debug status. Status retains exact process/window evidence, passive compiler packets/fresh script log errors when available, phase and remaining budget. Compiler failure stops startup early; absent diagnostics never prove successful compilation. Game absence, exit, ambiguous identity, window mismatch and timeout remain distinct. No exec/reload before the real game window. Shared installation .lock is held for the owner lifetime. Source hashes pin the owner; do not edit/remove its checkout mid-session. Failed startup retains a separate save/mod/settings baseline; do not relaunch over its live process. Confirm exact-process exit then debug recover audits/restores this startup, not an old session. See STARTUP_DIAGNOSTICS.md. No save chosen.'), 'attach': ('--adopt is mandatory.', 'Adopts compatible instrumented lab mode. Reuses a certain existing native receipt; no hello over unresolved work. Close exits the adopted game.'), 'inspect': ('--view world|actor|combat|profile|ui|controls|capabilities|water|teleport-observer|request-algorithm|crossbow; --reference generation|actor_id for actor/profile or selected combat actor. Water optionally accepts an actor reference; observer/crossbow views accept none.', 'Fresh native values and provenance. Busy while another operation runs; status returns cached observations. Water reads one depth at the selected actor or player position without advancing; raw 10000 is unavailable, not dry. No player fallback for an invalid actor. Depth alone does not establish navigation or teleport suitability. Request-algorithm includes retained records after disarm; it never calls a score function to inspect it. Crossbow distinguishes selected/equipped gear, raw guard/parry flags, bounded parry/crossbow lock sources, AltSign/native CastSign state, compiled buffer enum values, selection receipt, engine action values and original firing-initiation hints. Endpoint values are not per-edge execution traces, target/impact proof or readiness guarantees. No lock/flag writes or buffer processing. Capabilities lists exact condition fields and unsupported move lifecycle hooks.'), 'step': ('--ticks integer 1..600; optional --input mx|my|lx|ly|button|ticks with axes [-1,1], input duration 2..120 equal to --ticks.', 'before/after, ticks_requested/observed, input_delivered, effect_verified=false, release verification and generation-scoped event delta. Guard dispatches a paired handler release at each boundary; native parry/interaction locks can ignore it. guard_release_observed and guard_cleanup_status report raw guarded/parry readback as clear/retained/unknown separately from input_release_verified; retained/unknown is not verified cleanup. Never force flags or clear buffers to make it pass. Separate blocks are separate presses.'), 'run': ('--max-ticks integer 1..600; no player input.', 'Returns operation ID immediately. Native tick cap is a backstop; owner polls state/drains events, then verifies pause. Use cancel/operation; no unbounded resume.'), 'until': ('--condition typed JSON; --max-ticks 1..600; --step 1..120 (default 1). Conditions and fields: '+json.dumps({k:sorted(v) for k,v in debug_conditions.FIELDS.items()})+'.', 'Host-stepped condition/terminal/budget stop; event_tick, detection_tick, pause_tick and step_ticks. State transitions shorter than a step can be missed. Historical events are excluded; missing required getters fail explicitly.'), 'events': ('--cursor nonnegative integer; --generation required with nonzero cursor; --limit 1..128.', 'Fixed first-page target, next_cursor, gaps and complete. No events are deleted. Cursors cannot be reused after a save load.'), 'load': ('NAME plus --sha256 lowercase 64 hex. Case-insensitive unique save stem/filename; default wall budget 120 seconds.', 'Hashes fixture before one load, polls reads through loading stalls, verifies new generation. Preserves control mode: human does not pause; agent verifies a paused boundary. Old actor/cursor handles are invalid. Never resends load.'), 'teleport': ('Quoted x|y|z coordinates.', 'Existing verified navigation setup: tolerance .35 metres, <=120 ticks in 5-tick blocks; requested/navigation/observed positions and validity retained.'), 'spawn': ('TEMPLATE --level N [--count 1..8] [--distance metres].', 'Human mode: one native creation request plus snapshot, initialization_verified=false and simulation_controlled=false; no pause or stepping. Agent mode: existing verified initialization, <=180 ticks, 15-tick intervals, three stable samples and at least 45 settling ticks. Identity, level, health channel, abilities/armor stability and alive checks; no healing or releveling.'), 'controls': ('Quoted difficulty|upscaling|prototype_disabled: 1..4|0/1|0/1; optional --timescale .05..2.', 'Sets explicit controls and returns native lab_state plus snapshot. Timescale changes have no combat-equivalence qualification.'), 'sample': ('--ticks 1..600 [--input PAYLOAD] [--every 1..120, default 6] [--actor generation|id repeated <=24].', 'One input/advance with existing in-game sampler, <=301 retained samples, revision/generation, actual spacing/missing pages and completeness. No native task lifecycle or probability inference.'), 'observe-teleport': ('--reference generation|actor_id --ticks 1..600 [--input PAYLOAD]; --wall-seconds >0..60 including cleanup (default 30). Input duration must match ticks and remains 2..120.', 'Arms one cleanup-owned actor, advances once, then disarms through the same owner worker on completion/cancel/wall budget. Max 128 native observer records of 1 KiB each including start/end; dropped counters and ring gaps invalidate observer completeness. Static hook class, canonical config fingerprint and original callback-handled/incoming completion-success only. No concrete class, branch/export ID, original relocation request/result, arrival or Main result. Partial compile/completion-notification evidence is in teleport-callback-validation.json; animation/cancel/overflow/reset remain unexercised live. New layout requires cold restart. Inspect --view teleport-observer after interrupted cleanup; never blindly re-arm.'), 'record': ('start or stop; one open window per session.', 'Opens/closes a trace window without ending the owner. Durable operation journaling remains on. stop returns a manifest path; export seals an immutable copy before further work supersedes that snapshot.'), 'operation': ('OPERATION_ID from submit; no new native request.', 'accepted/running/completed/rejected/uncertain/interrupted and original result. Completed describes the command, not experiment validity. Results remain readable after broker exit.'), 'pause': ('No payload.', 'If busy, requests cancellation and returns its pending operation ID. Otherwise releases input then verifies native pause. A cancel request alone never proves release.'), 'cancel': ('OPERATION_ID of active work.', 'Sets a flag consumed by the sole game worker. Loading may have to finish before the world can be paused. Query the original operation for actual completion.'), 'recover': ('No payload; original owner or failed-startup artifacts must remain.', 'For a retained failed startup, requires confirmed closed game and audits/restores its own prelaunch saves/mods/settings baseline without attach/hello or changing native uncertainty. Otherwise restarts a dead owner from its unchanged original source, even when invoked from another checkout. Preserves interrupted/uncertain outcomes. After confirmed game exit, independently audits/restores even if journal validation fails; corruption remains retained and explicitly unverified. Never clears live uncertainty.'), 'handoff': ('No payload; certain paused loaded world required.', 'Fresh paused-state proof, then token rotation. Old token fails; new controller reads the updated local descriptor. No idle timeout grants ownership.'), 'close': ('No payload; certain native delivery receipt required.', 'One unsaved exit, confirmed process end, save audit, owned personal-settings/performance restoration and mod audit. Historical/compound uncertainty does not by itself block explicit close when the live native receipt is certain; old unknown outcomes remain unchanged. Unresolved native delivery still blocks exit and requires documented external closure/recovery. Cleanup failures retain evidence; no final save reload.'), 'export': ('MANIFEST --output NEW_DIRECTORY --format rust-jsonl.', 'Immutable raw journal, closed-union typed shadow.jsonl, hashed session sidecar and manifest. Open windows remain open; rejected/uncertain operations and gaps remain present. No native-equivalence claim.'), 'experiment': ('--plan plans/hag-pilot.json --launch.', 'Minimal passive throughput gate only: <=300 seconds including startup/cleanup, reserve 45 seconds, target20. Full client/schema/mode expansion stopped after measured gate failure; current recommendation calibration_only. Emits final JSON only; progress goes to stderr.'),}COMMON_PARAMETERS=[ {'name':'operation_id','type':'string','pattern':'[A-Za-z0-9_-]{1,80}','purpose':'Idempotent identity; omitted generates a new ID.'}, {'name':'expected_generation','type':'integer','minimum':0,'purpose':'Reject stale world decisions before dispatch.'}, {'name':'wall_seconds','type':'number','exclusiveMinimum':0,'maximum':300,'default':30}, {'name':'wait_seconds','type':'number','minimum':0,'maximum':300,'default':30,'purpose':'Client wait only.'}, {'name':'no_wait','type':'boolean','default':False},]
def register(): catalog.command('debug','Own and drive a persistent interactive native debugger.','Subcommand; debug --help lists operations.', 'Verified existing devkit installation.','Persistent process owns .lock; separate CLI calls reconnect.', 'JSON operation IDs and explicit outcomes. No action replay on client timeout.',['debug status']) for name,(summary,example) in SPECS.items(): arguments,completion=DETAILS.get(name,('See debug '+name+' --help for flags.','Operation receipt with explicit status; lookup after a timeout, never replay under a new ID.')) catalog.command('debug '+name,summary,arguments, 'Owner required except open, attach, recover and offline evidence-verify; paused loaded world for gameplay.', 'Native work is serialized and journaled; busy rejects competing operations. No implicit save load or reset.', completion,[example]) if name == 'control': catalog.CATALOG['debug '+name]['prerequisites'] = 'Owner required; certain native delivery for mode changes. No loaded player required.' if name == 'overlay': catalog.CATALOG['debug '+name]['prerequisites'] = ( 'Owner and matching HUD asset required. on/publication need a ready loaded world. ' 'Works in human or agent mode; no pause or stepping prerequisite.') catalog.CATALOG['debug '+name]['parameters']=[dict(v) for v in COMMON_PARAMETERS] if name not in ('open','attach','status','recover','operation','cancel','export','evidence-verify','experiment') else [] if name=='load': next(v for v in catalog.CATALOG['debug '+name]['parameters'] if v['name']=='wall_seconds')['default']=120 if name in ('observe-teleport','request-algorithm'): next(v for v in catalog.CATALOG['debug '+name]['parameters'] if v['name']=='wall_seconds')['maximum']=60 if name=='request-algorithm': catalog.CATALOG['debug '+name]['completion'] += ( " Each natural record includes binding_tag and binding_tag_status from the actual instance's " 'PDRequestBindingTag() accessor after original score/count handling. Default is empty/untagged. ' 'Trusted diagnostic subclasses may override ONLY that pure literal accessor (<=64 JSON-encoded bytes); ' 'oversize is null/invalid_size, never truncated into an identity. This is declared instance provenance, ' 'not a native resource hash, class/node identity, tree binding or selector result. Missing callbacks ' 'are unknown, not a negative contrast. Adding the accessor requires a cold restart.') if name in ('step','sample','observe-teleport'): catalog.CATALOG['debug '+name]['completion'] += ( ' Crossbow uses the genuine virtual right-shoulder ThrowItem/hold input path, not a firing callback. ' 'Requires unchanged session-captured standard on-foot bindings, equipped crossbow explicitly selected, ' 'and protected Witcher world. Longer holds reserve a neutral final tick; 2-tick engine release may ' 'be observed during a later explicit neutral advance. Physical input is cleared at the boundary. Acceptance is not a shot; ' 'queued firing may complete during later neutral ticks. Cancellation/release does not undo a shot. ' 'See CROSSBOW_INPUT.md and quickslot selection/restore contracts.') if name=='observe-teleport': catalog.CATALOG['debug '+name]['effects']='Passive callback recording around one explicit bounded input/neutral advance; no teleport, eligibility or selector invocation. Single-owner routing and journaling remain required.' catalog.CATALOG['debug '+name]['completion'] += ' An audited input/advance precondition rejection retains its native diagnostic and rejected status only with verified unchanged paused/no-save/released state, observer stop and gap-free window; unknown delivery or cleanup remains uncertain.' native_command={'step':'input','spawn':'spawn','teleport':'teleport','controls':'lab_config','sample':'sample_arm','request-algorithm':'request_algorithm_arm'}.get(name) if native_command: from .arguments import SPECS as NATIVE_SPECS catalog.CATALOG['debug '+name]['native_payload_parameters']=NATIVE_SPECS[native_command] if name=='until': catalog.CATALOG['debug '+name]['conditions']={k:sorted(v) for k,v in debug_conditions.FIELDS.items()} catalog.CATALOG['debug '+name]['limits']={'native_calls_per_operation':4096,'response_json_bytes_per_operation':16777216,'journal_bytes':268435456,'cleanup_reserve_bytes':2097152,'socket_message_bytes':4194304} catalog.GUIDES['debugger']='''Interactive debugger workflow: debug open # asynchronous lab launch; does not choose a save debug status # cached owner status; wait until owner exists debug inspect --view ui # fresh observation through the sole writer debug load NAME --sha256 HASH --wall-seconds 120 debug inspect --view world debug control status # fresh human/agent state; fresh process defaults human debug control agent # explicit takeover before bounded input/stepping debug heal # optional explicit full-health refill; remains paused debug step --ticks 30 debug control human # release virtual input and devkit pause; ordinary human play debug run --max-ticks 600 # immediate operation ID debug operation ID # look up; never resend input after timeout debug cancel ID # request cancellation; poll ID to verify pause debug close --wall-seconds 30 # unsaved exit, settings restoration and save audit
All mutating submissions accept --operation-id to provide idempotent identity,--expected-generation to reject stale actor/world decisions and --wall-seconds(>0..300). --wait-seconds controls only how long the CLI waits, not game execution.Use --no-wait for interactive asynchronous dispatch. One pending operation; busynever silently queues a second action. SIGINT ends the CLI wait, not the operation.
until uses host stepping (default 1 tick, --step 1..120), not an exact enginebreakpoint. Conditions are JSON: {"kind":"player_health_below","value":1000},{"kind":"player_dead"}, or {"kind":"event","event_kind":"contact"}.Event conditions start after the initial cursor; ring gaps terminate the run.State conditions may miss transitions between observations. Guard dispatches arelease at each boundary; inspect its separate raw guard/parry postcondition.Spawn/teleport use verified bounded setup; initializationstability remains a declared heuristic. Capability inspection lists unsupportedobservers and the exact fields required by every condition. Actor predicatesrequire generation and actor_id; disappearance is not death.
observe-teleport --reference 'GENERATION|ACTOR_ID' --ticks 60 is one finiteinput/neutral advance with opt-in callback recording, not an idle recording toggle.Only cleanup-owned actors qualify; max 600 ticks and 60 wall seconds includingcleanup. Callback handled/completion notifications do not prove movement or treeidentity. Partial build-specific evidence is in teleport-callback-validation.json;the prior bridge was restored. Unexercised paths remain explicitly unqualified.inspect --view water --reference 'GENERATION|ACTOR_ID' queries that actor's currentposition without moving Geralt. Invalid/stale refs never fall back to the player.
record start/stop creates trace windows without closing the session. export takesa finalized journal manifest and --output NEW_DIRECTORY --format rust-jsonl.evidence-verify checks raw journals or exported manifests. handoff requires freshpaused state and rotates the token; reconnect using the updated local descriptor.
A broker crash requires debug recover, which reuses receipts and retains unknownoutcomes. It never sends a new hello or clears native uncertainty. Read-onlyinspection remains available; an uncertain mutation can require closing the exactgame process externally. Operation journals are private, fsynced, bounded to256 MiB with a 2 MiB cleanup reserve; nothing is silently deleted. record stopwrites a manifest, which later work can supersede; close writes the final one.
A known input/advance precondition rejection is retained as the primary diagnostic.observe-teleport reports it as rejected only after verifying no accepted action,unchanged generation/tick, paused/no-save/released boundaries, observer stop and acomplete event window. Missing acknowledgements or cleanup/gap evidence remainuncertain. Historical compound uncertainty does not alone block explicit closewhen the live native receipt is certain; it never repairs the old operation.Unresolved native delivery still blocks close, pause and release. A cancel requestonly signals the owner worker and is not proof of stopped input.'''
def parser(parent): subs=parent.add_subparsers(dest='debug_command',required=True) for name in SPECS: p=subs.add_parser(name,description=catalog.render(catalog.CATALOG['debug '+name]), formatter_class=argparse.RawDescriptionHelpFormatter) if name in ('open','attach','recover'): p.add_argument('--startup-seconds',type=float,default=120) if name=='attach': p.add_argument('--adopt',action='store_true',required=True) continue if name in ('cancel','operation'): p.add_argument('operation_id'); continue if name=='evidence-verify': p.add_argument('manifest'); continue if name=='export': p.add_argument('manifest'); p.add_argument('--output',required=True); p.add_argument('--format',choices=['rust-jsonl'],default='rust-jsonl'); continue if name=='experiment': p.add_argument('--plan',required=True); p.add_argument('--launch',action='store_true',required=True); continue if name=='status': continue p.add_argument('--operation-id') p.add_argument('--expected-generation',type=int) p.add_argument('--wall-seconds',type=float,default=120 if name=='load' else 30,help='Operation wall budget >0..300; load default 120, others 30. Certain stepping reserves up to 2 seconds for release/pause.') p.add_argument('--wait-seconds',type=float,default=30,help='CLI wait only, 0..300. Timeout returns operation ID; the operation continues.') p.add_argument('--no-wait',action='store_true') if name=='control': p.add_argument('mode',choices=['status','human','agent']) elif name=='overlay': from .overlay import ACTIONS p.add_argument('action',choices=ACTIONS) p.add_argument('--reference') p.add_argument('--title') p.add_argument('--profile') p.add_argument('--row',dest='rows',action='append') p.add_argument('--accent',choices=['normal','warning','critical']) p.add_argument('--x',type=float) p.add_argument('--y',type=float) elif name=='inspect': p.add_argument('--view',default='world',choices=['world','actor','combat','profile','ui','controls','capabilities','water','teleport-observer','request-algorithm','crossbow']) p.add_argument('--reference') elif name=='quickslot': p.add_argument('action',choices=['crossbow','clear','restore']) p.add_argument('--generation',type=int,required=True) p.add_argument('--selection-id',type=int) elif name=='request-algorithm': p.add_argument('--actor',dest='actors',action='append',required=True) p.add_argument('--ticks',type=int,required=True) p.add_argument('--algorithm',choices=['approach'],default='approach') p.add_argument('--mode',choices=['observe','score'],default='observe') p.add_argument('--score',dest='scores',action='append',type=float,default=[]) elif name=='request-algorithm-stop': p.add_argument('--generation',type=int,required=True) p.add_argument('--window-id',type=int,required=True) elif name=='events': p.add_argument('--cursor',type=int,default=0); p.add_argument('--generation',type=int); p.add_argument('--limit',type=int,default=128) elif name in ('step','sample','observe-teleport'): p.add_argument('--ticks',type=int,required=True); p.add_argument('--input') if name=='observe-teleport': p.add_argument('--reference',required=True) if name=='sample': p.add_argument('--every',type=int,default=6); p.add_argument('--actor',dest='actors',action='append',default=[]) elif name in ('run','until'): p.add_argument('--max-ticks',type=int,required=True) if name=='until': p.add_argument('--condition',required=True); p.add_argument('--step',type=int,default=1) elif name=='load': p.add_argument('name'); p.add_argument('--sha256',required=True) elif name=='teleport': p.add_argument('position') elif name=='spawn': p.add_argument('template'); p.add_argument('--level',type=int,required=True) p.add_argument('--count',type=int,default=1); p.add_argument('--distance',type=float,default=3) elif name=='controls': p.add_argument('payload',help='difficulty 1..4 | upscaling 0/1 | prototype_disabled 0/1'); p.add_argument('--timescale',type=float) elif name.startswith('ui-'): p.add_argument('--root',default='') if name!='ui-tree': p.add_argument('field',choices=['label','text','name','class','path']); p.add_argument('value'); p.add_argument('--timeout',type=float,default=10) if name=='ui-dispatch': p.add_argument('--expect-label') elif name=='record': p.add_argument('boundary',choices=['start','stop'])
def operation(a): name=a.debug_command; args={} fields={'inspect':('view','reference'),'events':('cursor','generation','limit'), 'control':('mode',), 'overlay':('action','reference','title','profile','rows','accent','x','y'), 'quickslot':('action','generation','selection_id'), 'request-algorithm':('actors','ticks','algorithm','mode','scores'), 'request-algorithm-stop':('generation','window_id'), 'observe-teleport':('reference','ticks','input'), 'step':('ticks','input'),'sample':('ticks','input','every','actors'),'until':('condition','step'),'load':('name','sha256'), 'teleport':('position',),'spawn':('template','level','count','distance'),'controls':('payload','timescale'), 'ui-tree':('root',),'ui-find':('root','field','value','timeout'), 'ui-dispatch':('root','field','value','timeout','expect_label')} for f in fields.get(name,()): value=getattr(a,f) if value is not None: args[f]=json.loads(value) if f=='condition' else value if name in ('until','run'): args['ticks']=a.max_ticks if name=='record': name='record-'+a.boundary if not math.isfinite(a.wall_seconds) or not 0<a.wall_seconds<=300: raise ValueError('wall-seconds must be >0..300') if name in ('observe-teleport','request-algorithm') and a.wall_seconds>60: raise ValueError('observer wall-seconds must be >0..60') if not math.isfinite(a.wait_seconds) or not 0<=a.wait_seconds<=300: raise ValueError('wait-seconds must be 0..300') validate(name,args) return name,args
def dispatch(a): name=a.debug_command if name in ('open','attach','recover'): return debugger.start(name,a.startup_seconds) if name=='evidence-verify': from pathlib import Path from .debug_export import verify_export return verify_export(a.manifest) if json.loads(Path(a.manifest).read_text()).get('kind')=='shadow_export_manifest' else verify(a.manifest) if name=='export': from .debug_export import export return export(a.manifest,a.output) if name=='experiment': from . import debug_pilot import contextlib,sys with contextlib.redirect_stdout(sys.stderr): r=debug_pilot.run(a.plan) return dict(ok=r['functional_loop_usable'],report=r['path'],target_20_met=r['target_20_met'],recommendation=r['recommendation'],batch_expansion=r['batch_expansion']) c=debugger.Client() if name=='status': return debugger.starting_status() or c.request('status') if name in ('operation','cancel'): return c.request(name,operation_id=a.operation_id) if name=='pause': r=c.request('pause') else: kind,args=operation(a) r=c.submit(kind,args,a.operation_id, a.wall_seconds,a.expected_generation) if not r.get('ok') or 'operation' not in r: return r opid=r['operation']['operation_id'] if a.no_wait or name=='run': return r try: result=c.wait(opid,a.wait_seconds) if name=='handoff' and result.get('error','').startswith('owner_token_mismatch'): result=debugger.Client().wait(opid,a.wait_seconds) if result.get('operation',{}).get('result',{}).get('ok') is False: result['ok']=False if result.get('operation',{}).get('status') in ('rejected','uncertain','interrupted'): result['ok']=False return result except KeyboardInterrupt: return dict(ok=True,waiting=False,interrupted_wait=True,operation_id=opid,next='debug operation '+opid)