Game-state feed #2

Closed
opened 2026-10-17 02:09:11 +00:00 by mintopia · 6 comments
Owner

What to build

Prefactor for Discord Rich Presence and the race state websocket. The host keeps a single store of the current game state, fed from Lua patches through a new Host.state(key, value) function (ADR 0008), with native reads only where the scripts can't reach the data. Initial keys: current screen, game mode, event/track name, and online lobby status (none/hosting/joined, player count). Consumers subscribe to changes. BLUR_STATE_LOG=1 logs every change so the feed can be checked by itself.

Acceptance criteria

  • With BLUR_STATE_LOG=1, moving between menus logs the screen changes
  • Starting an event logs mode and track name
  • Creating or joining an online/LAN lobby logs the lobby status and player count, and updates as players join or leave
  • Host-side consumers can subscribe to changes (used by the Discord and websocket tickets)
  • No measurable frame-time cost when no consumer and no log is active
  • Verified on BLUR_ENGINE=recomp

Blocked by

None (can start immediately)

## What to build Prefactor for Discord Rich Presence and the race state websocket. The host keeps a single store of the current game state, fed from Lua patches through a new `Host.state(key, value)` function (ADR 0008), with native reads only where the scripts can't reach the data. Initial keys: current screen, game mode, event/track name, and online lobby status (none/hosting/joined, player count). Consumers subscribe to changes. `BLUR_STATE_LOG=1` logs every change so the feed can be checked by itself. ## Acceptance criteria - [ ] With `BLUR_STATE_LOG=1`, moving between menus logs the screen changes - [ ] Starting an event logs mode and track name - [ ] Creating or joining an online/LAN lobby logs the lobby status and player count, and updates as players join or leave - [ ] Host-side consumers can subscribe to changes (used by the Discord and websocket tickets) - [ ] No measurable frame-time cost when no consumer and no log is active - [ ] Verified on `BLUR_ENGINE=recomp` ## Blocked by None (can start immediately)
mintopia removed their assignment 2026-10-17 07:38:07 +00:00
Author
Owner

Implemented game-state feed (ADR 0008): src/host/game_state.{h,cpp} store + subscribe(), Host.state(key,value) Lua import, BLUR_STATE_LOG=1 logging, patches/Screens/Commands.post.lua wrapping screen/mode/event/track/lobby/players setters. Unit test game_state passes. Verified on recomp (career_race + mp_menus plans): screen changes, mode, event+track logged. NOT verified: live lobby host/join player counts (no plan drives a server session; wrappers on NetRace Create*/Join*/EnterLobby/Multiplayer.GetLobbySlots are untested). No consumers/log active = one hash-map compare per Lua call, no timing measured. mdrive.py gained DEVPATCH/NETON env overrides.

Implemented game-state feed (ADR 0008): src/host/game_state.{h,cpp} store + subscribe(), Host.state(key,value) Lua import, BLUR_STATE_LOG=1 logging, patches/Screens/Commands.post.lua wrapping screen/mode/event/track/lobby/players setters. Unit test game_state passes. Verified on recomp (career_race + mp_menus plans): screen changes, mode, event+track logged. NOT verified: live lobby host/join player counts (no plan drives a server session; wrappers on NetRace Create*/Join*/EnterLobby/Multiplayer.GetLobbySlots are untested). No consumers/log active = one hash-map compare per Lua call, no timing measured. mdrive.py gained DEVPATCH/NETON env overrides.
Author
Owner

