mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 03:13:45 +00:00
109 lines
4.8 KiB
Markdown
109 lines
4.8 KiB
Markdown
---
|
||
phase: 4
|
||
title: "Phase 4: Meanings on the wire"
|
||
status: completed
|
||
priority: P1
|
||
effort: "3h"
|
||
dependencies: [3]
|
||
---
|
||
|
||
# Phase 4: Meanings on the wire
|
||
|
||
## Overview
|
||
|
||
Carry a word's senses in the two messages that introduce a word to the client —
|
||
`PlayedWord` and `GameStarted` — regenerate the Go and JS types and the binary fixtures,
|
||
and have the room attach the senses where it already renders those messages.
|
||
|
||
## Requirements
|
||
|
||
- Functional: `proto/noitu/v1/game.proto` gains
|
||
```proto
|
||
// One definition of a word as Wiktionary gives it: the part of speech it
|
||
// sits under, in Vietnamese ("danh từ"), empty when the heading was not one
|
||
// the builder knows, and the definition stripped of markup at at most 200
|
||
// characters. Plain text both: the client renders them as text, never as
|
||
// markup.
|
||
message Sense {
|
||
string pos = 1;
|
||
string gloss = 2;
|
||
}
|
||
message PlayedWord {
|
||
…
|
||
// At most five. Empty when the dictionary has no definition for the word.
|
||
repeated Sense meanings = 7;
|
||
}
|
||
message GameStarted {
|
||
…
|
||
repeated Sense opening_meanings = 9;
|
||
}
|
||
```
|
||
(Validation session 1: `Sense` instead of a bare string, so the label is a field.)
|
||
- Functional: `wsapi.Dictionary` gains `Meanings(word string) []dictionary.Sense`.
|
||
`game.Dictionary` does not change; the engine never reads a meaning. `convert.go` gains
|
||
`Senses([]dictionary.Sense) []*noituv1.Sense`.
|
||
- Functional: `convert.PlayedWord` takes the senses (`PlayedWord(m game.Move, byMe bool,
|
||
meanings []dictionary.Sense)`) and its one caller, `sendTurnUpdate` (`room.go:903`, also
|
||
used by the resume path), passes `r.dict.Meanings(move.Word)`; `sendGameStarted` fills
|
||
`OpeningMeanings` from `r.dict.Meanings(r.opening)`. Both paths are per recipient already;
|
||
the lookup happens once per move, before the per-seat loop.
|
||
- Functional: the reconnect path (`GameStarted` + last `TurnUpdate`) carries meanings
|
||
without further change; a test asserts a resumed client sees the opening's and last word's
|
||
senses.
|
||
- Functional: `buf generate` regenerates `server/gen/` and `web/src/lib/proto/`;
|
||
`go test ./internal/wsapi -update` regenerates `proto/testdata/`; the JS decode test reads
|
||
the new field.
|
||
- Functional: the hand-built dictionaries in `wsapi` tests implement `Meanings`; at least
|
||
one returns senses so the assertion is not vacuous.
|
||
- Non-functional: frame size grows by up to ~1 KB per move. Nothing in the client or server
|
||
caps frames near that.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
room.handleSubmit ── engine.Submit ──► Move ──► meanings := r.dict.Meanings(move.Word)
|
||
for each seat: PlayedWord(move, byMe, meanings)
|
||
room.sendGameStarted ─────────────────────────► OpeningMeanings: r.dict.Meanings(r.opening)
|
||
```
|
||
|
||
The bot room path renders through the same `sendTurnUpdate`, so bot moves carry meanings
|
||
without a second change.
|
||
|
||
## Related Code Files
|
||
|
||
- Modify: `proto/noitu/v1/game.proto`
|
||
- Regenerate: `server/gen/noitu/v1/game.pb.go`, `web/src/lib/proto/**`, `proto/testdata/*`
|
||
- Modify: `server/internal/wsapi/room.go` — `Dictionary` interface, `sendGameStarted`,
|
||
the `PlayedWord(` call in `sendTurnUpdate`
|
||
- Modify: `server/internal/wsapi/convert.go` — `PlayedWord` signature, `Senses`
|
||
- Modify: `server/internal/wsapi/*_test.go` — fake dictionaries gain `Meanings`; new
|
||
assertions on `Played.Meanings` and `OpeningMeanings`, including after a resume
|
||
- Modify: `web/tests/game-wire.test.js` — the fixture decode asserts the new fields
|
||
|
||
## Implementation Steps
|
||
|
||
1. Edit the schema; `buf lint`; `buf generate`.
|
||
2. Interface and room changes; fix compile errors in tests by adding `Meanings` to fakes.
|
||
3. Add the assertions; `go test ./internal/wsapi -update` then `go test ./... -race`.
|
||
4. `cd web && npm test` — the wire test decodes the regenerated fixtures.
|
||
5. `make proto-check` — committed generated code matches the schema.
|
||
|
||
## Success Criteria
|
||
|
||
- [x] `buf lint` clean; `make proto-check` clean.
|
||
- [x] A bot game and a room game deliver `Played.Meanings` with `Pos` and `Gloss` for a word
|
||
the fake dictionary has senses for, and an empty list for one it does not.
|
||
- [x] A resumed session's `GameStarted.OpeningMeanings` and replayed `TurnUpdate.Played.Meanings`
|
||
are populated.
|
||
- [x] `proto/testdata/` fixtures regenerated and the JS suite decodes them.
|
||
|
||
## Risk Assessment
|
||
|
||
**Field numbers.** `PlayedWord` uses 1–6, `GameStarted` 1–8; 7 and 9 are free and nothing
|
||
is reserved in either. Signal: `buf breaking` complaint. Response: none expected; adding a
|
||
repeated field is wire-compatible, and there is one client.
|
||
|
||
**Meanings on `PlayerEliminated.suggestions`.** Out of scope by the plan's non-goals; a
|
||
reviewer may ask. Response: the suggestions are shown to somebody who just lost the turn,
|
||
a list of words, and a meaning under each would be a second UI. Not this plan.
|