Price-layer token foundation + the price triplet, built natively on it
PHASE 0 finding, reported before building: the token layer this order asked
me to establish ALREADY EXISTS and already matches HANDOFF.md exactly.
web/src/app/globals.css :root carries the design's surfaces, borders, text
ramp, fonts and grade colours byte-for-byte (aligned 2026-07-16), and
lib/colorContract.js already encodes the green-is-edge-only and
glow-is-A-tier-only laws with a test enforcing them. The stack is Tailwind v4
CSS-first (no config file) with components styled by inline style={{}} reading
var(--x) — 1,916 such reads — so CSS custom properties are the only vehicle
the stack natively consumes. Creating a second parallel layer would have meant
two competing sources of truth, so this EXTENDS the existing one.
PHASE 1 — additive only. globals.css gains one colour the system did not have,
the priced-out blue (#8fb2de + tints), plus a tokenized A-tier glow and the
fair-leg tints. The block writes the LAWS into the token layer itself — green
= takeable edge only, glow = A-tier only, amber = caution + the fair leg, red
= miss/negative only, blue = edge priced out, JetBrains Mono = all data — and
a test asserts every newly-declared name is new (zero collisions, zero
overrides). No existing hardcoded style was touched and no live surface was
migrated: the diff over existing files is 149 insertions, 0 deletions.
PHASE 2 — lib/valueState.js is the single verdict function; the component
renders what it returns and never re-derives one. VALUE fires only on
ev >= 2 AND a takeable price, mirroring src/config/valueEngine.js with a test
that cross-checks both files and fails on drift. Five states: VALUE (green),
EDGE-NOT-TAKEABLE (blue), NO EDGE (grey, stated at full voice), QUARANTINE
(model leg withheld, book+fair stand), REFUSAL (nothing rendered). Free tier
is gated at the wire — tierGating strips model_odds and sets
model_price_locked, so the lock is real rather than a blur over data already
sent; book and fair pass through on every tier because the fair leg is never
the paywall. Wired into the landing hero (data was already on /api/hero-prop)
and the read card, where the projection block reads first and the triplet sits
beside it, not in place of it. Ledger and public profile are out of scope —
no fair-odds columns exist there.
PHASE 3 — induced all six states in a real browser and read back computed
styles, not just markup. Green resolves on VALUE alone: rgb(0,212,160) on the
model leg and verdict; the +11.7%-EV-at-210 row renders rgb(143,178,222) blue
and a white model leg; quarantine shows MODEL "—" with book and fair intact;
refusal renders no legs at all; free tier renders a lock bar with book -120 and
fair -104 still honest. Landing hero on live data: book -153, fair -129, model
-343, VALUE +21.1% vs fair at +28.1% EV. At a real 390px column the three legs
hold at 117px each with no horizontal overflow and fair no smaller than its
neighbours.
Induction caught a real bug that markup review would not have: the "VS FAIR"
figure compared BOOK to fair, printing "VALUE · -6.5% VS FAIR" — a
contradiction on screen. The design's own two worked examples pin the formula
as MODEL minus FAIR in implied-probability percentage points; modelVsFair now
reproduces both exactly (+2.9 and -1.8) and a test locks them. The figure is
shown only when its sign agrees with the verdict, so a row that clears the EV
bar on the book price while our price sits level with fair leads with the EV
instead of a number that reads as a contradiction.
Tests 3539 passed / 290 suites, web build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
@@ -105,6 +105,45 @@ function mapScanToGradeResult(input = {}) {
|
||||
// Session 42 — Player Intelligence additions. All OPTIONAL + self-hiding:
|
||||
// they only render once the engine supplies them (Session 43 data pipeline).
|
||||
...buildIntelFields(input),
|
||||
// Session 66 — the PRICE LAYER (book · fair · model). Self-hiding: absent
|
||||
// unless the engine supplied real prices, so a read with no market prices
|
||||
// renders the projection alone rather than an empty gauge. NEVER
|
||||
// fabricated — the design file's numbers are a spec, not a fallback.
|
||||
...buildPriceTriplet(input),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* buildPriceTriplet(input) — the optional price-layer block for the card.
|
||||
*
|
||||
* Returns `{}` (section stays hidden) unless the engine supplied at least a
|
||||
* book price AND a fair price: with no honest de-vigged number there is no
|
||||
* price story to tell, and a lone book price is just the market repeated back.
|
||||
*
|
||||
* `model_price_locked` is set by the SERVER (utils/tierGating strips
|
||||
* `model_odds` for unentitled tiers and flags it) — the free tier never
|
||||
* receives the model price, so the lock is real, not a client-side blur over
|
||||
* data that was already sent. Book and fair always pass through: the fair leg
|
||||
* is never the paywall.
|
||||
*/
|
||||
function buildPriceTriplet(input) {
|
||||
const n = (v) => {
|
||||
if (v == null || v === '') return null;
|
||||
const x = typeof v === 'number' ? v : Number(v);
|
||||
return Number.isFinite(x) ? x : null;
|
||||
};
|
||||
const book = n(input.book_odds);
|
||||
const fair = n(input.fair_odds);
|
||||
if (book == null || fair == null) return {};
|
||||
return {
|
||||
priceTriplet: {
|
||||
book_odds: book,
|
||||
fair_odds: fair,
|
||||
model_odds: n(input.model_odds),
|
||||
ev_pct: n(input.ev_pct),
|
||||
quarantine_reason: input.quarantine_reason || null,
|
||||
model_price_locked: input.model_price_locked === true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,4 +181,4 @@ function buildIntelFields(input) {
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = { mapScanToGradeResult, statLabel, computeEdge, isPhosphorConfirmed, toSignals, buildIntelFields };
|
||||
module.exports = { mapScanToGradeResult, statLabel, computeEdge, isPhosphorConfirmed, toSignals, buildIntelFields, buildPriceTriplet };
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/* ============================================================
|
||||
VYNDR — THE PRICE-LAYER LAW (Session 66).
|
||||
|
||||
Plain CommonJS so .tsx components import it AND the Jest suite requires it
|
||||
directly (same pattern as colorContract.js / vyndrTokens.js / playerName.js).
|
||||
|
||||
ONE function decides what a price row is allowed to SAY. The triplet renders
|
||||
whatever this returns and never re-derives a verdict of its own, so there is
|
||||
exactly one place where "is this value?" is answered.
|
||||
|
||||
THE FIVE HONESTY STATES (design: `Vyndr Price Triplet.dc.html`, ACT 01):
|
||||
|
||||
VALUE green — ev >= VALUE_EV_THRESHOLD *AND* the book price is
|
||||
inside the takeable band. Both. Raw positive EV is
|
||||
NOT value: +11.7% EV at -210 is a juiced price we
|
||||
will not call a play.
|
||||
PRICED_OUT blue — real edge, untakeable price. We won't call a juiced
|
||||
price a value, and we won't pretend the edge wasn't
|
||||
there. This state exists so green never has to lie
|
||||
in either direction.
|
||||
NO_EDGE grey — the honest number is already priced. Stated at full
|
||||
voice, never hidden. That "no" is the instrument
|
||||
working.
|
||||
QUARANTINE amber — the MODEL leg is withheld (graded against inputs we
|
||||
no longer trust). Book and fair still render: we
|
||||
never hide the honest fair number because a
|
||||
different leg is poisoned. RARE — as of Jul 2026 the
|
||||
only quarantined rows are one frozen historical
|
||||
cohort, so this is not a routine state.
|
||||
REFUSAL dim — no fair price we'd defend. Nothing renders. No
|
||||
fabricated price, no empty gauge.
|
||||
|
||||
TRUTH LAW: the triplet never prints a number it can't stand behind. A leg
|
||||
with no value renders absent, never a zero and never a placeholder.
|
||||
============================================================ */
|
||||
|
||||
/* ---- Backend mirror -----------------------------------------------------
|
||||
These MUST match src/config/valueEngine.js. The browser can't require the
|
||||
backend module, so the constants are duplicated here and a test
|
||||
(priceTriplet.test.js) reads BOTH files and fails if they drift — the same
|
||||
guard used for playerName.js. If you tune the band, tune it in both. */
|
||||
const TAKEABLE_ODDS_CEILING = -160; // most-juiced favourite we'll promote
|
||||
const TAKEABLE_ODDS_MAX = 200; // longest dog we'll promote
|
||||
const VALUE_EV_THRESHOLD = 2; // a real edge, not rounding
|
||||
|
||||
const STATES = Object.freeze({
|
||||
VALUE: 'VALUE',
|
||||
PRICED_OUT: 'PRICED_OUT',
|
||||
NO_EDGE: 'NO_EDGE',
|
||||
QUARANTINE: 'QUARANTINE',
|
||||
REFUSAL: 'REFUSAL',
|
||||
// Not a verdict — a display state. The model leg exists and is honest, the
|
||||
// viewer just isn't entitled to it (free tier). Book + fair render normally.
|
||||
NO_VERDICT_LOCKED: 'NO_VERDICT_LOCKED',
|
||||
});
|
||||
|
||||
/** Strict numeric parse — `Number(null) === 0` is the fabrication bug this
|
||||
* whole layer exists to prevent, so nothing coerces. */
|
||||
function num(v) {
|
||||
if (v == null || v === '') return null;
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
/** isTakeable(american) — inside the promotable band. Mirrors valueEngine. */
|
||||
function isTakeable(american) {
|
||||
const a = num(american);
|
||||
if (a == null) return false;
|
||||
return a >= TAKEABLE_ODDS_CEILING && a <= TAKEABLE_ODDS_MAX;
|
||||
}
|
||||
|
||||
/** isValue(american, evPct) — takeable AND clears the EV threshold. Both. */
|
||||
function isValue(american, evPct) {
|
||||
const ev = num(evPct);
|
||||
return isTakeable(american) && ev != null && ev >= VALUE_EV_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* deriveValueState(row) — the single verdict function.
|
||||
*
|
||||
* row: { book_odds, fair_odds, model_odds, ev_pct, quarantine_reason,
|
||||
* refused, model_price_locked }
|
||||
*
|
||||
* Order matters and encodes the honesty ordering:
|
||||
* 1. REFUSAL first — with no fair price there is nothing honest to show.
|
||||
* 2. QUARANTINE next — the model leg is untrustworthy, so no verdict may be
|
||||
* computed from it, but book+fair still stand.
|
||||
* 3. Then the three real verdicts.
|
||||
* A locked (free-tier) model leg is NOT quarantine and NOT refusal: the data
|
||||
* exists and is honest, the viewer just isn't entitled to it — so the verdict
|
||||
* is withheld rather than asserted, and book+fair render normally.
|
||||
*/
|
||||
function deriveValueState(row = {}) {
|
||||
const fair = num(row.fair_odds);
|
||||
const book = num(row.book_odds);
|
||||
|
||||
// 1. Refusal — no fair price we'd defend (or no market to price against).
|
||||
if (row.refused === true || fair == null || book == null) return STATES.REFUSAL;
|
||||
|
||||
// 2. Quarantine — model leg withheld; book + fair still render.
|
||||
if (row.quarantine_reason) return STATES.QUARANTINE;
|
||||
|
||||
// Free-tier lock: entitled data withheld, not absent. No verdict is claimed
|
||||
// (the verdict IS the model leg), but this is not quarantine — the row is
|
||||
// honest, it's just gated.
|
||||
if (row.model_price_locked === true) return STATES.NO_VERDICT_LOCKED;
|
||||
|
||||
const model = num(row.model_odds);
|
||||
const ev = num(row.ev_pct);
|
||||
// Without a model price or an EV there is no verdict to render — treat as a
|
||||
// withheld model leg rather than asserting "no edge" we didn't compute.
|
||||
if (model == null || ev == null) return STATES.QUARANTINE;
|
||||
|
||||
// 3. The three real verdicts.
|
||||
if (isValue(book, ev)) return STATES.VALUE;
|
||||
if (ev >= VALUE_EV_THRESHOLD) return STATES.PRICED_OUT; // edge, untakeable price
|
||||
return STATES.NO_EDGE;
|
||||
}
|
||||
|
||||
/** valueStateColor(state) — the ONE mapping from verdict to token.
|
||||
* Green appears here exactly once, on VALUE, and nowhere else. */
|
||||
function valueStateColor(state) {
|
||||
switch (state) {
|
||||
case STATES.VALUE:
|
||||
return 'var(--g-a)';
|
||||
case STATES.PRICED_OUT:
|
||||
return 'var(--priced-out)';
|
||||
case STATES.QUARANTINE:
|
||||
return 'var(--amber)';
|
||||
case STATES.NO_EDGE:
|
||||
case STATES.REFUSAL:
|
||||
case STATES.NO_VERDICT_LOCKED:
|
||||
default:
|
||||
return 'var(--text-2)';
|
||||
}
|
||||
}
|
||||
|
||||
function impliedProb(american) {
|
||||
const a = num(american);
|
||||
if (a == null) return null;
|
||||
return a > 0 ? 100 / (a + 100) : -a / (-a + 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* modelVsFair(modelOdds, fairOdds) — the "+2.9% VS FAIR" figure on the verdict
|
||||
* line. It is the gap between OUR price and the honest de-vigged price, in
|
||||
* implied-probability PERCENTAGE POINTS (not a ratio, and NOT book-vs-fair).
|
||||
*
|
||||
* Derived from the design file's own two worked examples, both of which this
|
||||
* reproduces exactly:
|
||||
* STATE 1 book +125 · fair +110 · model +98 → "+2.9% VS FAIR"
|
||||
* STATE 3 book +118 · fair +104 · model +112 → "-1.8% VS FAIR"
|
||||
* Positive = the model prices it SHORTER than fair (we think it's likelier
|
||||
* than the honest number says). Null unless both legs are real.
|
||||
*/
|
||||
function modelVsFair(modelOdds, fairOdds) {
|
||||
const pm = impliedProb(modelOdds);
|
||||
const pf = impliedProb(fairOdds);
|
||||
if (pm == null || pf == null) return null;
|
||||
return Math.round((pm - pf) * 1000) / 10;
|
||||
}
|
||||
|
||||
/** fmtOddsAmerican(v) — display an American price, or null for honest absence.
|
||||
* Never returns '0', never returns a placeholder number. */
|
||||
function fmtOddsAmerican(v) {
|
||||
const n = num(v);
|
||||
if (n == null) return null;
|
||||
const r = Math.round(n);
|
||||
return r > 0 ? `+${r}` : String(r);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
STATES,
|
||||
TAKEABLE_ODDS_CEILING,
|
||||
TAKEABLE_ODDS_MAX,
|
||||
VALUE_EV_THRESHOLD,
|
||||
isTakeable,
|
||||
isValue,
|
||||
deriveValueState,
|
||||
valueStateColor,
|
||||
modelVsFair,
|
||||
impliedProb,
|
||||
fmtOddsAmerican,
|
||||
};
|
||||
Reference in New Issue
Block a user