summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--cs2pov/automation.py75
-rw-r--r--cs2pov/config.py6
-rw-r--r--cs2pov/navigation.py33
-rwxr-xr-xscripts/recorder9
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