Follow-up. Fixed lobby hooks after testing on recomp against a local blur-online server (NETON=1, BLUR_ONLINE_URL=ws://127.0.0.1:18080/socket): hosting a LAN game logs lobby=hosting, track, players=1. Found Multiplayer.RefreshLobby never fires in the lobby and GetLobbySlots only returns data when the lobby screen rebuilds its roster, so players now comes from wrapping GetLobbySlots (updates when the game refreshes the roster). Removed the ticket ref from the Lua header comment. All 17 ctest tests pass. Frame cost: no per-frame hooks remain; wrappers run only on screen/mode/lobby/race calls; no-op set() = one mutex+map compare. Not timed with fps measurement. NOT verified: a second client joining/leaving (two-instance LAN test could not discover the host on loopback), so joined role and player-count updates on join/leave are untested.

Follow-up. Fixed lobby hooks after testing on recomp against a local blur-online server (NETON=1, BLUR_ONLINE_URL=ws://127.0.0.1:18080/socket): hosting a LAN game logs lobby=hosting, track, players=1. Found Multiplayer.RefreshLobby never fires in the lobby and GetLobbySlots only returns data when the lobby screen rebuilds its roster, so players now comes from wrapping GetLobbySlots (updates when the game refreshes the roster). Removed the ticket ref from the Lua header comment. All 17 ctest tests pass. Frame cost: no per-frame hooks remain; wrappers run only on screen/mode/lobby/race calls; no-op set() = one mutex+map compare. Not timed with fps measurement. NOT verified: a second client joining/leaving (two-instance LAN test could not discover the host on loopback), so joined role and player-count updates on join/leave are untested.
mintopia removed their assignment 2026-10-17 10:26:08 +00:00
Author
Owner

Attempt 6 update (commits d1f36eb, 5dc9d4d, a48582f on harmonic/task-9).

Fixed

  • Comment policy: the earlier critic findings are fixed, and a re-run of /comment-check found 3 more, also fixed (wrap renamed hook_after, header comments trimmed). That re-run returned FAIL (3) and was not repeated after the fixes. TSTRING constant added.
  • Bug found while testing LAN join: the feed's Lua hook kept only the first return value of the wrapped game functions. NetRace.JoinLanServer returns 3 values (start, customrace, event_id), so a joining client lost its race setup. DTLS was established, the client then sent nothing, and the host logged "DTLS receive timeout" and the game showed "Unable to connect to session". hook_after now passes every return value through and runs the hook under pcall, so a feed error can't break a game call. Checked with a Lua harness (3 returns including a nil, hook that throws). In-game runs with the fix still log screen changes.
  • BLUR_NETTRACE bind lines now include the errno reason.

Verified on recomp

  • Menu screen changes, mode, event and track logged (earlier attempts, and again this attempt).
  • Hosting a LAN game logs lobby=hosting, track, players=1 (several runs, two instances on one Mac with BLUR_PORTMAP/BLUR_ADAPTER_IP).
  • Frame cost, feed patch on vs removed, no consumer and no log (career_race with vsync off, presents/s median over the 35 s throttle segment, 2 runs each): on 130 / 97, off 85 / 87. The spread is run-to-run noise and the feed-on runs were not slower, so there is no measurable cost.
  • ctest: 18/18 pass.

Not verified: a client joining or leaving (lobby=joined, players 1→2→1). Every join attempt before the fix failed for the reason above. Runs with the fix couldn't finish because the Mac's screen locked during the session (CGSSessionScreenIsLocked=true): the game hangs at boot or drops key presses. Needs one two-instance run on an unlocked machine. Driver plans: /tmp/t9/host.py, /tmp/t9/client.py, /tmp/t9/lan1.sh.

Attempt 6 update (commits d1f36eb, 5dc9d4d, a48582f on harmonic/task-9). **Fixed** - Comment policy: the earlier critic findings are fixed, and a re-run of /comment-check found 3 more, also fixed (`wrap` renamed `hook_after`, header comments trimmed). That re-run returned FAIL (3) and was not repeated after the fixes. `TSTRING` constant added. - **Bug found while testing LAN join:** the feed's Lua hook kept only the first return value of the wrapped game functions. `NetRace.JoinLanServer` returns 3 values (start, customrace, event_id), so a joining client lost its race setup. DTLS was established, the client then sent nothing, and the host logged "DTLS receive timeout" and the game showed "Unable to connect to session". `hook_after` now passes every return value through and runs the hook under `pcall`, so a feed error can't break a game call. Checked with a Lua harness (3 returns including a nil, hook that throws). In-game runs with the fix still log screen changes. - `BLUR_NETTRACE` bind lines now include the errno reason. **Verified on recomp** - Menu screen changes, mode, event and track logged (earlier attempts, and again this attempt). - Hosting a LAN game logs `lobby=hosting`, track, `players=1` (several runs, two instances on one Mac with BLUR_PORTMAP/BLUR_ADAPTER_IP). - Frame cost, feed patch on vs removed, no consumer and no log (`career_race` with vsync off, presents/s median over the 35 s throttle segment, 2 runs each): on 130 / 97, off 85 / 87. The spread is run-to-run noise and the feed-on runs were not slower, so there is no measurable cost. - ctest: 18/18 pass. **Not verified:** a client joining or leaving (`lobby=joined`, players 1→2→1). Every join attempt before the fix failed for the reason above. Runs with the fix couldn't finish because the Mac's screen locked during the session (CGSSessionScreenIsLocked=true): the game hangs at boot or drops key presses. Needs one two-instance run on an unlocked machine. Driver plans: /tmp/t9/host.py, /tmp/t9/client.py, /tmp/t9/lan1.sh.
mintopia removed their assignment 2026-10-17 14:25:41 +00:00
Author
Owner

Attempt 7: last acceptance criterion verified (commit on harmonic/task-9: header comment fix).

Join/leave verified on recomp: two-instance LAN run (screen unlocked this time, /tmp/t9/lan1.sh) with the hook_after fix.

  • Host: lobby=hosting, track=UK_London_Hackney_4, players=1 → 2 (client joins) → 1 (client leaves) → 2 (rejoin), then race start logs event=Multiplayer_Racing and the Ingame screens.
  • Client: lobby=joined, players=2; on leave lobby=none, players=0, track=; rejoin lobby=joined, players=2; race loads and runs; when the session ends, lobby=none/players=0/event=/track=.

Other

  • /comment-check: FAIL (1). The Commands.post.lua header said "loads once" but the file has a reload guard. Header now gives the guard's real purpose: stop a reload wrapping the hooks twice.
  • ctest 18/18, 6 full runs in a row. host_net failed twice when run right after the game instances exited (looks like port reuse). It passed on rerun.

All acceptance criteria are now met.

Attempt 7: last acceptance criterion verified (commit on harmonic/task-9: header comment fix). **Join/leave verified on recomp**: two-instance LAN run (screen unlocked this time, /tmp/t9/lan1.sh) with the `hook_after` fix. - Host: `lobby=hosting`, `track=UK_London_Hackney_4`, `players=1` → `2` (client joins) → `1` (client leaves) → `2` (rejoin), then race start logs `event=Multiplayer_Racing` and the Ingame screens. - Client: `lobby=joined`, `players=2`; on leave `lobby=none`, `players=0`, `track=`; rejoin `lobby=joined`, `players=2`; race loads and runs; when the session ends, `lobby=none`/`players=0`/`event=`/`track=`. **Other** - /comment-check: FAIL (1). The Commands.post.lua header said "loads once" but the file has a reload guard. Header now gives the guard's real purpose: stop a reload wrapping the hooks twice. - ctest 18/18, 6 full runs in a row. `host_net` failed twice when run right after the game instances exited (looks like port reuse). It passed on rerun. All acceptance criteria are now met.
Author
Owner

Attempt 8: fixed the comment violation the verifier flagged (commit on harmonic/task-9).

  • patches/Screens/Commands.post.lua:
    • Removed the banner header.
    • Renamed the reload guard global GameStateFeed to GameStateHooksInstalled, so the name says what the guard is for.
  • src/host/lua_patch.cpp: the Host.state doc said only nil clears a value. Any type that isn't a string, number or bool clears it, and the doc now says that.

Checks:

  • luac -p passes on the patch.
  • recomp boot smoke test with BLUR_STATE_LOG=1: hooks still install and log screen, mode, lobby and players.
  • ctest 18/18.
  • /comment-check over 72217f3..HEAD: PASS.
Attempt 8: fixed the comment violation the verifier flagged (commit on harmonic/task-9). - `patches/Screens/Commands.post.lua`: - Removed the banner header. - Renamed the reload guard global `GameStateFeed` to `GameStateHooksInstalled`, so the name says what the guard is for. - `src/host/lua_patch.cpp`: the `Host.state` doc said only nil clears a value. Any type that isn't a string, number or bool clears it, and the doc now says that. Checks: - `luac -p` passes on the patch. - recomp boot smoke test with `BLUR_STATE_LOG=1`: hooks still install and log screen, mode, lobby and players. - ctest 18/18. - /comment-check over `72217f3..HEAD`: PASS.
Author
Owner

Completed and merged by Harmonic (task 9).

Completed and merged by Harmonic (task 9).
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
mintopia/blur#2
No description provided.