diff options
| author | Schark <jordan@schark.online> | 2026-02-25 01:26:53 -0500 |
|---|---|---|
| committer | Schark <jordan@schark.online> | 2026-02-25 01:26:53 -0500 |
| commit | 76f49ede79a1a9954df24f1fffc2c8624363e30b (patch) | |
| tree | 4390c23a757ef91bd8cac60eab3a92a040467cff | |
| parent | 64e17e20f6f2b4e5426fce9d86c58aab4f89411f (diff) | |
| download | cs2pov-76f49ede79a1a9954df24f1fffc2c8624363e30b.tar.gz cs2pov-76f49ede79a1a9954df24f1fffc2c8624363e30b.zip | |
Tick-based nav kinda working, no surviving logic
Diffstat (limited to '')
| -rw-r--r-- | cs2pov/automation.py | 75 | ||||
| -rw-r--r-- | cs2pov/config.py | 6 | ||||
| -rw-r--r-- | cs2pov/navigation.py | 33 | ||||
| -rwxr-xr-x | scripts/recorder | 9 |
4 files changed, 94 insertions, 29 deletions
diff --git a/cs2pov/automation.py b/cs2pov/automation.py index 7988e43..dbc90b8 100644 --- a/cs2pov/automation.py +++ b/cs2pov/automation.py @@ -107,6 +107,47 @@ def check_demo_ended(log_path: Path, last_position: int = 0) -> tuple[bool, int] return False, last_position +def check_demo_ended_tick_aware( + log_path: Path, + last_position: int, + min_end_tick: int, +) -> tuple[bool, int]: + """Check if demo has ended, filtering out pauses from navigation. + + In tick-nav mode, 'CGameRules - paused on tick X' lines appear from our + own deliberate pauses (calibration, death handling). This function only + considers it a real demo end if the tick is past min_end_tick. + + Args: + log_path: Path to CS2 console.log + last_position: File position to start reading from + min_end_tick: Only consider ended if paused tick >= this value. + Typically the last alive segment's end tick. + + Returns: + Tuple of (demo_ended: bool, new_position: int) + """ + if not log_path.exists(): + return False, last_position + + demo_end_pattern = re.compile(r"CGameRules - paused on tick (\d+)") + + try: + with open(log_path, 'r', errors='ignore') as f: + f.seek(last_position) + content = f.read() + new_position = f.tell() + + for match in demo_end_pattern.finditer(content): + tick = int(match.group(1)) + if tick >= min_end_tick: + return True, new_position + + return False, new_position + except Exception: + return False, last_position + + @dataclass class DemoEndInfo: """Information about when the demo ended.""" @@ -353,38 +394,52 @@ def calibrate_tick_offset( display: str, window_id: str, log_position: int, + calibration_tick: int = 0, verbose: bool = False, ) -> tuple[int, int]: - """Calibrate the tick offset by pausing and reading actual tick. + """Calibrate the tick offset by sending demo_gototick and measuring drift. + + demo_gototick X doesn't land exactly at tick X — there's a consistent + drift per demo. We measure it by: + 1. Send demo_gototick to a known tick + 2. Pause and read actual tick from console + 3. offset = actual_tick - requested_tick + 4. Resume playback - At startup after map load, we pause, read the actual tick from console, - and compute the offset. All future demo_gototick calls subtract this - offset from their target. + All future goto calls subtract this offset: demo_gototick(target - offset). Args: console_log_path: Path to CS2 console.log display: X display string window_id: CS2 window ID log_position: Current position in console.log + calibration_tick: Tick to goto for calibration (default 0) verbose: Print debug output Returns: - (offset, new_log_position). offset = actual_tick read from console. + (offset, new_log_position). offset = actual_tick - calibration_tick. """ - # Pause demo via F7 (bound to demo_pause 1) + # Seek to the calibration tick + send_console_command(f"demo_gototick {calibration_tick}", display, window_id) + time.sleep(2.0) + + # Pause demo via F7 send_key("F7", display, window_id) - time.sleep(0.5) + time.sleep(1.0) # Read the paused tick from console actual_tick, log_position = read_paused_tick(console_log_path, log_position, timeout=5.0) - # Unpause via F6 (bound to demo_pause 0) + # Let CS2 settle before unpausing + time.sleep(0.5) + + # Resume via F6 (demo_resume — idempotent) send_key("F6", display, window_id) if actual_tick is not None: - offset = actual_tick + offset = actual_tick - calibration_tick if verbose: - print(f" Tick calibration: actual_tick={actual_tick}, offset={offset}") + print(f" Tick calibration: goto {calibration_tick} → landed at {actual_tick}, offset={offset}") return offset, log_position else: if verbose: diff --git a/cs2pov/config.py b/cs2pov/config.py index 8294983..84dc93e 100644 --- a/cs2pov/config.py +++ b/cs2pov/config.py @@ -83,10 +83,12 @@ cl_drawhud 1\ # Keybinds for tick-based navigation (pause/unpause via keybind is faster # than typing console commands since it's a single xdotool key press) +# NOTE: demo_pause toggles regardless of argument, so we use demo_resume +# for unpausing which is idempotent (safe to send multiple times). TICK_NAV_BINDS = """ // Tick navigation keybinds (used by --tick-nav) -bind "F6" "demo_pause 0" -bind "F7" "demo_pause 1" +bind "F6" "demo_resume" +bind "F7" "demo_pause" """ diff --git a/cs2pov/navigation.py b/cs2pov/navigation.py index fd30ebf..dbd173c 100644 --- a/cs2pov/navigation.py +++ b/cs2pov/navigation.py @@ -13,7 +13,7 @@ from typing import Optional from .automation import ( check_death_in_console, - check_demo_ended, + check_demo_ended_tick_aware, calibrate_tick_offset, find_cs2_window, send_console_command, @@ -200,14 +200,23 @@ def recording_loop_tick_nav( else: print(" Warning: CS2 window not found for tick navigation") - # Calibrate tick offset - if window_id: - print(" Calibrating tick offset...") - state.tick_offset, log_position = calibrate_tick_offset( - console_log_path, display, window_id, log_position, verbose - ) - death_log_position = log_position - print(f" Tick offset: {state.tick_offset}") + # TODO: Tick offset calibration disabled — demo_gototick appears to land + # accurately without correction. Re-enable if drift is observed. + # if window_id: + # print(" Calibrating tick offset...") + # state.tick_offset, log_position = calibrate_tick_offset( + # console_log_path, display, window_id, log_position, verbose + # ) + # death_log_position = log_position + # print(f" Tick offset: {state.tick_offset}") + state.tick_offset = 0 + + # Compute minimum tick that indicates a real demo end (not a navigation pause). + # Any "paused on tick X" where X is below this is from our own calibration/navigation. + last_segment = state.timeline.alive_segments[-1] + min_end_tick = last_segment.end_tick + if verbose: + print(f" Demo end threshold: tick >= {min_end_tick}") print(f" {len(state.timeline.alive_segments)} alive segments to navigate") @@ -226,8 +235,10 @@ def recording_loop_tick_nav( print(" FFmpeg stopped unexpectedly") return "ffmpeg_stopped", state.transitions - # Check for demo end - demo_ended, log_position = check_demo_ended(console_log_path, log_position) + # Check for demo end — only if paused tick is past our last alive segment + demo_ended, log_position = check_demo_ended_tick_aware( + console_log_path, log_position, min_end_tick + ) if demo_ended: print(" Demo end detected in console.log") return "demo_ended", state.transitions diff --git a/scripts/recorder b/scripts/recorder index 05365d9..f1831a1 100755 --- a/scripts/recorder +++ b/scripts/recorder @@ -1,8 +1,5 @@ # POV recorder -cs2pov pov -d /path/to/demo.dem -p "player" -o /path/to/output.mp4 +cs2pov pov -d ./demos/260224/nuke.dem -p "schark" -o ./recordings/260224/schark_nuke.mp4 --tick-nav +sleep 10 +cs2pov pov -d ./demos/260224/ancient.dem -p "schark" -o ./recordings/260224/schark_ancient.mp4 --tick-nav -# Trim -cs2pov trim /path/to/recording_raw.mp4 -d /path/to/demo.dem -p "player" -o /path/to/output.mp4 - -# Info -cs2pov info /path/to/demo.dem |
