ADR-045: Three Sync Dimensions (Accuracy, Coverage, Readability)¶
Status: Accepted (May 8, 2026 — supersedes the v1 Proposed of this ADR dated May 6, which was a five-dimension version. The v1 is preserved in git history.) Date: 2026-05-08 Context: Cici's twin redesign includes a scorecard on the Sync (Play) screen that tells the user where to spend their next play session — "train your weakest dimension at X/100" + a recommendation hero card built on top. The first version of this ADR proposed five dimensions (Accuracy, Consistency, Coverage, Information, Peer Readability) and acknowledged that two of them didn't really map to user actions. The May 8 review with Jonathan confirmed: drop the two non-actionable ones, ship three.
At a glance
What it decides: The Sync (Play) scorecard shows three diagnostic dimensions — Accuracy, Coverage, Readability — each a 0–100 number that maps to exactly one play action. This narrowed the earlier five-dimension framing (it dropped Consistency and Information as non-actionable), and these three explain the headline sync_score rather than replacing it.
- Each dimension = one action: low Accuracy → play more rounds; low Coverage → play your thin category; low Readability → play multiplayer.
- Actionability is the bar: Consistency and Information were dropped because neither has a clear "do this next" — they were measurement-without-direction.
- Vector view, not a composite: the three stay separate (no averaged "Sync Readiness" number) — harder to game and more diagnostic.
- Compute on demand, no migration: recalculated per API call from existing tables; the helper still computes all five, the UI surfaces three.
- Watch: dimension distribution in the first 2 weeks (clustering low = too punishing, high = too generous) and drift-override frequency.
flowchart TD
S["Sync (Play) scorecard"] --> A["Accuracy<br/>Does your twin predict you?"]
S --> C["Coverage<br/>How balanced is your play?"]
S --> R["Readability<br/>Do other humans predict you?"]
A --> AA["Action: play more rounds"]
C --> CA["Action: play your weakest category"]
R --> RA["Action: join multiplayer"]
The three in-force dimensions and the single play action each one maps to.
Decision¶
Three dimensions on the Sync (Play) scorecard. Each one is a 0–100 number (or "—" for users who haven't played enough yet to compute it). Each one maps to exactly one concrete action the user can take. The existing sync_score continues to be the headline number on Twin / share cards / rankings; these three are the diagnostic breakdown that explains why the sync_score is what it is.
The three dimensions, in plain English¶
1. Accuracy — "How often is your twin predicting your choices correctly?"
The model has been trying to predict your decisions all along. Accuracy is its hit rate over your last 10 predictions, multiplied by 100. The action when it's low: play more rounds. The model needs more games to learn your patterns.
2. Coverage — "How balanced is your play across decision domains and contexts?"
You've played some scenarios about governance, some about team conflict, some about resource constraints, etc. Coverage measures how spread out that play has been across (a) the four scenario categories and (b) the ten context triggers (crisis, team conflict, opportunity, etc.). The math is "have you tried lots of different kinds of decisions, or have you mostly stuck to one type?" The action when it's low: play a scenario in whichever area you've been thin in. The recommendation engine names the specific category for you.
3. Readability — "How well do the people around you predict your choices?"
Different from Accuracy. Accuracy measures the model predicting you. Readability measures other humans predicting you, in multiplayer rounds where teammates guess each other's answers. Both can be high, both can be low, and the gap between them is itself an interesting signal — but each tells you something the other doesn't. The action when it's low: play multiplayer. A null Readability (because you've never played multiplayer) is the strongest version of this nudge.
Why three, not five¶
The first version of this ADR proposed five dimensions. Two got dropped in the May 8 rewrite because they failed the user-energy-spent test — they weren't actionable.
-
Consistency measured how concentrated your decision style is within a category — i.e., when you face governance scenarios, do you mostly reach for the same driver, or are you all over the map? Real signal, but the action wasn't real. "Be more consistent" isn't something a user can will themselves into. It's also not unambiguously good — most humans are contextual decision-makers (different driver in different contexts), and per ADR-011 even genuinely rigid players cap at ~85% on this metric. So a low Consistency score has no clear "do this" attached.
-
Information measured how surprising your decisions are to the rule engine — high means the model is still learning from you, low means it has you figured out. Genuinely interesting if you're nerdy about model calibration, but neither direction is good or bad. "Raise your Information score" would mean making your decisions more random. "Lower your Information score" would mean your model has you mapped, which is fine but isn't a goal. Either way, no action.
Both lived in v1 of this ADR. Both are dropped from the scorecard in v2. The three remaining dimensions all map to clear "do this next" instructions.
How the recommendation engine works on top¶
The Sync (Play) screen has a hero card that says something like "Train your weakest dimension: Coverage (32/100)" with a one-tap CTA. The decision tree behind the card is now a single switch:
weakest dimension is Accuracy → "Play more Quick Rounds"
weakest dimension is Coverage → "Play a Quick Round in {weakest category}"
weakest dimension is Readability → "Join a multiplayer session"
Plus two override cases that beat the weakest-dim rule when they fire:
- Drift detection — if your twin missed predictions on ≥60% of the last 5 rounds, the card switches to "Your twin missed 4 of the last 5 — pattern shift?" That's higher-priority than any dimension call.
- Multiplayer gap — if Readability is null and you've played some solo, the card pushes you toward multiplayer specifically (overrides "Train Accuracy" since the gap-fill is more useful than more solo data).
Already implemented at src/lib/twin/recommendations.ts.
Vector view, not composite score¶
The three dimensions are displayed separately as three numbers — not averaged into one composite "Sync Readiness Score" or anything like that. The existing sync_score already plays the single-headline-number role on Twin pages, share cards, and rankings. Adding a second composite competes with it and confuses the picture.
The diagnostic value lives in seeing the three numbers individually. "Coverage 32, Accuracy 78, Readability 44" tells a story (your model knows you well within a narrow range, you haven't tried many kinds of scenarios yet, peers don't read you super well — try multiplayer in unfamiliar categories). Averaging that to "51" hides everything.
This is also a defense against gaming: with one composite, users who care about the number figure out which component contributes most and over-train it. With three separate numbers, there's nothing to game — each one is its own honest signal.
What "compute on demand" means¶
Sync stores a lot of data per user (player_decision_profiles has a couple dozen columns; category_patterns and context_rules are JSONB blobs that update every game). The dimensions could be added as three more columns on that table, computed at write time, and read back fast.
We chose not to. Reasons:
- Each dimension is a small SQL query against existing tables. At current scale (~50 active users) the read cost is negligible.
- Storing them adds write paths — every game-end has to recompute and persist three values. That's three more places that can drift out of sync with the data they're supposedly summarizing.
- If read load becomes a problem later, caching or precomputation is a one-day change. Nothing about the math requires storage.
So all three dimensions are recalculated each time the API endpoint is called. Already implemented at src/lib/twin/sync-dimensions.ts.
"Cold start" — what to show before there's enough data¶
A user who just signed up doesn't have meaningful values for all three dimensions yet:
- Accuracy needs at least 1 prediction to have a hit rate at all
- Coverage needs at least 1 game played
- Readability needs at least 1 multiplayer session
When a dimension can't be computed yet (the data isn't there), the helper returns null. The UI shows "—" in that slot with a hover/tap tooltip explaining what unlocks it ("Play 1+ multiplayer sessions to unlock Readability"). Don't hide unready dimensions — the consistent shape across users matters more than the visual cleanliness of hiding an empty box. Hiding makes the scorecard a different shape for new vs. established users, which makes it harder to learn.
Rationale¶
The decisive frame was Jonathan's: "what would users find most useful for where they should spend their energy in terms of the kinds of scenarios they're doing?" Once that's the question, the answer is "metrics that translate to a play action." Five dimensions where two of them have no clear corresponding move is wrong-shape for that surface.
The "vanity signals" instinct is correct and not a flaw. A scorecard that's vanity-with-direction (Strava is a fair analogy — your weekly mileage is a vanity number that drives you to run more) is exactly what drives engagement and learning. A scorecard that's measurement-without-direction is what drives confusion. The three dimensions are vanity-with-direction; the dropped two were measurement-without-direction.
The model under the hood is rigorous (two-stage rule engine + Claude prediction, 12 behavioral signals, calibrated confidence). The display doesn't need to mirror that rigor — it needs to be legible and motivating.
Alternatives Considered¶
-
Five dimensions (v1 of this ADR). Rejected. Two of the five had no user action attached, which clutters the scorecard and burdens new users with terms they can't act on.
-
Two dimensions (just Accuracy + Coverage, drop Readability). Rejected. Readability is the only signal that captures how the people around you predict you (versus how the model does), and it's the one that pushes users toward multiplayer — a healthy nudge given that team features depend on multiplayer play.
-
Composite single score replacing or augmenting sync_score. Rejected. sync_score already plays the headline-number role on Twin / share cards. A second composite (whether replacing or supplementing) competes for attention and is gameable in ways three separate numbers aren't.
-
Stored dimensions on
player_decision_profiles. Rejected for v1. Compute on demand is fast enough at current scale; storage adds write-path complexity for no measurable read benefit. Can revisit later if read load forces it. -
Surface Consistency + Information on a "deeper signals" tab. Considered. Rejected because they don't have a clear product job even on a secondary surface — they're not vanity-with-direction, they're vanity-without-direction, which has no surface where it earns its keep. Easy to add later if a real use case appears (e.g., an "advanced mode" for sophisticated users who want to see the model's calibration); the helper already computes both, so the data is there.
Discussion¶
The hardest call was dropping Information specifically. It's the dimension that most speaks to whether the model has accurately captured someone — high Information means there's still pattern the model hasn't caught; low means the model has them figured. To a sophisticated user, that's arguably the most important number on the scorecard.
The argument that won: the scorecard isn't for sophisticated users. It's for engagement and direction. The pull toward "rigorous psychological measurement" is the path the original five-dimension version went down, and it produced a surface where two-fifths of the dimensions had no user response. Better to ship three vanity-with-direction signals that drive play than five rigorous-looking signals where users bounce off the unactionable ones.
The Information dimension is preserved in code (src/lib/twin/sync-dimensions.ts still computes it; the API still returns it). If a "deep stats" surface ever earns its product job, it's one prop change away from being visible.
The "drift detection" recommendation kind (the override that fires when the last 5 rounds were mostly misses) is doing real work in the rec engine. It's the one case where the recommendation isn't "train weakest" — it's "something has shifted, sit with it for a round." That's a meaningfully different message and the right one when the data shows pattern instability. Don't conflate it with the dimension scorecard.
Consequences¶
- Three dimensions on the Sync (Play) scorecard, not five. The helper at
src/lib/twin/sync-dimensions.tscontinues to compute all five — the UI just surfaces three. - Recommendation engine simplifies.
pickWeakestTrainable()ranks across three dimensions instead of four (was already excluding Information; now also excludes Consistency). - No new tables, no migration. Pure derivation continues.
- sync_score is unchanged — still the single headline number on profile/share/team contexts. Three dimensions are the diagnostic breakdown that explains it, not a replacement.
- Naming on the UI: "Accuracy", "Coverage", "Readability" (drops "Peer" — context tells you it's about how others read you). If "Coverage" reads too techy in user testing, swap to "Breadth"; tunable via the helper's display name.
- Watch: dimension distribution across active users in the first 2 weeks of UI exposure. If everyone clusters at 30–50 across all three, math is too punishing. If everyone is at 80+, too generous. Tunable via the helper without UI changes.
- Watch: how the recommendation engine's drift-detection override interacts with the weakest-dim recommendation. If users see "drift" recommendations too often, lower its frequency (currently triggers at ≥60% miss rate over 5 rounds).
Key files:
- src/lib/twin/sync-dimensions.ts — computeSyncDimensions() returns all 5; UI surfaces 3
- src/lib/twin/recommendations.ts — three-dimension decision tree, drift-detection override
- src/app/api/twin/dimensions/route.ts — wire endpoint
- Cici's Twin (Play) UI (pending) — surfaces Accuracy, Coverage, Readability prominently
Revisit — 2026-05-31 (ADR-045 holds; no new Play-screen dimension)¶
Triggered by the two measurement bodies of work that landed after this ADR: the scenario dimension cube (docs/research/foundations/scenario-dimension-cube.md — 12 structural axes + 2 content axes + 2 measured outcomes) and the measurement schema (docs/research/foundations/measurement-schema.md — 70 primitives across capture/extraction/aggregation/exposure + 9 measured outcomes M1–M9). Both expanded what Pulse measures per player. The revisit question: does the three-dimension set still hold, or does a new Play-screen dimension now earn its slot?
Conclusion: the three-dimension set holds. No candidate passes the actionability test. The new measurement work lands on the surfaces it was designed for — measured outcomes on insights/agent (ADR-065), cube axes as a future Coverage-targeting refinement — not on the play-action scorecard.
Cross-check against the schema's 9 measured outcomes (M1–M9): none is surfaced on the Play screen, by design. The M-series is a different layer (emergent player×system properties → insights X3/X4 and agent prose X5), not the play-action scorecard (X2 ← aggregation A2). Each fails the Play-screen actionability test: - The two that come closest collapse onto existing dimensions. M4 (calibration drift) — its only play-action is "play more" (= Accuracy), and its drift case is already this ADR's drift-detection override (E19); "trust your certainty less" is a behavioral adjustment, not a play action. M3 (domain-confidence asymmetry) — its only play-action is "play domain X" (= Coverage), and per-category scoring is explicitly deferred by ADR-070 pending the category-axis and light-paper questions. - The rest (M1 identity load, M2 subjective stakes, M5 trust state, M6 aspiration gap, M7 frame-restructuring, M8 cross-domain stability, M9 context bleed) are descriptive/insight or substrate-gated, with no "do this next."
Cross-check against the cube's 12 structural axes: every structural axis (deadline, stakes magnitude, authority, visibility, default-outcome, …), run through the actionability test, produces the same candidate action — "play a scenario whose shape is X" — which is Coverage's action at finer grain. A "time-pressure regime" slot would be a second dimension with an identical CTA to Coverage, violating this ADR's "each maps to exactly one concrete action" and vector-view anti-redundancy principles. The cube therefore sharpens where Coverage points, not how many numbers the scorecard has. This is the schema's Q4c (cube structural-axis stratification), which is substrate-gated and not yet ready: cube vectors aren't tagged on the corpus (~29 of 254 decomposed; live generation rolled back at v1.8.1 for latency), and no UI lets a player seek a scenario by deadline/stakes/authority, so the implied CTA can't be delivered today.
On the schema's architectural-independence finding (offered as a reason to lower the promotion bar): it cuts the other way here. Because new dimensions are additive and the helper already computes the two demoted signals, a genuine candidate can be promoted in a one-prop change the day it earns an action — so there's no need to pre-add defensively. The bar stays "has a distinct play action." Nothing clears it.
Forward-pointer (enhancement to an existing dimension, not a new one): when Q4c ships and the corpus carries cube vectors, Coverage's recommendation engine should upgrade its CTA from "play a {weakest category}" to "play a {weakest cube-region}" scenario (e.g., "you've only decided under time pressure — try a sleep-on-it one"). That sharpens Coverage's targeting; it does not add a scorecard slot. Track under Q4c, not as a new dimension.
Net: Accuracy / Coverage / Readability unchanged. Consistency and Information remain computed-but-unsurfaced. No migration, no code change.