feat: one reactions system for every game, on its own channel
Reactions did not sync, and when something appeared it floated in the middle of
the screen with no sign of who sent it. Four games had four implementations,
three broken in a different way:
- chess kept ONE shared `emote` slot in the match's game_state, so two players
reacting in the same second overwrote each other, and it carried no sender;
- ludo posted reactions through the MOVE endpoint, which replaces game_state
wholesale — sending an emote wiped turn_count and broke the sync loop. Its
receive side registered with mp.onEmoteReceived, which only stores a callback
that nothing ever invokes, so ludo players never saw a reaction at all;
- domino had a third variant, again a single shared slot;
- backgammon wrote a `last_emote` column no client ever read, so the opponent
saw nothing. (That table does not exist in the database either — see below.)
Reactions are chat, not game state. They now live in `chat_messages` on a
`match` channel via the new api/match-chat.php, so nothing about a reaction can
reach a board, a clock or a turn — a social feature must not be able to corrupt
a game in progress. The multiplayer sync path is untouched.
core/reactions.js is the single client implementation. Its central idea is
SEATS: a game registers which element belongs to which player, and every bubble
is then drawn attached to that player, carrying their name and avatar. That is
the actual fix for "you can't tell who is talking". Seat resolution falls back
to "the seat that isn't mine" in a two-player game, so a game can register its
opponent seat before it knows the opponent's id.
Only preset emotes and preset phrases are accepted, validated server-side —
free text between strangers in a live match is a moderation surface nobody is
staffing. The cooldown is enforced on the server, not just in the UI.
Also fixed, found on the way:
- ChessBoard has draw(), not render(). board.js called render() whenever a
themed piece image finished loading, so it threw every time and the board
never repainted with custom piece art.
- chess/scenes/game.js built an inline onerror="" whose body embedded the result
of emoji() — which returns <img src="..."> when a themed asset exists. Its
double quotes closed the attribute, breaking the markup and throwing a
SyntaxError on every bot game; the stray '"> rendered as text inside the
avatar. The fallback is wired in JS now.
Adds tools/ui-audit.mjs (drives the real app at three phone widths and reports
measurable layout defects) and tools/test-reactions.mjs (end-to-end reaction
check against a live deployment, with teardown).
Noted, not fixed here: backgammon_matches and backgammon_queue do not exist in
the database, so backgammon multiplayer has never worked. Out of scope for this
change and untouched.
Co-Authored-By:
Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
api/match-chat.php
0 → 100644
public/css/reactions.css
0 → 100644
public/js/core/reactions.js
0 → 100644
tools/shot_test.mjs
0 → 100644
tools/test-reactions.mjs
0 → 100644
tools/ui-audit.mjs
0 → 100644
Please register or sign in to comment