Core Signals
Core signals are the errors, warnings, and telemetry events emitted by playroll-cpp.
There are two different audiences:
- Player-facing signals: errors and warnings that the UI can show directly to the player.
- Operational signals: telemetry events that testers and engineers use to diagnose quality, lifecycle, upload, and environment issues.
User-facing contract
The user-facing contract is RecorderError.
Every user-facing recorder error includes:
| Field | Meaning |
|---|---|
reason | Human-technical reason returned through the HTTP/API boundary. |
ui_error.mode | blocking_modal or warning. |
ui_error.title | Short user-facing title. |
ui_error.message | User-facing explanation and suggested action. |
ui_error.code | Stable wire code, for example WINDOW_NOT_16_9. |
ui_error.dismissible | Whether the UI can dismiss the message. |
ui_error.actions | (optional) Array of {type, label, payload} action buttons the UI can render. type ∈ open_url / open_path / open_folder_picker / copy_to_clipboard. New error codes can ship a recovery button without UI work — the UI has one generic handler per type. Omitted from the JSON when empty. |
ui_error.extras | (optional) Record<string,string> of structured fields for templating + telemetry slicing (e.g. nvenc_status_name, action_hint, checked_drive, driver_version_nvidia). Omitted from the JSON when empty. |
blocking_modal means recording must not proceed or has been force-stopped. warning means recording can continue, but the user should understand a degraded condition.
Start-time blocking errors
These errors block recording before capture starts.
| Code | Meaning | Typical user action | Monitored event family |
|---|---|---|---|
ENCODER_UNAVAILABLE | No compatible hardware H.264 encoder was detected. | Update GPU drivers or use a supported NVIDIA/AMD GPU. | capture.v2.start, capture.prestart |
ENCODER_INIT_FAILED | The selected GPU encoder failed to initialize. | Update NVIDIA driver (most common fix); close other recording apps; retry. | capture.v2.start, capture.v2.nvenc.* |
UNKNOWN_ENCODER | The requested encoder is not available in this build. | Pick a supported encoder. | capture.v2.encoder_policy_override |
POOL_ALLOC_FAILED | GPU texture pool allocation failed. | Close GPU-heavy apps, retry. | capture.v2.start, capture.v2.pool_pressure |
EXTRADATA_TIMEOUT | Encoder initialized but did not produce required keyframe/extradata. | Reboot, close encoder consumers, retry. | capture.v2.start |
MUXER_OPEN_FAILED | Core could not open the MP4 output. | Check recordings folder permissions and file locks. | capture.v2.muxer_open |
AUTH_EXPIRED | Player auth session expired. | Sign in again. | recording.blocked, upload.auth_rejected |
ALREADY_RECORDING | A session is already active. | Stop the current recording first. | recording.blocked |
NO_GAME_DETECTED | No supported game/window was found. | Launch the game and bring it to foreground. | recording.blocked |
WINDOW_NOT_16_9 | Window resolution/aspect is unsupported. Since EC-380 this is a persistent not-recordable state (windowAspectOk:false on the status payload): the UI disables the record affordance and the F5 hotkey is suppressed core-side, so per-attempt modals no longer occur. The prestart check remains as last-line defense. | Use a supported resolution/profile (resizing to 16:9 re-enables live, no restart needed). | monitor.window_aspect_unsupported (one WARNING per window instance), recording.start_suppressed, capture.prestart |
WINDOW_SPANS_MONITORS | The game window crosses display boundaries. | Move the window fully onto one monitor. | capture.prestart, window.diagnostic |
DISK_INSUFFICIENT_SPACE | Output drive is below the start threshold. | Free space on the named drive, or change the recordings folder to a drive with more room. | capture.prestart |
OUTPUT_PATH_NOT_WRITABLE | Recordings folder is not writable. | Fix permissions or move output path. | capture.prestart |
SETTINGS_POLICY_VIOLATION | Required graphics baseline is not applied. | Apply Playroll settings. | recording.blocked_settings_policy, settings.apply_failed |
SETTINGS_RESTART_REQUIRED | Settings were applied while the game was running. | Restart the game. | recording.start_gate_autofix_blocked_for_restart |
Error enrichment (extras + actions)
A subset of start-time errors ship with structured ui_error.extras and pre-built ui_error.actions. These let the UI render a precise message + an action button without parsing the human-readable text, and they let dashboards slice by exact root cause without regex on reason.
| Code | extras keys | actions[].type (when applicable) |
|---|---|---|
ENCODER_INIT_FAILED (NVENC path) | encoder_id, nvenc_status_name (e.g. INVALID_VERSION, OUT_OF_MEMORY), action_hint (update_driver / free_vram / close_other_recorders) | open_url → NVIDIA driver download, when action_hint=update_driver |
DISK_INSUFFICIENT_SPACE | checked_path, checked_drive, free_bytes, free_gb, total_bytes, total_gb, min_required_bytes, min_required_gb, plus install_drive when it differs from checked_drive | open_path → Explorer on the checked drive · open_folder_picker → change playroll.recordings_path |
When you add a new error code with an action_hint, prefer the existing four actions[].type values (open_url, open_path, open_folder_picker, copy_to_clipboard) — the renderer already supports them. Only introduce a new type when no primitive fits.
Start-time warnings
These conditions can be shown to the user, but recording can continue.
| Code | Meaning | User impact | Monitored event family |
|---|---|---|---|
AUDIO_UNAVAILABLE | Game/system audio capture could not start. | MP4 may be recorded without sound. | capture.v2.aac, capture.v2.aac_encode, capture.v2.aac_poll |
Mid-session forced-stop errors
These errors happen after a session has already started. Core should stop the session, preserve what it can, and expose a terminal error to UI/history.
| Code | Meaning | Expected final state | Monitored event family |
|---|---|---|---|
ASPECT_CHANGED_MIDSESSION | Window resized outside supported profile and the change persisted past the 2.5 s confirmation window (transient display-mode glitches are ridden out). | Session stops and may become blocked depending on validation. | recording.window_*, window.diagnostic, capture.aspect_guard |
DISK_FULL_MIDSESSION | Output drive dropped below safety threshold. | Session stops; upload may be blocked if artifacts are incomplete. | capture.v2.stop, recording.session_ended |
WINDOW_LOST | Game window disappeared, closed, or became unavailable. | Session stops with terminal error. | recording.window_lost, recording.window_timeout |
INPUT_BLOCKED_ELEVATED_GAME | The game runs as administrator while Playroll does not: Windows UIPI blocks hotkey delivery and raw-input capture while the game is focused, so the input-idle stop fired even though the user was playing. Remediation: disable "Run as administrator" on the game and its launcher, then restart the game; alternatively run Playroll elevated. | Session stops with terminal error; video artifacts are healthy. | capture.v2.target_baseline (target_proc_elevated, core_elevated), hotkeys.uipi_blocked_suspected, recording.input_idle_pause_timeout |
SYSTEM_SUSPEND_MIDSESSION | Windows slept or hibernated during recording. | Session stops with terminal error. | capture.v2.stop |
ENCODER_FAILURE_MIDSESSION | Encoder stopped producing valid output. | Session stops; validator decides uploadability. | capture.v2.nvenc.encode_failed, capture.v2.zero_video_output_watchdog |
SETTINGS_CHANGED_MIDSESSION | Monitored game settings changed during recording. | Session stops to protect baseline consistency. | recording.stopped_settings_changed, settings.watchdog_runtime_error |
Boot-time environment blockers
These errors prevent recording capability before the user starts a session.
| Code | Meaning | User action |
|---|---|---|
OS_VERSION_UNSUPPORTED | Windows build is too old. | Update Windows. |
WGC_NOT_SUPPORTED | Windows Graphics Capture is unavailable. | Fix OS policy/SKU or reinstall supported Windows. |
D3D11_UNAVAILABLE | Direct3D 11 device creation failed. | Update GPU drivers and reboot. |
NO_GPU_ENCODER | No supported NVIDIA/AMD encoder found. | Use supported hardware or update drivers. |
NVENC_DRIVER_TOO_OLD | NVIDIA driver is below minimum. | Install a newer NVIDIA driver. |
AMF_DRIVER_TOO_OLD | AMD AMF runtime is below minimum. | Install a newer AMD Adrenalin driver. |
WASAPI_NO_DEFAULT_DEVICE | Windows has no default audio output. | Pick or connect an output device. |
VM_PERF_INSUFFICIENT | VM capture performance is below minimum. | Use host machine or improve VM GPU/vCPU allocation. |
Boot-time environment warnings
These warnings should be visible because they explain likely degraded recordings.
| Code | Meaning | Risk |
|---|---|---|
VM_DETECTED | A hypervisor was detected. | Capture quality depends on VM GPU setup. |
LOW_VRAM | GPU has low dedicated video memory. | Higher risk of frame drops under load. |
PROCESS_LOOPBACK_UNAVAILABLE | Per-process audio capture is unavailable. | Recording may include all system audio. |
MULTI_GPU | Multiple GPUs are active. | Possible black capture if game and recorder use different GPUs. |
Startup context fields
playroll-core emits a one-shot service.startup_context_* family at boot. These fields are stable and meant to be joined against recording / error events for cohort analysis.
| Event | Field | Meaning |
|---|---|---|
service.startup_context_adapter | driver_version | Raw Windows DXGI driver version, e.g. 32.0.15.6094. |
service.startup_context_adapter | driver_version_raw | Same value as a hex 64-bit. |
service.startup_context_adapter | driver_version_nvidia | NVIDIA-branded XXX.YY form, e.g. 560.94. Populated only for adapters with vendor_id = 0x10DE. |
service.startup_context_flags | probe.has_nvidia_gpu | true when cuInit + cuDeviceGetCount succeed. |
service.startup_context_flags | probe.default_encoder_id | Currently nvenc_h264_tex or empty. |
service.startup_context_flags | policy.encoder | Currently always nvenc_only (see EC-173). |
service.startup_context_flags | nvenc_sdk_min_driver_version | The minimum NVIDIA driver branch (XXX.YY) supported by the bundled NVENC SDK. Bumped alongside the SDK. Compare against driver_version_nvidia to find users below the floor. |
Typical join: nvenc_status_name = 'INVALID_VERSION' (from ENCODER_INIT_FAILED.extras) plus driver_version_nvidia < nvenc_sdk_min_driver_version (from the startup context) → users whose driver is provably too old for this build.
Quality monitoring signals
These signals are operational telemetry. They are not necessarily shown to the user today, but they should drive dashboards, alerts, and future UI warnings.
| Event | Level | What it tells us |
|---|---|---|
capture.v2.fps_profiler.event | WARNING/INFO | FPS quality degraded, changed reason/severity, or recovered. |
capture.v2.fps_low_detected | WARNING | Sustained sub-50 real FPS detected (EC-346 thresholds). Observe-only since EC-360: one-shot per session, the recording continues and uploads; the meta fps_breakdown carries the same flag. |
capture.v2.dead_capture_detected | WARNING | Majority of muxed frames are CFR duplicates — the capture is effectively dead (EC-348 synthetic-ratio signal). Observe-only since EC-360, one-shot per session. |
capture.v2.qos | WARNING/INFO | Capture queue pressure, drops, escalations, and recovery behavior. |
capture.v2.qos_saturated | WARNING | Capture queue stayed saturated long enough to warn. |
capture.v2.qos_snapshot | INFO | Periodic counters for seen/enqueued/dropped frames. |
capture.v2.pool_pressure | INFO | Texture pool waits, held slots, availability, and pressure percentiles. |
capture.v2.encode_stage.frame_metrics | INFO | Encode throughput, latency, lock waits, and encode failures. |
capture.v2.packet_queue.depth | INFO | Packet queue depth and enqueue blocks. |
capture.v2.mux_stage.write_metrics | INFO | MP4 write latency, disk I/O, and write failures. |
capture.v2.zero_video_output_watchdog | INFO/WARNING/ERROR | Detects capture/encode flow with no muxed video output. |
capture.v2.nvenc.need_more_input | WARNING | Encoder is repeatedly asking for more input. |
capture.v2.nvenc.zero_bitstream | WARNING | Encoder returned an empty bitstream. |
capture.v2.nvenc.encode_failed | ERROR | NVENC encode call failed. |
capture.v2.nvenc.lock_failed | ERROR | Encoder bitstream lock failed. |
capture.v2.mux_stage.write_failed | ERROR | Mux stage failed to write packet data. |
FPS profiler reasons
capture.v2.fps_profiler.event uses these reasons:
| Reason | Meaning | Likely owner |
|---|---|---|
low_source_fps | Capture/source side is not producing frames fast enough. | Capture/window/game performance. |
capture_pool_pressure | Texture pool saturation or pool-slot drops. | GPU texture pool sizing or downstream backpressure. |
encode_backpressure | Encode latency or NVENC lock waits are above budget. | Encoder/GPU/driver contention. |
mux_backpressure | Packet queue, mux, or disk I/O is slowing output. | Disk or mux pipeline. |
low_muxed_fps | Muxed FPS is low without a more specific detected cause. | Needs cross-signal investigation. |
Severity thresholds for a 60 FPS target:
| Severity | Current threshold |
|---|---|
good | Window muxed FPS is at least 58 and drop ratio is below 5%. |
degraded | Window muxed FPS is below 58 or drop ratio is at least 5%. |
severe | Window muxed FPS is below 49.8 or drop ratio is at least 15%. |
critical | Window muxed FPS is below 45 or drop ratio is at least 30%. |
The profiler ignores the first 10 seconds as warmup. Degraded warnings require two bad windows; severe and critical warnings can fire after one bad window. Recovery requires three consecutive good windows.
Validation and uploadability signals
Validation determines whether a finished session can upload.
| Event | Level | What it tells us |
|---|---|---|
recording.validation | INFO/WARNING | Final artifact validation result and diagnostics. |
recording.session_ended | INFO/WARNING | Session terminal notification outcome. |
sqlite.session_end | INFO | Local terminal session state was persisted. |
sqlite.file_status | INFO | Local artifact upload state changed. |
sqlite.sidecars_failed | INFO | CSV/JSON sidecars were failed because MP4 was not uploadable. |
Current validation block reasons include:
| Reason | Meaning |
|---|---|
mp4_missing | MP4 path is missing or cannot be statted. |
mp4_too_small | MP4 exists but is below minimum file size. |
mp4_no_frames | Capture/metadata indicates no video frames. |
mp4_duration_unreadable | MP4 duration could not be read. |
mp4_too_short | MP4 duration is below minimum session threshold. |
csv_missing | CSV path is missing. |
csv_parse_error | CSV cannot be opened, has unexpected header, invalid rows, invalid timestamps, or invalid event JSON. |
csv_non_monotonic_timestamps | CSV timestamps moved backwards. |
csv_missing_start | CSV has no valid start event. |
csv_missing_end | CSV has no valid end event. |
csv_duration_mismatch | CSV duration is non-positive or does not match MP4/metadata tolerance. |
json_missing | JSON metadata path is missing. |
json_parse_error | JSON cannot be opened or parsed. |
json_duration_mismatch | JSON duration fields are invalid or inconsistent. |
Since EC-360, mp4_fps_below_threshold no longer blocks: a muxed FPS below
the floor is appended to the recording.validation diagnostics
(mp4_fps_below_threshold (observe-only)) on an uploadable result.
Upload monitoring signals
Upload telemetry explains whether a validated file reached S3 and why retries or failures happened.
| Event | Level | What it tells us |
|---|---|---|
upload.file_start | INFO | Upload worker started an artifact. |
upload.file_done | INFO | Upload worker completed local state transition. |
upload.s3_put_ok | INFO | Single PUT upload succeeded. |
upload.multipart_start | INFO | Multipart upload started. |
upload.multipart_ok | INFO | Multipart upload completed. |
upload.backoff | INFO | A failed file was re-queued with backoff. Since EC-526 the backoff is persisted per file (files.next_attempt_at, exponential 30s → 16min cap, survives restarts) and carries persisted:true; the worker no longer sleeps, so other pending files keep uploading. seconds=0 rows date a client to pre-EC-526. |
upload.legacy_audio_blocked | WARNING | Legacy OGG upload was blocked. |
upload.sidecar_blocked | WARNING | CSV/JSON sidecar was blocked because MP4 is missing or not uploadable. |
upload.file_not_ready | WARNING | File is not ready for upload yet. |
upload.file_discarded | WARNING | File was discarded from upload flow. |
upload.auth_rejected | WARNING | Upload URL request was rejected by auth. |
upload.user_id_mismatch | WARNING | Local file user ID did not match authenticated user. |
upload.presigned_url_failed | ERROR | Backend did not return a usable presigned URL. |
upload.invalid_presigned_url | ERROR | Presigned URL shape was invalid. |
upload.file_missing | ERROR | Local file disappeared before upload. |
upload.file_open_failed | ERROR | Local file could not be opened. |
upload.s3_put_failed | ERROR | S3 PUT failed. |
upload.multipart_*_failed | WARNING/ERROR | Multipart part, complete, or abort flow failed. |
upload.max_retries | ERROR | Upload exceeded retry budget. Since EC-526 the attempt is counted before it runs (an external kill mid-attempt still burns budget) and a maxed file is failed at pick time (detected:on_pick) — a stuck file can no longer retry forever across restarts. |
Window, pause, and idle signals
These events explain why a session was paused, stopped, or became risky during gameplay.
| Event | Level | Meaning |
|---|---|---|
recording.window_lost | WARNING | Target window was destroyed or lost. |
recording.window_timeout | WARNING | Target window stayed unavailable long enough to trigger timeout behavior. |
recording.window_minimized | INFO | Target window was minimized. |
recording.window_restored | INFO | Target window returned. |
recording.input_idle_pause_timeout | INFO | Auto-pause fired after input idle timeout. |
idle_warning.reminder_fired | INFO | Idle warning reminder was emitted while paused. |
Command-path and crash signals (2026-06-11, EC-373/374/377/379/380)
Signals introduced by the brondix-incident fixes. Behavioral notes that matter when reading dashboards:
recorder_error.createdseverity now follows the directive mode (EC-377): warning-mode codes (MULTI_GPU,LOW_VRAM, …) log at WARNING and once per core run; blocking modals stay ERROR and are never deduped. ERROR-severity rows containing warning-mode codes date a client to pre-4.1.12.- A terminal stop is emitted once per session (EC-379 latch): one game-exit produces exactly one
capture.terminal_stopand one user-facing notification. Multi-modal storms date a client to pre-4.1.12. - Recording commands from the UI are bounded at 30 s (EC-374); the old 307–309 s
CAPTURE_UNAVAILABLEfailure signature should disappear entirely.CORE_COMMAND_TIMEOUTis the new honest "core busy" denial. - Since EC-525, a silent core
/healthno longer producescore.crashwhen the process is still alive: the UI emitscore.unresponsive(once per episode), waits a 90 s grace period, then escalates withcore.unresponsive_killbefore killing+restarting.core.crashis reserved for real process exits — anexit_codeof-1/null on acore.crashrow dates the client to pre-EC-525 (the health-poll false-crash loop) or means a spawn error. Auto-restarts are budgeted at 3 per rolling 10 minutes (previously the budget reset on every recovery, making the kill/respawn loop infinite — the marcox01225 incident).
| Event | Level | Meaning |
|---|---|---|
recording.command_timeout | ERROR | A lifecycle command (start/stop/pause/resume) was posted to the core's command pump but not processed within the bounded wait (20 s). The pump is wedged or extremely busy; the command may still execute later. |
core.unresponsive (UI) | WARNING | The core process is alive but /health stopped answering (3 consecutive failed polls, ~15 s). Carries the consecutive-failure count and unresponsive duration. Not a crash; nothing is killed yet (EC-525). |
core.unresponsive_kill (UI) | ERROR | The unresponsive grace period (90 s) expired and the UI supervisor killed+restarted the live-but-wedged core. A deliberate supervisor kill, distinct from core.crash (EC-525). |
core.autorestart_suppressed (UI) | ERROR | The auto-restart budget (3 per rolling 10 min since EC-525) is exhausted; the launcher shows the terminal "recording unavailable" state instead of looping. |
core.crash_dump_written | ERROR | Emitted at the boot FOLLOWING a native crash: a minidump was written by the unhandled-exception filter (%LOCALAPPDATA%\Playroll\crashdumps, newest 3 kept). Carries exception_code — grab the dump for root-cause. __fastfail (0xC0000409) cannot be captured by design. |
monitor.window_aspect_unsupported | WARNING | The detected game window violates the capture policy (non-16:9 / sub-minimum / spans monitors). One per detected window instance; the live state is windowAspectOk on the status payload. |
recording.start_suppressed | INFO | A start trigger (F5) was consumed core-side because the target is not recordable (reason: window_aspect_unsupported). No session, no modal. |
lobby.resync_after_restart (UI) | INFO | The UI re-pushed the active lobby to a freshly (re)started core (EC-369). |
Settings enforcement signals
Settings enforcement protects the recording baseline.
| Event | Level | Meaning |
|---|---|---|
recording.blocked_settings_policy | WARNING | Recording start was blocked by game settings policy. |
recording.start_gate_autofix_blocked_for_restart | WARNING | Core applied settings but game restart is required. |
recording.start_gate_autofix_unfixable | WARNING | Core could not automatically fix settings. |
settings.apply_failed | WARNING | Applying required game settings failed. |
settings.restore_failed | WARNING | Restoring original game settings failed. |
settings.watchdog_runtime_error | WARNING | Runtime settings watchdog encountered a monitoring error. |
recording.stopped_settings_changed | INFO | Recording was stopped because settings changed mid-session. |
Practical support flow
For a failed or suspicious session, inspect signals in this order:
RecorderErrorcode shown to the player, if present.recording.session_endedand local session final status.recording.validationblock reason and diagnostics.capture.v2.fps_profiler.eventand QoS snapshots around the same time.capture.v2.zero_video_output_watchdog, NVENC, mux, and disk I/O events.- Upload events and local
sqlite.file_status. - Settings and window watchdog events if the player reports minimization, game closure, auto-pause, or settings changes.
Documentation maintenance
When core adds a new RecorderError, validation block reason, or operational event that affects support/debugging, update this page in the same change.