Skip to main content

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:

FieldMeaning
reasonHuman-technical reason returned through the HTTP/API boundary.
ui_error.modeblocking_modal or warning.
ui_error.titleShort user-facing title.
ui_error.messageUser-facing explanation and suggested action.
ui_error.codeStable wire code, for example WINDOW_NOT_16_9.
ui_error.dismissibleWhether the UI can dismiss the message.
ui_error.actions(optional) Array of {type, label, payload} action buttons the UI can render. typeopen_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.

CodeMeaningTypical user actionMonitored event family
ENCODER_UNAVAILABLENo compatible hardware H.264 encoder was detected.Update GPU drivers or use a supported NVIDIA/AMD GPU.capture.v2.start, capture.prestart
ENCODER_INIT_FAILEDThe selected GPU encoder failed to initialize.Update NVIDIA driver (most common fix); close other recording apps; retry.capture.v2.start, capture.v2.nvenc.*
UNKNOWN_ENCODERThe requested encoder is not available in this build.Pick a supported encoder.capture.v2.encoder_policy_override
POOL_ALLOC_FAILEDGPU texture pool allocation failed.Close GPU-heavy apps, retry.capture.v2.start, capture.v2.pool_pressure
EXTRADATA_TIMEOUTEncoder initialized but did not produce required keyframe/extradata.Reboot, close encoder consumers, retry.capture.v2.start
MUXER_OPEN_FAILEDCore could not open the MP4 output.Check recordings folder permissions and file locks.capture.v2.muxer_open
AUTH_EXPIREDPlayer auth session expired.Sign in again.recording.blocked, upload.auth_rejected
ALREADY_RECORDINGA session is already active.Stop the current recording first.recording.blocked
NO_GAME_DETECTEDNo supported game/window was found.Launch the game and bring it to foreground.recording.blocked
WINDOW_NOT_16_9Window 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_MONITORSThe game window crosses display boundaries.Move the window fully onto one monitor.capture.prestart, window.diagnostic
DISK_INSUFFICIENT_SPACEOutput 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_WRITABLERecordings folder is not writable.Fix permissions or move output path.capture.prestart
SETTINGS_POLICY_VIOLATIONRequired graphics baseline is not applied.Apply Playroll settings.recording.blocked_settings_policy, settings.apply_failed
SETTINGS_RESTART_REQUIREDSettings 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.

Codeextras keysactions[].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_SPACEchecked_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_driveopen_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.

CodeMeaningUser impactMonitored event family
AUDIO_UNAVAILABLEGame/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.

CodeMeaningExpected final stateMonitored event family
ASPECT_CHANGED_MIDSESSIONWindow 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_MIDSESSIONOutput drive dropped below safety threshold.Session stops; upload may be blocked if artifacts are incomplete.capture.v2.stop, recording.session_ended
WINDOW_LOSTGame window disappeared, closed, or became unavailable.Session stops with terminal error.recording.window_lost, recording.window_timeout
INPUT_BLOCKED_ELEVATED_GAMEThe 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_MIDSESSIONWindows slept or hibernated during recording.Session stops with terminal error.capture.v2.stop
ENCODER_FAILURE_MIDSESSIONEncoder stopped producing valid output.Session stops; validator decides uploadability.capture.v2.nvenc.encode_failed, capture.v2.zero_video_output_watchdog
SETTINGS_CHANGED_MIDSESSIONMonitored 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.

CodeMeaningUser action
OS_VERSION_UNSUPPORTEDWindows build is too old.Update Windows.
WGC_NOT_SUPPORTEDWindows Graphics Capture is unavailable.Fix OS policy/SKU or reinstall supported Windows.
D3D11_UNAVAILABLEDirect3D 11 device creation failed.Update GPU drivers and reboot.
NO_GPU_ENCODERNo supported NVIDIA/AMD encoder found.Use supported hardware or update drivers.
NVENC_DRIVER_TOO_OLDNVIDIA driver is below minimum.Install a newer NVIDIA driver.
AMF_DRIVER_TOO_OLDAMD AMF runtime is below minimum.Install a newer AMD Adrenalin driver.
WASAPI_NO_DEFAULT_DEVICEWindows has no default audio output.Pick or connect an output device.
VM_PERF_INSUFFICIENTVM 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.

