Things That Have Spots: A TypeScript Validator for Category-Puzzle Explanations
The answer to Pinpoint #835 on August 13, 2026 is “Things that have spots.” Its clues are The Sun, Dominoes, Leopards, Dalmatians, and Ladybirds (ladybugs). This article uses that sequence to build a small TypeScript validator for puzzle explanations.
The goal is not to automate intuition. It is to catch weak explanations: missing clues, duplicated evidence, empty claims, or a category that quietly relies on exceptions.
Define evidence instead of storing only an answer
A bare string such as "Things that have spots" cannot explain why it is correct. Store one evidence record per clue.
const clues = [
"The Sun",
"Dominoes",
"Leopards",
"Dalmatians",
"Ladybirds",
] as const;
type Clue = (typeof clues)[number];
type Evidence = Readonly<{
clue: Clue;
observation: string;
bridge: string;
}>;
type Explanation = Readonly<{
category: string;
evidence: readonly Evidence[];
}>;
observation records what can be checked about the clue. bridge states why that observation supports the proposed category. Keeping those fields separate makes circular reasoning easier to notice.
Encode the Pinpoint #835 explanation
const pinpoint835: Explanation = {
category: "Things that have spots",
evidence: [
{
clue: "The Sun",
observation: "The Sun has sunspots.",
bridge: "A sunspot is a spot-like visible region.",
},
{
clue: "Dominoes",
observation: "Domino faces use pips as countable marks.",
bridge: "The pips are visually recognizable spots.",
},
{
clue: "Leopards",
observation: "Leopard coats have spotted markings.",
bridge: "The markings directly match the category.",
},
{
clue: "Dalmatians",
observation: "Dalmatians are known for spotted coats.",
bridge: "The coat pattern directly matches the category.",
},
{
clue: "Ladybirds",
observation: "Many familiar ladybirds have spots on their wing cases.",
bridge: "Those visible markings match the category.",
},
],
};
My aha moment was Dominoes. The first clue encouraged topic-based guesses such as astronomy or light. Dominoes broke that model: their domain is unrelated to the Sun, but their pips preserve a visual attribute. The clue order therefore teaches us to validate an attribute, not force all nouns into one subject.
Return structured validation issues
Throwing on the first problem makes an author fix one issue at a time. A validator is more useful when it returns every detected issue.
type ValidationIssue = Readonly<{
code:
| "EMPTY_CATEGORY"
| "MISSING_CLUE"
| "DUPLICATE_CLUE"
| "EMPTY_OBSERVATION"
| "EMPTY_BRIDGE";
message: string;
}>;
function validateExplanation(
expectedClues: readonly Clue[],
explanation: Explanation,
): ValidationIssue[] {
const issues: ValidationIssue[] = [];
if (explanation.category.trim().length === 0) {
issues.push({
code: "EMPTY_CATEGORY",
message: "The category must be stated explicitly.",
});
}
const counts = new Map<Clue, number>();
for (const item of explanation.evidence) {
counts.set(item.clue, (counts.get(item.clue) ?? 0) + 1);
if (item.observation.trim().length === 0) {
issues.push({
code: "EMPTY_OBSERVATION",
message: `${item.clue} needs a checkable observation.`,
});
}
if (item.bridge.trim().length === 0) {
issues.push({
code: "EMPTY_BRIDGE",
message: `${item.clue} needs a link to the category.`,
});
}
}
for (const clue of expectedClues) {
const count = counts.get(clue) ?? 0;
if (count === 0) {
issues.push({
code: "MISSING_CLUE",
message: `${clue} is not explained.`,
});
}
if (count > 1) {
issues.push({
code: "DUPLICATE_CLUE",
message: `${clue} appears ${count} times.`,
});
}
}
return issues;
}
Run it against the complete explanation:
console.log(validateExplanation(clues, pinpoint835));
// []
An empty issue array means the explanation is structurally complete. It does not prove that every observation is true; factual review remains a separate human responsibility.
Add a failing test that represents a tempting shortcut
“Spotted animals” fits the final three clues but fails the first two. A structured validator exposes that incompleteness if an author omits inconvenient evidence.
import { strict as assert } from "node:assert";
const incomplete: Explanation = {
category: "Spotted animals",
evidence: pinpoint835.evidence.slice(2),
};
const issues = validateExplanation(clues, incomplete);
assert.deepEqual(
issues.filter((issue) => issue.code === "MISSING_CLUE").map((issue) => issue.message),
["The Sun is not explained.", "Dominoes is not explained."],
);
This test captures a useful rule: a category is not strong because it explains the easiest clues. It is strong when the same interpretation covers the entire sequence.
Keep semantic truth outside the type system
TypeScript can guarantee that every clue value belongs to the known tuple. It can ensure required fields exist. It cannot determine whether “pips are visually recognizable spots” is a fair ordinary-language bridge.
That boundary suggests a two-stage review:
- Run structural validation for completeness and duplicates.
- Review every observation for accuracy, ordinary wording, and consistency with the category.
Avoid inventing a numeric “confidence” score for five hand-written records. A clean list of evidence and counterexamples is easier to audit than a percentage with no statistical basis.
Why the clue order still matters
The validator treats all clues equally, but the solving experience does not.
- The Sun opens several plausible topics.
- Dominoes create the cross-domain pivot.
- Leopards and Dalmatians confirm the visual interpretation.
- Ladybirds/ladybugs provide a final familiar example.
The code checks the finished explanation. The clue order explains how a solver can reach it.
That distinction is valuable in other systems too: validation checks whether a final state is complete, while a trace explains the path that produced it.
For the original daily puzzle context, see Pinpoint Answer Today.
Author and disclosure: LION ZHANL maintains Pinpoint Answer Today and wrote this independent engineering analysis. It is not affiliated with or endorsed by LinkedIn or Microsoft. The draft was prepared with AI assistance, then reviewed and edited by LION ZHANL for originality, factual accuracy, and TypeScript consistency.