WI-015: Teaching cost
WI-015: Teaching cost
Narrowed at spec review. This item originally proposed personal mechanic reactions as well. Writing the design showed the ordering was wrong — see What was deferred, and why below. Scope is now teaching cost only.
Problem
Nothing records what a game cost to teach. That is a real tabletop concern with no home in the schema and no equivalent on BoardGameGeek: complexity ratings describe the game, not the experience of explaining it to four people on a Tuesday.
It is also the part of the original proposal that needs no controlled vocabulary, so it can ship now.
In scope
sessions.teachMinutes(integer, nullable) — how long the teach took this time. It varies by group, so it belongs on the play, not the game.sessions.wasTaught(boolean, nullable) — whether anyone was taught at all. Distinguishes “nobody needed teaching” from “not recorded”, which a nullteachMinutesalone cannot.user_game_teaching—userId,gameId,canTeach,typicalTeachMinutes. A standing capability: “I can teach Brass.”- Regenerated
packages/sharedschemas.
Out of scope
- Mechanic reactions of any kind — deferred, see below.
- Deriving
canTeachfrom session history. Asking is cheaper and more honest than inferring from a handful of plays. - Any UI or recommendation logic.
What was deferred, and why
The original proposal conflated three things with different lifetimes:
| Lifetime | Powers | Status | |
|---|---|---|---|
| Play observation — “trading confused everyone” | one session | history, feed | Deferred |
| Standing preference — “I like engine-building” | evolves | discovery | Deferred |
| Teaching capability — “I can teach Brass” | per user-game | group planning | This item |
Two reasons for the split.
Teaching capability is not a mechanic reaction. It is a user↔game capability that merely correlates with complexity. Modelling it alongside mechanic opinions would be a category error, and awkward to unpick later.
The other two depend on a vocabulary that does not exist. Reactions only help discovery if they reference a controlled mechanic vocabulary, which WI-020/WI-028 produce (ADR-0017). Building them first means keying opinions to free-text strings that later need remapping — and that migration is unverifiable, because nobody can tell whether “engine building” and “engine-building” were the same person’s opinion.
They should be revisited once the catalogue exists. The proposed shape is preserved in this item’s history.
Definition of done
-
sessions.teachMinutesandsessions.wasTaughtexist, both nullable — one-tap logging is unaffected (ADR-0007). -
user_game_teachingexists with a unique(userId, gameId), cascading on both. -
teachMinutesrejects zero and negatives at the contract level. - A session records
wasTaught = falsedistinctly from not recording it — a test asserting the three states (true,false,null) round-trip, since collapsing false into null is the easy mistake. - “Which games can this user teach” is answerable in one query, and indexed.
-
packages/sharedexports derived schemas for the new table; a test asserts they track the columns. - Gate green.
Verification
pnpm --filter db testpnpm --filter @tabletop/shared testpnpm gateNotes
teachMinutes sits on the session rather than the game because teaching time is a property of the group, not the box. typicalTeachMinutes on user_game_teaching is the user’s own estimate — deliberately not an average of their sessions, which would be a derived value and would need the same trigger discipline as the shelf projection for no real gain.