CodeMeaningRisk
VM_DETECTEDA hypervisor was detected.Capture quality depends on VM GPU setup.
LOW_VRAMGPU has low dedicated video memory.Higher risk of frame drops under load.
PROCESS_LOOPBACK_UNAVAILABLEPer-process audio capture is unavailable.Recording may include all system audio.
MULTI_GPUMultiple 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.

EventFieldMeaning
service.startup_context_adapterdriver_versionRaw Windows DXGI driver version, e.g. 32.0.15.6094.
service.startup_context_adapterdriver_version_rawSame value as a hex 64-bit.
service.startup_context_adapterdriver_version_nvidiaNVIDIA-branded XXX.YY form, e.g. 560.94. Populated only for adapters with vendor_id = 0x10DE.
service.startup_context_flagsprobe.has_nvidia_gputrue when cuInit + cuDeviceGetCount succeed.
service.startup_context_flagsprobe.default_encoder_idCurrently nvenc_h264_tex or empty.
service.startup_context_flagspolicy.encoderCurrently always nvenc_only (see EC-173).
service.startup_context_flagsnvenc_sdk_min_driver_versionThe 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.

EventLevelWhat it tells us
capture.v2.fps_profiler.eventWARNING/INFOFPS quality degraded, changed reason/severity, or recovered.
capture.v2.fps_low_detectedWARNINGSustained 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_detectedWARNINGMajority 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.qosWARNING/INFOCapture queue pressure, drops, escalations, and recovery behavior.
capture.v2.qos_saturatedWARNINGCapture queue stayed saturated long enough to warn.
capture.v2.qos_snapshotINFOPeriodic counters for seen/enqueued/dropped frames.
capture.v2.pool_pressureINFOTexture pool waits, held slots, availability, and pressure percentiles.
capture.v2.encode_stage.frame_metricsINFOEncode throughput, latency, lock waits, and encode failures.
capture.v2.packet_queue.depthINFOPacket queue depth and enqueue blocks.
capture.v2.mux_stage.write_metricsINFOMP4 write latency, disk I/O, and write failures.
capture.v2.zero_video_output_watchdogINFO/WARNING/ERRORDetects capture/encode flow with no muxed video output.
capture.v2.nvenc.need_more_inputWARNINGEncoder is repeatedly asking for more input.
capture.v2.nvenc.zero_bitstreamWARNINGEncoder returned an empty bitstream.
capture.v2.nvenc.encode_failedERRORNVENC encode call failed.
capture.v2.nvenc.lock_failedERROREncoder bitstream lock failed.
capture.v2.mux_stage.write_failedERRORMux stage failed to write packet data.

FPS profiler reasons

capture.v2.fps_profiler.event uses these reasons:

ReasonMeaningLikely owner
low_source_fpsCapture/source side is not producing frames fast enough.Capture/window/game performance.
capture_pool_pressureTexture pool saturation or pool-slot drops.GPU texture pool sizing or downstream backpressure.
encode_backpressureEncode latency or NVENC lock waits are above budget.Encoder/GPU/driver contention.
mux_backpressurePacket queue, mux, or disk I/O is slowing output.Disk or mux pipeline.
low_muxed_fpsMuxed FPS is low without a more specific detected cause.Needs cross-signal investigation.

Severity thresholds for a 60 FPS target:

SeverityCurrent threshold
goodWindow muxed FPS is at least 58 and drop ratio is below 5%.
degradedWindow muxed FPS is below 58 or drop ratio is at least 5%.
severeWindow muxed FPS is below 49.8 or drop ratio is at least 15%.
criticalWindow 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.

EventLevelWhat it tells us
recording.validationINFO/WARNINGFinal artifact validation result and diagnostics.
recording.session_endedINFO/WARNINGSession terminal notification outcome.
sqlite.session_endINFOLocal terminal session state was persisted.
sqlite.file_statusINFOLocal artifact upload state changed.
sqlite.sidecars_failedINFOCSV/JSON sidecars were failed because MP4 was not uploadable.

Current validation block reasons include:

