Touchstone 0.1#
Touchstone 0.1 is the open, reproducible standard for turning a card condition
assessment — measured centering plus an enumerated list of defects — into a
grade.
Given the same enumeration, any implementation of this rubric produces the same
number: the arithmetic is the standard, not a proprietary black box. The reference
implementation lives alongside this document as scoring.mjs, interpreting the
data in rubric.json; this file is the human-readable prose version of the
same rules. Philosophy: forgive centering drift near mint, escalate as it
becomes the card's defining flaw, and punish damage. A card can drift off
center and still be a 10; a crushed corner or a paper break cannot hide behind a
good average.
How a score is computed#
- Each region —
centering,corners,edges,surface, and (config-gated)print— starts at the base score, 1000 (the flawless ceiling), separately per face. In plain terms: every face begins as a perfect card, judged four ways under a rubric with noprint_attributestable, five under one that defines it (see Print quality below). - Every defect and every non-zero centering deviation subtracts a penalty from
its region. A region's sub-score is
1000 − Σ(penalties), floored at 0 — a region can't go negative no matter how many defects stack. In plain terms: each flaw takes points off the category it belongs to, and a category can be emptied but never owes points. - A face's
side_pointsis the minimum of its region sub-scores (four without aprint_attributestable, five with one) — the worst region caps the whole side. In plain terms: a face is only as good as its worst category. - The card's
pointsis the worse of the two sides (pure bottleneck — a flawless back cannot rescue a damaged front). In plain terms: the card is only as good as its worse face. Which face that is, and how a tie between them resolves, is specified in Binding face and binding region below. pointsis floored to an integer before the ladder (custom rubrics may produce fractional penalties; the ladder only ever sees whole numbers). In plain terms: fractions are dropped before grading.- The ladder converts points to a grade:
raw = floor(points / 50) × 0.5, thengrade = raw ≥ 9.5 ? 10 : max(raw, 1). The 9.5 slot is deliberately promoted to 10 — the market's 9→10 value cliff, expressed as arithmetic. Grade never reports below 1. In plain terms: every 50 points is half a grade; 950 or better is a 10 because the 9.5 slot is promoted.
Binding face and binding region#
A score result MUST name the face and the region that produced the grade.
The binding face is the face with the lower side_points. Ties MUST
resolve to the front.
The binding region is the lowest-scoring region on the binding face.
Ties MUST resolve to the earliest region in the fixed order
centering → corners → edges → surface → print, where print participates
only when the rubric enables the print-attribute ladders. This order is
normative: two implementations that disagree on a tie produce different
binding_region values for the same card and are not interoperable.
Centering#
Centering is a measurement, not a defect list entry. Per face, you supply
lr_pct (the left border's share of left+right border width) and tb_pct (the
top border's share of top+bottom); 50.0 is perfectly centered on that axis.
Deviation is the larger of the two axis offsets from 50:
deviation = max(|lr_pct − 50|, |tb_pct − 50|). Deviation is the max axis, not the sum of both axes — a card off on only one axis isn't punished twice.Inputs are accepted at any precision but quantized to 0.1 point before scoring, for determinism.
The penalty is a progressive, piecewise-linear curve per face. Each curve is a list of segments
{ up_to, slope }, sorted ascending; a segment spans from the previous segment'sup_to(0 for the first) to its own, and the final segment is open-ended (up_to: null). The penalty accumulatesslope × spanacross every segment the deviation crosses, then is rounded half-up ONCE at the end — not banker's rounding (JavaScript's defaultMath.roundand Python'sround()both round half-to-even in some cases; this rubric always rounds an exact.5up). Concretely:penalty = floor(Σ slopeᵢ × spanᵢ + 0.5). A scalar slopesis exactly the one-segment curve[{ up_to: null, slope: s }], so the two forms share one semantics.Front curve (deviation in percentage points):
Segment Slope (pts/pt) Penalty at segment end 0 → 10 10 100 10 → 15 15 175 15 → 20 20 275 20 → 30 12 395 30 → 35 20 495 35 → ∞ 40 — Back curve:
Segment Slope (pts/pt) Penalty at segment end 0 → 40 2 80 40 → ∞ 10 — Why these knees. The curve is shaped to the philosophy above: drift near mint is forgiven, and each knee is a point at which off-centering stops being a quibble and starts being the thing you notice about the card. Read out as border ratios, a card whose only flaw is centering grades:
Front border ratio Deviation Penalty Points Grade 55/45 5 50 950 10 60/40 10 100 900 9 65/35 15 175 825 8 70/30 20 275 725 7 80/20 30 395 605 6 85/15 35 495 505 5 90/10 40 695 305 3 Each knee sits where a round border ratio lands on a whole grade: 60/40 at 9, 65/35 at 8, 70/30 at 7, 80/20 at 6, 85/15 at 5. No round ratio falls between 85/15 and 90/10, which is why the table steps from 5 straight to 3.
The back is judged far more gently, because the reverse border is not what a collector is looking at:
Back border ratio Deviation Penalty Points Grade 75/25 25 50 950 10 90/10 40 80 920 9 Past the last rows on either table — front 90/10, back 90/10 — the curves escalate steeply (40 and 10 pts/pt): centering that bad is the card's defining flaw, and the grade should say so.
Worked GEM cutoffs: front 45/55 → deviation 5.0 → penalty
50→ 950 points → still 10; one tenth worse (44.9/55.1) → deviation 5.1 →51→ 949 → drops to 9. Back 75/25 → deviation 25.0 →50→ 950 → still 10; the gentle back slope (2/pt) plus half-up rounding holds GEM through 75.2/24.8 (deviation 25.2 → 50.4 → rounds to50→ 950) and breaks it at 75.3/24.7 (deviation 25.3 → 50.6 →51→ 949 → 9).Knee-crossing example (also the single-round rule): front deviation 10.1 →
10 × 10 + 0.1 × 15 = 101.5→ rounded once, half-up →102→ 898 points → grade 8.5. Rounding an exact accumulated.5up: front deviation 10.3 →100 + 0.3 × 15 = 104.5→105→ 895 → 8.5 (half-to-even would have given 104).
Corner & edge defects#
Every corner (tl/tr/bl/br) and edge (top/right/bottom/left)
defect is classified into one of four severity classes:
- de minimis — faint whitening only visible up close or under angled light. The GEM budget is face-dependent, because front penalties carry the ×1.3 multiplier: a GEM card may keep three de-minimis in a back region (the fourth breaks GEM), but only two in a front region (the third breaks GEM). This matches the empirical finding that top-grade cards routinely carry a few flagged-but-forgiven dings, while holding the front to the stricter standard the multiplier implies.
- minor — visible at arm's length but small: visible whitening or a small nick, a soft corner touch.
- moderate — obvious at a glance: the corner point is flattened or rounded but the card layers are intact.
- heavy — structural: layers visibly separated, frayed, or the corner is fully blunted/bent.
The GEM boundary is a deliberate construction: only de-minimis flaws can keep a 10. The mildest minor defect in the rubric — a minor edge on the back, penalty 51 — lands at 949, one point past the cliff. A defect visible at arm's length does not keep a 10.
Each distinct physical flaw is one defect object, and multiple defect
objects MAY target the same corner or edge — a corner with both whitening and
a nick is two defects. All corner penalties on a face sum into that face's
single corners region sub-score (and likewise all edge penalties into
edges): the bottleneck is per-REGION, not per-corner. The corner/edge label
is locational metadata and never changes the score. Pooling per region is
deliberate — a region is scored collectively; true per-corner sub-scores are a
future rubric refinement.
Reference penalties (back-face values; front is ×1.3), deducted from that region:
| Severity | Corner | Edge |
|---|---|---|
| de minimis | 15 | 14 |
| minor | 55 | 51 |
| moderate | 175 | 150 |
| heavy | 325 | 305 |
Front multiplier: ×1.3. Every defect penalty on the front face — corner, edge, or surface — is multiplied by 1.3 and rounded half-up after multiplication (centering is exempt; it already has its own front/back slope). Applied to the tables above: front corner penalties are 20 / 72 / 228 / 423; front edge penalties are 18 / 66 / 195 / 397.
Surface defects#
Surface defects require a depth, a size, and normalized x,y
coordinates (0–1 from the top-left of the face) — location is required here
because, unlike a corner or edge, "surface" alone doesn't say where on the card.
- Depth —
surface: does not break the gloss (a scuff, a print line).scratch: a visible line you can catch a fingernail on.deep: a crease or break in the paper. - Size —
dot: about 2mm or less.lt_1cm: under 1cm.lt_5cm: under 5cm.full_card: spans most of the card.
Reference penalty matrix (back-face values; front is ×1.3):
| Depth \ Size | dot | lt_1cm | lt_5cm | full_card |
|---|---|---|---|---|
| surface | 5 | 25 | 80 | 180 |
| scratch | 15 | 55 | 160 | 330 |
| deep | 35 | 110 | 280 | 510 |
The same ×1.3 front multiplier (rounded half-up) applies on the front face.
Line endpoints#
Surface, crease, and edge defects — the three linear regions — MAY carry an
optional second point, x2,y2, normalized 0–1 exactly like x,y, turning
the placed point into a line segment (e.g. a UI line-drag that measures a
scratch and derives its size class from the physical length). Line endpoints
are a presentational/derivation aid only and are never scored: the
reference scorer validates them when present (each finite and 0–1, and only
ever as a pair — a lone x2 is rejected with "x2 and y2 must be provided together") and the arithmetic then ignores them entirely, so a defect with
endpoints scores identically to the same defect without them
(test/vectors.json pins this). Point-only regions (corner, stain,
print_defect) don't declare them — an x2 there is ignored as an unknown
annotation field, like any other. Endpoints are a purely additive input-schema
affordance: they change no penalty, and the severity/size classes remain the
scored input.
Print quality#
Print quality — the criteria that decide vintage 10s and 9s (focus/registration, gloss, print imperfections, border whiteness, creases, staining) — takes two shapes, both mid-band fit:
- Attribute ladders —
focus,gloss,borderare face-global judgments (not located anywhere in particular), assessed independently per face as an optionalprint: { focus?, gloss?, border? }object on that face. Config-gated: a rubric withoutprint_attributesrejects anyprintblock outright ("print requires a rubric with print_attributes"); an absent object, or an absent attribute within it, means perfect — no penalty. Ladders are short. - Placeable defect types —
crease,stain, andprint_defectsit alongsidecorner/edge/surfaceasregionvalues on a defect. Unlike corner/edge (wherex,yare optional annotation), all three requirex,y— likesurface, location matters for a placed flaw. Each carries a severity drawn from that region's own vocabulary; crease, stain, and print_defect each have a disjoint severity vocabulary from one another and from the shared corner/edge vocabulary — acreasedefect never accepts astainorprint_defectseverity word (or vice versa), even where a word is spelled the same (heavyis a valid word in all four vocabularies — shared corner/edge, crease, stain, print_defect — independently defined at a different penalty in each).
Mid-band fitting#
Every print-quality number below is fit by choosing the penalty that lands the card
at the middle of the target grade's 50-point band, not its edge —
penalty = 1000 − (100g + 25) for target grade g. A card whose only flaw is that
one attribute or defect lands exactly grade g, with margin against the band edge
on both sides.
Attribute ladders (per face, region print)#
| Attribute | Options → grade cap | Penalty |
|---|---|---|
focus |
sharp→10 · slightly_out→7 · noticeably_out→5 · severely_out→2 | 0 / 275 / 475 / 775 |
gloss |
full→10 · most_retained→7 · some_loss→6 · much_lost→3 · absent→2 | 0 / 275 / 375 / 675 / 775 |
border |
clean→10 · slightly_off_white→9 · yellowed→3 · browned→1.5 | 0 / 75 / 675 / 825 |
slightly_off_white caps at 9, not 10: a touch of off-whiteness is a real flaw but
not a disqualifying one, so border is the only ladder whose first non-baseline rung
doesn't already cost a full grade band. Attribute penalties are multiplier-exempt:
front_defect_multiplier (×1.3 on corner/edge/surface) does not apply here — each
rung is already an absolute grade cap, not a generic front/back ratio.
Placeable types#
Crease (pools into the surface region bucket; binding_region: "surface" no
longer implies scuff/scratch/deep-type wear specifically — it may be a crease):
| Severity | light | full_card | heavy | through_layers |
|---|---|---|---|---|
| Penalty | 575 | 675 | 825 | 875 |
| Grade cap | 4 | 3 | 1.5 | 1 |
Stain (pools into surface; penalty is per-face — a back stain is forgiven far
more than a front one, an asymmetry the generic ×1.3 front multiplier cannot express, so
stain_penalties carries explicit {front, back} columns per severity instead):
| Severity | very_slight | slight | obvious | heavy |
|---|---|---|---|---|
| Back penalty (cap) | 75 (9) | 275 (7) | 475 (5) | 775 (2) |
| Front penalty (cap) | 675 (3) | 675 (3) | 775 (2) | 875 (1) |
Print defect (a placed line/dot/snow-type printing flaw; pools into the print
region — the same bucket the attribute ladders bottom, so a placed print defect and,
say, a gloss rung compete for which one caps the face):
| Severity | slight | minor | blemish | heavy |
|---|---|---|---|---|
| Penalty | 25 | 75 | 275 | 675 |
| Grade cap | 10 | 9 | 7 | 3 |
slight's penalty (25 → 975 points → raw 9.5 → promoted to 10) is deliberately
GEM-compatible: one slight printing imperfection does not, on its own, cost a card
its top grade.
Gating#
print(the attribute-ladder input) requires a rubric withprint_attributes; absent,score()fails loudly.creaserequirescrease_penalties;stainrequiresstain_penalties— neither requiresprint_attributes— a rubric can support creases and stains without shipping the attribute ladders at all.print_defectis double-gated: it requires bothprint_defect_penalties(to look up the penalty) andprint_attributes(because it pools into theprintregion, which only exists on a rubric that defines that table — without the second gate, a custom rubric withprint_defect_penaltiesbut noprint_attributeswould silently produce a broken region score).- A rubric with none of these tables stays strictly four-region: the
printregion appears infaces.<face>.regionsonly whenprint_attributesis present.
Notes#
line_itemsorder is presentation order, not normative — only each line item'sregionandpenaltyare meaningful for scoring; the order items are pushed in (and therefore the order a UI would render them) is an implementation artifact of iteration order, not part of the spec.- A placed print defect's
line_itemsdetail reads"print {severity}"(e.g."print slight"), matching the"{depth} {size}"/"{corner} {severity}"pattern used by the other defect types. - Validation-order asymmetry (observable, non-normative): a
surfacedefect checksdepth/sizebeforex/y, so a defect missing both size and coordinates reports the size error first; the three placeable types checkx/ybeforeseverity, so acreasedefect missing both coordinates and a valid severity reports the coordinate error first instead. Which error surfaces first when multiple fields are invalid at once is not part of the spec — only that the input is rejected, and the accepted score is unaffected either way.
Worked example#
A moderate corner defect on the back: penalty 175 (from the corner table
above). 1000 − 175 = 825. That region is now the side's minimum, so
side_points = 825. With nothing else on the card, points = 825, which
falls in the 800–849 step: grade = 8, band = LP.
The same defect on the front instead: the penalty is multiplied by 1.3
first — round(175 × 1.3) = round(227.5) = 228 — then subtracted:
1000 − 228 = 772. That falls in the 750–799 step: grade = 7.5, still
band = LP. Same physical defect, worse grade, purely because it's on the
face judged more harshly.
Grade ladder#
points maps to grade in 50-point steps (raw = floor(points/50) × 0.5,
with the 9.5 slot promoted to 10), and grade maps to a condition band by
taking the highest min_grade the grade still reaches:
| Points | Grade | Condition band |
|---|---|---|
| 950–1000 | 10 | NM |
| 900–949 | 9 | NM |
| 850–899 | 8.5 | LP |
| 800–849 | 8 | LP |
| 750–799 | 7.5 | LP |
| 700–749 | 7 | LP |
| 650–699 | 6.5 | MP |
| 600–649 | 6 | MP |
| 550–599 | 5.5 | MP |
| 500–549 | 5 | MP |
| 450–499 | 4.5 | HP |
| 400–449 | 4 | HP |
| 350–399 | 3.5 | HP |
| 300–349 | 3 | HP |
| 250–299 | 2.5 | DMG |
| 200–249 | 2 | DMG |
| 150–199 | 1.5 | DMG |
| 0–149 | 1 | DMG |
Bands, by minimum grade: NM ≥9 · LP ≥7 · MP ≥5 · HP ≥3 ·
DMG ≥1. Band selection is order-insensitive in the rubric data — the
scorer picks whichever band has the highest min_grade the grade still
clears.
Versioning#
This standard is Touchstone 0.1. Versions are two-part, major.minor;
there is no patch digit. One number everywhere — this prose, the rubric data's
rubric_version, and the $id path the schemas are served from all carry it.
That is the version to cite when you claim conformance.
Every change to any number in this document or in rubric.json — a
curve segment, a penalty, a band boundary — is a new version, never a
silent edit in place. This prose is normative, so a number cannot move in the
data without moving here too. Every score result names the rubric_id and
rubric_version that produced it, so a grade is always reproducible against
the exact rules in force when it was computed. Per-set rubrics (e.g. a
vintage-basketball override) are not special-cased code — they are new
instances of rubric-config.schema.json, resolved by the framework's
inheritance (default → game → era/class → set).
Conformance with Touchstone 0.1 means passing test/vectors.json. This
prose is normative. The reference implementation and the golden vectors are
conformance evidence — the executable demonstration that an implementation
agrees with this document. Where they and this prose disagree, one of them
contains a bug: file it, decide which is wrong, and fix that one. Neither the
implementation's internal ordering, its floating-point accumulation, nor the
English text of its error messages is part of the standard.
One known limitation is stated here rather than buried: the aggregation is a deliberate first-order approximation — a pure minimum, with no gap-credit or count-rule term. Whether it stays that way is a data-gated decision for a future revision.