# Building a puzzle that never rolls a die: in SilkWard every move produces the same outcome

> A deterministic puzzle on a node-and-edge board, offline-first; one TypeScript core feeds 36+6 levels to both web and mobile.

- What it does today: Lets the player solve 36 levels + 6 hidden levels on a node-and-edge board through fully deterministic moves, with no luck or reflexes involved; a single TypeScript core drives both the web (PWA) and mobile (iOS/Android) client at once. Playable in the browser at silkward.com.
- Form: Web, Mobile
- State: Halted
- Started: 2026-09-09
- Left Labs: 2026-09-23
- Technologies: TypeScript, Next.js, React, Expo, React Native, Vitest
- Tags: #games, #puzzle, #determinism, #offline
- Website: https://silkward.com
- Source: https://muhammetsafak.com/labs/silkward-lab/
- Language: en-US
- Author: Muhammet Şafak

---
Turn-based puzzles filled a gap Hitman GO left for me: few rules, zero luck,
the feeling of building an outcome by thinking it through. I built SilkWard
into that gap — a puzzle that moves across a node-and-edge graph, told mostly
without words.

## No luck, no reflexes, no surprise rule

These three principles sum up the game's design philosophy. Every move you
make on the node-and-edge graph also triggers the enemies' next step, and
that step follows the same deterministic rule. Lose a level and the reason
is never a bad roll — it is the move itself. 36 levels + 6 hidden levels span
three acts.

## One core, two platforms

The rules engine was written with zero dependencies: it knows neither the DOM
nor React Native, only plain TypeScript. That purity lets the web client
(a Next.js-based PWA) and the mobile client (Expo) share one behavior instead
of two — the rules engine and the solver live in one shared core package,
level data lives in a separate data package, and interface text lives in its
own language package; only the interface layer is platform-specific.

## How a level gets calibrated

A new level is not done once a single file is written: it is designed with
an editor tool first, then exported into the data package as a level
definition. The level's minimum move count is never guessed by hand — it is
the number a BFS-based solver tool finds, and level design is then
calibrated against that number. A validation command re-checks that this
number is still correct on every change, so the "par" value never quietly
drifts out of date.

The rules engine itself is held to the same discipline: the core package
bans access to a random number generator or the system clock — determinism
is enforced by a build-chain constraint, not a code comment. An optional AI
art pipeline also sits outside the core code, confined to a separate
development-tools directory; it was tried, but its output never passed
review, so it never shipped — the art in the playable build is fully
procedural, and the game's runtime carries no AI client at all.

## A game that almost never touches the network

The first load caches SilkWard's shell (interface, code, art); from then on
play makes no network request. The one exception is sound: the 26 audio
files are not preloaded — each one is downloaded once the first time it
plays and cached permanently, so the airplane-mode guarantee only holds from
the second session onward; with no network on the very first session, the
game just plays silently instead of crashing. That is not an "offline mode"
bolted on afterward; it was a constraint set from the start: `fetch` is
banned by lint rules in the core and app layers, and `XMLHttpRequest` use is
caught by a separate source-scanning test — the constraint lives in the code
rather than being an honor-system promise.

The record sits here as halted: the game is playable today at
silkward.com, but no further development is happening on it.
