EN

Match Anatomy

A single match record as the atomic unit — three tabs for the before, the during and the after, a structured tactical brief written once before kickoff, branded source chips instead of raw URLs, and the deterministic verdict that closes the loop.

Part of the Paulopus case study. A single match is the atomic unit of this product. Everything else — the karma ledger, the home tabs, the analytics gauges — is derived from what one match record holds and how one match page presents it. This focus is about that page: the three tabs it splits into, the structured brief that fills the middle one, the FIFA replay card on the debrief, and the source chips that keep the whole thing honest.

One record, three moments in time

Decision. A match is not a single object frozen at kickoff. It has a before (a prediction that can change as news breaks), a during (a heavy tactical read written once, just before kickoff), and an after (the graded verdict and its appreciation). Cramming those into one screen buried the distinction; the reader couldn't tell a locked call from a post-match rationalization.

Approach. On 2026-06-13 the match page was split into three tabs — Pronostic, Brief, and Debrief — each mapped to a distinct field on the match record rather than to a view filter (PR#9). The underlying document already carried the pieces separately.

Artifact. The data/matches.json record shape, per architecture-summary.json:

fieldrole on the page
drafts[]pronostic revision history (latest = the live call)
detailedBriefthe structured pre-kickoff tactical read
debriefthe post-match appreciation and verdict
resultregulation + optional extraTime/penalties/decidedBy
karmathe graded outcome
replayUrloptional FIFA replay link

Result. Each of the 104 matches (M001M104) renders the same three-tab anatomy, with each tab backed by its own field and its own write gate — so a tab is empty until its content is actually due, not faked.

The structured DetailedBrief

Decision. The middle tab needed to be more than a paragraph. A real pre-match read has weather and pitch conditions, what each side intends to do, the individual duels that decide it, and a synthesized call — and those deserve structure, not prose the reader has to mine.

Approach. PR#9 introduced a heavy DetailedBrief type with named sections — conditions, per-team tactics, matchups, and a bottom line — gated by its own write-once, pre-kickoff rule and driven by a dedicated prompt in the writer routine.

Artifact. The brief's shape, drawn from the PR#9 description:

type DetailedBrief = {
  conditions: string;      // weather, pitch, stakes
  tactics: {               // per-team intent
    home: string;
    away: string;
  };
  matchups: string;        // the duels that decide it
  bottomLine: string;      // the synthesized call
};

Result. The Brief tab presents a consistent, sectioned tactical breakdown across every fixture instead of freeform text — written exactly once, before kickoff, so it reads as a genuine forecast rather than hindsight.

A pronostic that can change its mind

Decision. The first model locked a prediction write-once. But predictions should move: a confirmed lineup or a fresh injury report legitimately changes the call. An immutable lock made Paulopus look either stubborn or lucky, never responsive.

Approach. PR#13 replaced the immutable locked-prediction model with a drafts[] revision history and a single pronosticTrigger() entry point with three named triggers, revisable right up to kickoff. Crucially, ingest appends a new draft only on a real change (append-only-on-change semantics).

Artifact. The trigger surface, per the PR#13 description:

pronosticTrigger(kind: "opening" | "brief-update" | "lineup-confirmed")
// -> appended to drafts[] only when the call actually changes

Result. The Pronostic tab shows the current call (the latest draft) while the record retains the full trail of how it got there — and an earlier deadline-lock fallback (PR#8) guarantees that even a routine run outside the narrow lock window still promotes the latest early-read draft, so no match reaches kickoff without a pronostic.

Source chips instead of raw URLs

Decision. The pronostic and debrief cited real web sources — the writer routine is explicitly forbidden from fabricating them and capped at four searches per item, real https sources only. But those citations rendered as an ugly bulleted list of raw URLs, which read as debug output, not provenance.

Approach. On 2026-06-16, PR#15 replaced the bulleted raw URLs on both the pronostic and debrief tabs with a shared SourceChip component: a locally-generated favicon mark plus the bare domain, set in a pixel-bordered chip matching the retro UI.

Artifact. The before/after of a citation on the tab:

- - https://www.example-sport.com/article/2026/match-preview-12345
+ [◧ example-sport.com]   ← favicon mark + bare domain, pixel-bordered chip

Result. Provenance became a scannable row of branded chips. The favicon is generated locally rather than fetched, so the chips carry no external requests — the source is legible at a glance without leaking the reader's visit to third-party favicon services.

The FIFA replay card on the debrief

Decision. After a match, the single most valuable outbound link is the official replay. But FIFA replays are neither embeddable nor discoverable from any feed — there's no API to pull them.

Approach. PR#17 (2026-06-17) added an optional Match.replayUrl, surfaced as a pixel-styled thumbnail card at the top of the Debrief tab. Because the links can't be automated, they're curated by hand through a new pnpm replay <matchId> <fifaUrl> script. An attempt to automate this research inside the writer routine was later removed (PR#22), deferring curation entirely to the manual script.

Artifact. The curation command:

pnpm replay M042 https://www.fifa.com/…/replay
# sets Match.replayUrl -> renders the thumbnail card atop the debrief

Result. Matches that have a hand-verified replay show a clean, on-brand card linking straight to the official footage; matches without one simply omit the card, since replayUrl is optional.

The verdict that closes the loop

Decision. The Debrief tab isn't just prose — it's where the prediction gets graded. That grade has to be deterministic and grounded in what actually happened on the pitch, not a vibe.

Approach. lib/scorer.ts computes playedScore(result) = extraTime ?? regulation and realWinner(result) (penalties when present, else the played score), then scoreMatch()/scoreKarma() assign one of four verdicts and its points deterministically from the pronostic versus that played score and real winner.

Artifact. The verdict scale (architecture-summary.json):

verdictpoints
Perfect+1.5
Inspired+1
Missed0
RedCard−1

Result. Every debrief carries a verdict the reader can reconstruct from the scorecard, and no karma ledger is stored — deriveLedger(matches) recomputes the whole standing from each match's drafts[] and result on every read. The match record is the single source of truth, and the three-tab page is its full anatomy laid open.

Match Anatomy
  • slugmatch-anatomy-0
  • contentPart of the [Paulopus](/plant/paulopus#execution) case study. A single match is the atomic unit of this product. Everything else — the karma ledger, the home tabs, the analytics gauges — is derived from what one match record holds and how one match page presents it. This focus is about that page: the three tabs it splits into, the structured brief that fills the middle one, the FIFA replay card on the debrief, and the source chips that keep the whole thing honest. ## One record, three moments in time **Decision.** A match is not a single object frozen at kickoff. It has a *before* (a prediction that can change as news breaks), a *during* (a heavy tactical read written once, just before kickoff), and an *after* (the graded verdict and its appreciation). Cramming those into one screen buried the distinction; the reader couldn't tell a locked call from a post-match rationalization. **Approach.** On 2026-06-13 the match page was split into three tabs — Pronostic, Brief, and Debrief — each mapped to a distinct field on the match record rather than to a view filter (PR#9). The underlying document already carried the pieces separately. **Artifact.** The `data/matches.json` record shape, per `architecture-summary.json`: | field | role on the page | | --- | --- | | `drafts[]` | pronostic revision history (latest = the live call) | | `detailedBrief` | the structured pre-kickoff tactical read | | `debrief` | the post-match appreciation and verdict | | `result` | regulation + optional `extraTime`/`penalties`/`decidedBy` | | `karma` | the graded outcome | | `replayUrl` | optional FIFA replay link | **Result.** Each of the 104 matches (`M001`–`M104`) renders the same three-tab anatomy, with each tab backed by its own field and its own write gate — so a tab is empty until its content is actually due, not faked. ## The structured DetailedBrief **Decision.** The middle tab needed to be more than a paragraph. A real pre-match read has weather and pitch conditions, what each side intends to do, the individual duels that decide it, and a synthesized call — and those deserve structure, not prose the reader has to mine. **Approach.** PR#9 introduced a heavy `DetailedBrief` type with named sections — conditions, per-team tactics, matchups, and a bottom line — gated by its own **write-once, pre-kickoff** rule and driven by a dedicated prompt in the writer routine. **Artifact.** The brief's shape, drawn from the PR#9 description: ```ts type DetailedBrief = { conditions: string; // weather, pitch, stakes tactics: { // per-team intent home: string; away: string; }; matchups: string; // the duels that decide it bottomLine: string; // the synthesized call }; ``` **Result.** The Brief tab presents a consistent, sectioned tactical breakdown across every fixture instead of freeform text — written exactly once, before kickoff, so it reads as a genuine forecast rather than hindsight. ## A pronostic that can change its mind **Decision.** The first model locked a prediction write-once. But predictions should move: a confirmed lineup or a fresh injury report legitimately changes the call. An immutable lock made Paulopus look either stubborn or lucky, never *responsive*. **Approach.** PR#13 replaced the immutable locked-prediction model with a `drafts[]` revision history and a single `pronosticTrigger()` entry point with three named triggers, revisable right up to kickoff. Crucially, ingest appends a new draft only on a *real* change (append-only-on-change semantics). **Artifact.** The trigger surface, per the PR#13 description: ```ts pronosticTrigger(kind: "opening" | "brief-update" | "lineup-confirmed") // -> appended to drafts[] only when the call actually changes ``` **Result.** The Pronostic tab shows the current call (the latest draft) while the record retains the full trail of how it got there — and an earlier deadline-lock fallback (PR#8) guarantees that even a routine run outside the narrow lock window still promotes the latest early-read draft, so no match reaches kickoff without a pronostic. ## Source chips instead of raw URLs **Decision.** The pronostic and debrief cited real web sources — the writer routine is explicitly forbidden from fabricating them and capped at four searches per item, real `https` sources only. But those citations rendered as an ugly bulleted list of raw URLs, which read as debug output, not provenance. **Approach.** On 2026-06-16, PR#15 replaced the bulleted raw URLs on both the pronostic and debrief tabs with a shared `SourceChip` component: a locally-generated favicon mark plus the bare domain, set in a pixel-bordered chip matching the retro UI. **Artifact.** The before/after of a citation on the tab: ```diff - - https://www.example-sport.com/article/2026/match-preview-12345 + [◧ example-sport.com] ← favicon mark + bare domain, pixel-bordered chip ``` **Result.** Provenance became a scannable row of branded chips. The favicon is generated locally rather than fetched, so the chips carry no external requests — the source is legible at a glance without leaking the reader's visit to third-party favicon services. ## The FIFA replay card on the debrief **Decision.** After a match, the single most valuable outbound link is the official replay. But FIFA replays are neither embeddable nor discoverable from any feed — there's no API to pull them. **Approach.** PR#17 (2026-06-17) added an optional `Match.replayUrl`, surfaced as a pixel-styled thumbnail card at the top of the Debrief tab. Because the links can't be automated, they're curated by hand through a new `pnpm replay <matchId> <fifaUrl>` script. An attempt to automate this research inside the writer routine was later removed (PR#22), deferring curation entirely to the manual script. **Artifact.** The curation command: ```bash pnpm replay M042 https://www.fifa.com/…/replay # sets Match.replayUrl -> renders the thumbnail card atop the debrief ``` **Result.** Matches that have a hand-verified replay show a clean, on-brand card linking straight to the official footage; matches without one simply omit the card, since `replayUrl` is optional. ## The verdict that closes the loop **Decision.** The Debrief tab isn't just prose — it's where the prediction gets graded. That grade has to be deterministic and grounded in what actually happened on the pitch, not a vibe. **Approach.** `lib/scorer.ts` computes `playedScore(result) = extraTime ?? regulation` and `realWinner(result)` (penalties when present, else the played score), then `scoreMatch()`/`scoreKarma()` assign one of four verdicts and its points deterministically from the pronostic versus that played score and real winner. **Artifact.** The verdict scale (`architecture-summary.json`): | verdict | points | | --- | --- | | Perfect | +1.5 | | Inspired | +1 | | Missed | 0 | | RedCard | −1 | **Result.** Every debrief carries a verdict the reader can reconstruct from the scorecard, and no karma ledger is stored — `deriveLedger(matches)` recomputes the whole standing from each match's `drafts[]` and `result` on every read. The match record is the single source of truth, and the three-tab page is its full anatomy laid open.
  • date2026-07-24
  • descriptionA single match record as the atomic unit — three tabs for the before, the during and the after, a structured tactical brief written once before kickoff, branded source chips instead of raw URLs, and the deterministic verdict that closes the loop.
  • nameMatch Anatomy
  • typearticle
  • statepublished