Run pointer-enabled candidate sessions across two Gamescope restarts, require compositor feedback after each reconnect, preserve the Sigil PID, and document the recovery contract.
- commit
- 8d1c8eabf67cc190c6f4c8f7d8662bdb2fc364a2
- parent
- 9215ed8d6d1d3790d46e61baa6450de8960931ed
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 30 | ++++++++++-- |
| M | scripts/ | 73 | +++++++++++++++++++++++++++++- |
@@ -1180,10 +1180,15 @@ Sigil reads `GAMESCOPE_MOUSE_FOCUS_DISPLAY` from the bootstrap root, reconnects
11801180 to the active local display, and samples `QueryPointer` at no more than 60 Hz.11811181 It also reads `GAMESCOPE_CURSOR_VISIBLE_FEEDBACK` from that active root so the11821182 client overlay disappears when Gamescope hides its cursor. Missing, malformed,1183-or unreachable Xwayland state disables the separately negotiated pointer1184-feedback capability; relative uinput remains available without guessed cursor1185-coordinates. The service `DISPLAY=:0` line is retained as an explicit fallback1186-for configurations created before `xwayland_display` was added.1183+or unreachable Xwayland state during startup disables the separately1184+negotiated pointer feedback capability. After successful startup, losing1185+Gamescope publishes pointer feedback as unavailable, then reconnects the1186+bootstrap display with a bounded 100 ms to 2 second backoff. A complete1187+reconnect re-interns both Gamescope atoms, discovers the replacement native1188+surface, and validates one active-display sample before publishing coordinates1189+again. Relative uinput remains available without guessed cursor coordinates.1190+The service `DISPLAY=:0` line is retained as an explicit fallback for1191+configurations created before `xwayland_display` was added.11871192 11881193 Audio is optional and must resolve one exact PipeWire sink, never a microphone.11891194 The appliance owns a persistent 48 kHz stereo null sink so capture does not@@ -1533,10 +1538,14 @@ sudo libinput debug-events --device "$pointer_node"
15331538 target/debug/sigil-probe \15341539 --node-id <host-node-id> \15351540 --frames 120 \1536- --pointer-smoke1541+ --pointer-smoke \1542+ --pointer-feedback-smoke15371543 ```15381544 1539-The probe must report `pointer_smoke=ok`. It uses the native pointer-surface1545+The probe must report `pointer_smoke=ok` and `pointer_feedback_smoke=ok`.1546+The feedback check requires an immediately available compositor position and1547+visibility sample; an input acknowledgment with unavailable coordinates fails1548+the proof. The pointer smoke uses the native pointer-surface15401549 dimensions negotiated in the media `HostHello`, not the potentially downscaled15411550 encoded dimensions. On a 2560x1600 Gamescope surface, before the ordinary15421551 motion, evtest must show the synchronization sequence:@@ -1547,6 +1556,15 @@ reports relative `POINTER_MOTION` and `POINTER_BUTTON` events. The probe fails
15471556 closed if the host omits the native pointer-surface dimensions. The interactive15481557 `xmessage` activation remains the downstream Gamescope/Xwayland proof.15491558 1559+For compositor replacement recovery, keep the candidate Sigil daemon running,1560+record its PID, and restart `gamescope-session-plus@steam.service` twice. After1561+each restart, wait for the `gamescope` PipeWire node and the bootstrap root1562+property to return, then rerun the pointer and feedback smoke without replaying1563+the redeemed invitation. Both runs must pass, the Sigil PID must remain1564+unchanged, and the host log must contain one `Gamescope Xwayland pointer1565+feedback reconnected` event per restart. The packaged hardware UAT performs1566+this sequence after its fixed-mode session checks.1567+15501568 For a deterministic pre-demo gamepad proof, leave `evtest "$gamepad_node"`15511569 running on the host and run this from the client checkout:15521570 @@ -78,7 +78,13 @@ done
7878 real_config="$HOME/.config/sigil-spark/host.toml"7979 real_sigil="$HOME/.local/libexec/sigil-spark/current/sigil"8080 real_identity="$(sed -n 's/^identity_path = "\(.*\)"$/\1/p' "$real_config")"81+uinput_block="$(awk '82+ /^\[uinput\]$/ { emit = 1 }83+ emit && /^\[/ && $0 != "[uinput]" { exit }84+ emit { print }85+' "$real_config")"8186 [[ -n "$real_identity" && -f "$real_identity" ]]87+[[ "$uinput_block" == '[uinput]'$'\n'* ]]8288 [[ -x "$real_sigil" ]]8389 original_ready() {8490 local invocation pid@@ -196,6 +202,7 @@ probe_node_id="$(sed -n 's/^node_id=//p' "$private/probe-identity.log")"
196202 197203 pw_dump="$(command -v pw-dump)"198204 pw_link="$(command -v pw-link)"205+xprop="$(command -v xprop)"199206 gst_launch="$(command -v gst-launch-1.0)"200207 gst_inspect="$(command -v gst-inspect-1.0)"201208 ffmpeg="$(command -v ffmpeg)"@@ -212,9 +219,11 @@ width = 1280
212219 height = 800213220 framerate = 60214221 codec = "h264"215-input_mode = "log"222+input_mode = "uinput"216223 ffmpeg_path = "$ffmpeg"217224 225+$uinput_block226+218227 [gamescope_pipewire]219228 node_name = "gamescope"220229 media_class = "Video/Source"@@ -235,9 +244,11 @@ state_path = "$state"
235244 source = "gamescope-pipewire"236245 framerate = 60237246 codec = "h264"238-input_mode = "log"247+input_mode = "uinput"239248 ffmpeg_path = "$ffmpeg"240249 250+$uinput_block251+241252 [gamescope_pipewire]242253 node_name = "gamescope"243254 media_class = "Video/Source"@@ -508,6 +519,7 @@ run_probe_cycle() {
508519 local invitation_path="${5:-}"509520 local slow_consumer_ms="${6:-}"510521 local expected_path_mode="${7:-direct}"522+ local pointer_feedback_smoke="${8:-false}"511523 local log="$raw/$mode-$transport-$request_id.log"512524 local command=(513525 "$probe"@@ -523,8 +535,15 @@ run_probe_cycle() {
523535 [[ -n "$invitation_path" ]] && command+=(--invitation "$invitation_path")524536 [[ -n "$slow_consumer_ms" ]] && command+=(--slow-consumer-ms "$slow_consumer_ms")525537 [[ "$expected_path_mode" != relay ]] || command+=(--relay-only)538+ if [[ "$pointer_feedback_smoke" == true ]]; then539+ command+=(--pointer-smoke --pointer-feedback-smoke)540+ fi526541 timeout --signal=TERM --kill-after=5s 45s "${command[@]}" >"$log" 2>&1527542 validate_probe "$log" "$transport" "$size" "$request_id" "$slow_consumer_ms" "$expected_path_mode"543+ if [[ "$pointer_feedback_smoke" == true ]]; then544+ grep -Fxq pointer_smoke=ok "$log"545+ grep -Fxq pointer_feedback_smoke=ok "$log"546+ fi528547 }529548 530549 validate_host_recovery() {@@ -661,6 +680,47 @@ for cycle in $(seq 1 10); do
661680 run_probe_cycle fixed grouped-v3 1280x800 "$((1100 + cycle))"662681 done663682 validate_host_recovery "$raw/fixed-host.log"683+684+fixed_candidate_pid="$(systemctl --user show -p MainPID --value \685+ "$fixed_candidate_unit.service")"686+[[ "$fixed_candidate_pid" =~ ^[0-9]+$ && "$fixed_candidate_pid" -gt 1 ]]687+pointer_reconnects_before="$(grep -Fc \688+ 'Gamescope Xwayland pointer feedback reconnected' "$raw/fixed-host.log" || true)"689+for cycle in 1 2; do690+ systemctl --user restart gamescope-session-plus@steam.service691+ gamescope_ready=false692+ for _ in $(seq 1 600); do693+ if systemctl --user is-active --quiet gamescope-session-plus@steam.service \694+ && DISPLAY=:0 "$xprop" -root GAMESCOPE_MOUSE_FOCUS_DISPLAY >/dev/null 2>&1 \695+ && "$pw_dump" | jq -e '696+ .[]697+ | select(.type == "PipeWire:Interface:Node")698+ | select(.info.props["node.name"] == "gamescope")699+ | select(.info.props["media.class"] == "Video/Source")700+ ' >/dev/null701+ then702+ gamescope_ready=true703+ break704+ fi705+ sleep 0.1706+ done707+ [[ "$gamescope_ready" == true ]]708+ [[ "$(systemctl --user show -p MainPID --value \709+ "$fixed_candidate_unit.service")" == "$fixed_candidate_pid" ]]710+ expected_reconnects="$((pointer_reconnects_before + cycle))"711+ for _ in $(seq 1 100); do712+ reconnects="$(grep -Fc 'Gamescope Xwayland pointer feedback reconnected' \713+ "$raw/fixed-host.log" || true)"714+ [[ "$reconnects" -ge "$expected_reconnects" ]] && break715+ sleep 0.1716+ done717+ [[ "$reconnects" -ge "$expected_reconnects" ]]718+ run_probe_cycle "pointer-recovery-$cycle" iroh-moq 1280x800 \719+ "$((1200 + cycle))" "" "" direct true720+ sed -E $'s/\x1B\\[[0-9;]*[[:alpha:]]//g' \721+ "$raw/pointer-recovery-$cycle-iroh-moq-$((1200 + cycle)).log" \722+ >"$evidence/pointer-recovery-$cycle.log"723+done664724 stop_candidate665725 666726 native_timer="goq-uat-native-$invocation_id"@@ -753,6 +813,9 @@ fi
753813 echo external_cqp_minimum_fps=55754814 echo "external_cqp_observed_fps=$external_cqp_observed_fps"755815 echo cross_state_capture_contention=rejected816+ echo gamescope_pointer_restart_cycles=2817+ echo gamescope_pointer_feedback_recovery=pass818+ echo gamescope_pointer_daemon_pid_stable=pass756819 echo "native_size=$native_size"757820 echo native_performance_gate=observational758821 echo "native_observed_fps=$native_observed_fps"@@ -791,5 +854,9 @@ sha256sum -c "$private/original-identity.sha256"
791854 echo original_identity_preserved=pass792855 echo original_release_preserved=pass793856 } >"$evidence/summary.env"794-chmod 0600 "$evidence/summary.env" "$evidence"/probes/*.log "$evidence"/*-capture.log857+chmod 0600 \858+ "$evidence/summary.env" \859+ "$evidence"/probes/*.log \860+ "$evidence"/*-capture.log \861+ "$evidence"/pointer-recovery-*.log795862 cat "$evidence/summary.env"