ReasonMeaning
mp4_missingMP4 path is missing or cannot be statted.
mp4_too_smallMP4 exists but is below minimum file size.
mp4_no_framesCapture/metadata indicates no video frames.
mp4_duration_unreadableMP4 duration could not be read.
mp4_too_shortMP4 duration is below minimum session threshold.
csv_missingCSV path is missing.
csv_parse_errorCSV cannot be opened, has unexpected header, invalid rows, invalid timestamps, or invalid event JSON.
csv_non_monotonic_timestampsCSV timestamps moved backwards.
csv_missing_startCSV has no valid start event.
csv_missing_endCSV has no valid end event.
csv_duration_mismatchCSV duration is non-positive or does not match MP4/metadata tolerance.
json_missingJSON metadata path is missing.
json_parse_errorJSON cannot be opened or parsed.
json_duration_mismatchJSON 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.

EventLevelWhat it tells us
upload.file_startINFOUpload worker started an artifact.
upload.file_doneINFOUpload worker completed local state transition.
upload.s3_put_okINFOSingle PUT upload succeeded.
upload.multipart_startINFOMultipart upload started.
upload.multipart_okINFOMultipart upload completed.
upload.backoffINFOA 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_blockedWARNINGLegacy OGG upload was blocked.
upload.sidecar_blockedWARNINGCSV/JSON sidecar was blocked because MP4 is missing or not uploadable.
upload.file_not_readyWARNINGFile is not ready for upload yet.
upload.file_discardedWARNINGFile was discarded from upload flow.
upload.auth_rejectedWARNINGUpload URL request was rejected by auth.
upload.user_id_mismatchWARNINGLocal file user ID did not match authenticated user.
upload.presigned_url_failedERRORBackend did not return a usable presigned URL.
upload.invalid_presigned_urlERRORPresigned URL shape was invalid.
upload.file_missingERRORLocal file disappeared before upload.
upload.file_open_failedERRORLocal file could not be opened.
upload.s3_put_failedERRORS3 PUT failed.
upload.multipart_*_failedWARNING/ERRORMultipart part, complete, or abort flow failed.
upload.max_retriesERRORUpload 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.

EventLevelMeaning
recording.window_lostWARNINGTarget window was destroyed or lost.
recording.window_timeoutWARNINGTarget window stayed unavailable long enough to trigger timeout behavior.
recording.window_minimizedINFOTarget window was minimized.
recording.window_restoredINFOTarget window returned.
recording.input_idle_pause_timeoutINFOAuto-pause fired after input idle timeout.
idle_warning.reminder_firedINFOIdle 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.created severity 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_stop and 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_UNAVAILABLE failure signature should disappear entirely. CORE_COMMAND_TIMEOUT is the new honest "core busy" denial.
  • Since EC-525, a silent core /health no longer produces core.crash when the process is still alive: the UI emits core.unresponsive (once per episode), waits a 90 s grace period, then escalates with core.unresponsive_kill before killing+restarting. core.crash is reserved for real process exits — an exit_code of -1/null on a core.crash row 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).
EventLevelMeaning
recording.command_timeoutERRORA 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)WARNINGThe 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)ERRORThe 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)ERRORThe 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_writtenERROREmitted 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_unsupportedWARNINGThe 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_suppressedINFOA 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)INFOThe UI re-pushed the active lobby to a freshly (re)started core (EC-369).

Settings enforcement signals

Settings enforcement protects the recording baseline.

EventLevelMeaning
recording.blocked_settings_policyWARNINGRecording start was blocked by game settings policy.
recording.start_gate_autofix_blocked_for_restartWARNINGCore applied settings but game restart is required.
recording.start_gate_autofix_unfixableWARNINGCore could not automatically fix settings.
settings.apply_failedWARNINGApplying required game settings failed.
settings.restore_failedWARNINGRestoring original game settings failed.
settings.watchdog_runtime_errorWARNINGRuntime settings watchdog encountered a monitoring error.
recording.stopped_settings_changedINFORecording was stopped because settings changed mid-session.

Practical support flow

For a failed or suspicious session, inspect signals in this order:

  1. RecorderError code shown to the player, if present.
  2. recording.session_ended and local session final status.
  3. recording.validation block reason and diagnostics.
  4. capture.v2.fps_profiler.event and QoS snapshots around the same time.
  5. capture.v2.zero_video_output_watchdog, NVENC, mux, and disk I/O events.
  6. Upload events and local sqlite.file_status.
  7. 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.