How Netherguard Cases Are Tested for Fair Deduction

NETHERGUARD DEVELOPMENT NOTES

Written and tested by Diligesker Lab · Reviewed July 30, 2026 · Report a correction

A logic case can have polished art and readable clues and still be unfair. It may allow two final answers, contain a statement that is false in its intended solution, or reach a point where no suspect can be proved without guessing. Netherguard treats those as different defects and checks them at different layers. This is a first-party account of the current case-validation path used in the project.

Netherguard board showing suspects, traits, and revealed clues
The same board state used by a player is reconstructed by offline validation and browser tests.

Layer 1: reject malformed case data

Each investigation is authored as structured round data: a board layout, an initial reveal, and one clue per speaker. Before logic is considered, the linter checks the file against a JSON schema and loads the project’s character, hero, and trait catalogs. The deeper validator then rejects duplicate slot IDs or coordinates, unknown character references, an initial reveal outside the layout, missing speakers, unknown mention targets, unsupported clue types, and count values that exceed the group they describe.

This layer catches a surprisingly important class of errors. A clue about “row 4” can be logically reasonable in isolation but invalid on a three-row board. A trait count can sound natural while referring to a trait no displayed character actually has. Those are data-contract failures, not puzzle difficulty.

Layer 2: enumerate the possible worlds

The solver represents every suspect as one bit: infiltrator or resident. For a board with n suspects, it evaluates the possible assignments from 0 through 2n−1, discards assignments that conflict with already revealed cards, and then applies every active clue. From the surviving assignments it records whether each unresolved card can still be an infiltrator, a resident, or both.

A card is “forced” only when all surviving assignments give it the same status. If both statuses remain possible, the game has not proved that card yet. The release validator also solves with the complete clue set and requires exactly one final assignment. It separately checks that every clue is true in that solved assignment. Unique answer and truthful clues are therefore two explicit checks rather than one assumption.

Layer 3: prove the case can unfold without guessing

A unique final answer is not enough. Imagine a case where all clues together identify one solution, but the player cannot reveal the later clues until confirming a card that the early clues do not force. The ending is unique, yet the play sequence still demands a guess.

Netherguard’s progressive check starts from the authored initial reveal, activates only clues whose speakers have been confirmed, and repeatedly searches for at least one forced unresolved card. It fails if the active clues become contradictory or if unresolved cards remain with no forced move. It also explores the available forced branches so that choosing one valid forced card before another does not lead into a dead end.

Layer 4: solvable does not automatically mean varied

The project applies profile-specific quality gates for tutorial, standard, hard, and nightmare cases. These gates look beyond bare solvability. They can limit duplicate logic signatures and excessive dependence on row counts, require broader trait use, and, for harder profiles, require minimum shares of column, relation, advanced, and flexible-count clues. Hard and nightmare profiles also require both same-status and different-status relationships plus column and trait families.

This is a pacing and variety safeguard, not a mathematical proof that every player will find a case fun. Difficulty remains partly subjective. The gate’s job is narrower: stop a nominally hard case from passing when it is mostly repeated versions of the same simple statement.

Layer 5: replay the deduction path in a browser

The end-to-end test loads the round manifest, opens each case in a real browser session, reads the runtime state, and confirms one currently forced suspect at a time. It expects no evidence-error or failure dialog after a forced choice. The case must end with every slot resolved, and the forced-candidate sequence observed in the browser must match the offline analysis telemetry.

That comparison matters because correct source data is not enough if the shipped interface activates clues differently. The browser path checks the player-visible handoff from round data to active clue set, forced status, confirmation, and completion.

Current verification snapshot

On July 30, 2026, I ran the repository’s round linter against the current campaign and received a clean result for 50 files with zero reported issues. I also ran the campaign contract check; it reported that campaign metadata, difficulty ramp, stored balance report, per-round profile contracts, and generated star targets were aligned. These are version-specific results, so they should be rerun whenever a case or clue engine changes.

What to send with a suspected contradiction

Automated checks do not make player reports unnecessary. If a case appears stuck, include the case title or number, interface language, a board screenshot, which cards were already confirmed, and the exact clue pair that appears incompatible. Browser and device details help when the problem is presentation rather than logic. Use the contact page for a report and check Updates for later case revisions.

For help solving rather than reporting a defect, start with the clue-reading guide. The Reedshadow Passage walkthrough demonstrates how direct, row, trait, and neighbor constraints agree on one board.