The log that remembers both players
The shared game log learns to show two players at once, PvP gets a live panel of its own, and a scoring bug that only hurt guests surfaces along the way.
The game log component had only ever shown one player’s turns, fine for the Daily Challenge but a mismatch for both duel modes, where a second player’s words and fruit chips had nowhere to go. A wayfinder map, drilled out of an older PvP issue asking for a game log panel in the first place, locked a two-owner design: entries gain an optional owner, and the log mirrors itself, one side’s chips growing rightward and the other’s leftward, only when a log actually contains an opponent entry. Daily Challenge and every log recorded before this change keep the plain single-column layout unchanged.
AI Duel picked up the mirrored view first, the opponent’s words and fruit routed through the same two chip-pushing helpers the player’s own turns already used. PvP needed more: a batch endpoint to reconstruct history after a reload, since a resuming guest has no local memory of turns that happened before they arrived, and a builder that derives every chip from a turn’s own stored data rather than a word’s dictionary metadata, which the PvP client never has:
export function buildPvpLogEntries(
turn: Pick<PvpTurnRow, 'turnIndex' | 'word' | 'actor'> & { result: PvpTurnResult },
viewerRole: 'host' | 'guest'
): GameLogEntry[] {
const owner: DuelTurn = turn.actor === viewerRole ? 'player' : 'opponent';
const { result } = turn;
const eatenEdibles = result.eatenEdibles ?? [];
// ...derives every remaining chip field from the turn's own stored data.
The issue that specified this half of the work, though, undersold its own point: it covered reconstructing history and widening the end-of-game recap’s log toggle to PvP, but left out the actual live log panel on the page a PvP game is played on, which was the entire reason the original issue existed. Adding it meant raising the reserved chrome height to make room and re-verifying, across two real browser sessions playing against each other, that the on-screen keyboard stayed above the fold at phone width.
Working the PvP side surfaced a second, unrelated bug: the guest in a PvP game saw a length-multiplier badge in the UI that never actually multiplied their score. The engine had only ever applied the multiplier to its internal player actor, written back when the only kind of opponent was the AI, which was never supposed to get one, but PvP maps its guest onto that same opponent slot. One flag fixed it:
function actorGetsLengthMultiplier(isPlayer: boolean, config: DuelGameConfig): boolean {
return isPlayer || config.opponentMultiplierEnabled === true;
}
False for AI Duel, true for PvP, since a human guest deserves the same scoring the host gets. No historical leaderboard rows were recomputed; only final scores are stored, never a per-turn breakdown to recompute from, and the reasoning for leaving past games as they were is recorded directly on the bug’s own issue.