Scriber.Lattice (Scriber v0.1.2)

Copy Markdown View Source

The on-disk half of the game: one directory of real files per stratum.

A gate opens for one access code, and that code is written into the stratum's files rather than carried as an item. Scriber.Console is what reads them. Solving a stratum is: walk into the gate to learn its node state, grep the records at the console for that state, then unseal with the key on the line that matches.

What a stratum looks like on disk

<home>/runs/<seed>/stratum-2/
  README              what this stratum is, and which commands exist
  manifest            node records, one of which carries the gate's key
  logs/               from stratum 3 onwards, the manifest sharded one record per file
  gate.log            the gate controller's log
  .seal               the SHA-256 of the access code, never the code itself
  unsealed            created by `unseal/2` when a code is accepted

Exactly one record carries state=<gate_state/2> and that record's key= is the access code from code/2. The other records are decoys wearing other states and other keys. From depth 3 onwards the records move into logs/, one per file, and manifest says so.

Neither .seal nor gate.log contains the code or the gate's state, so reading every file in the directory is not enough to solve the stratum on its own.

Where it lives

home/0 resolves the root directory: $SCRIBER_HOME, else $XDG_STATE_HOME/scriber, else ~/.local/state/scriber. Nothing is ever deleted on the game's own initiative — runs accumulate one directory per seed until destroy/1 removes one.

Example

dir = Scriber.Lattice.prepare(1234, 1)
Scriber.Lattice.unsealed?(1234, 1)
#=> false

Summary

Functions

The access code that opens a stratum's gate: eight lowercase hex characters.

Delete a run's whole directory, every stratum and marker in it. Raises on failure.

The node state naming which record carries the gate's key.

The directory every run is kept under.

Write the stratum's files, creating the directory if needed, and return its path.

The directory holding a whole run, named for its seed.

The seeds of every run still on disk, most recently modified first.

The directory for one stratum of a run.

Record on disk that this stratum's gate has been opened.

Whether this stratum's gate has been opened from the console.

Functions

code(seed, depth)

@spec code(integer(), pos_integer()) :: String.t()

The access code that opens a stratum's gate: eight lowercase hex characters.

A pure function of seed and depth. The game never shows this to the player; it is written into exactly one record by prepare/2 and has to be found there.

destroy(seed)

@spec destroy(integer()) :: :ok

Delete a run's whole directory, every stratum and marker in it. Raises on failure.

gate_state(seed, depth)

@spec gate_state(integer(), pos_integer()) :: String.t()

The node state naming which record carries the gate's key.

A pure function of seed and depth, drawn from a fixed vocabulary of state words. No decoy record ever wears this state, so exactly one record in the stratum matches it. The player learns it by walking into the gate, not from any file.

home()

@spec home() :: Path.t()

The directory every run is kept under.

Resolved in order: $SCRIBER_HOME, then $XDG_STATE_HOME/scriber, then ~/.local/state/scriber. An environment variable that is empty, unset, or holds a relative path counts as absent, as the XDG base directory specification requires. When the system reports no home directory, the temporary directory stands in for ~.

prepare(seed, depth)

@spec prepare(integer(), pos_integer()) :: Path.t()

Write the stratum's files, creating the directory if needed, and return its path.

Writes .seal, README, the records (manifest, or logs/shard-N.log from depth 3), and gate.log, overwriting any that exist. Idempotent: everything written is derived from seed and depth, so re-preparing a stratum reproduces it byte for byte.

Files this function does not write are left alone, including the unsealed marker, so re-preparing an opened stratum does not seal it again.

root(seed)

@spec root(integer()) :: Path.t()

The directory holding a whole run, named for its seed.

runs()

@spec runs() :: [integer()]

The seeds of every run still on disk, most recently modified first.

Directory names that are not whole integers are ignored, as is a missing runs directory, which yields [].

stratum_dir(seed, depth)

@spec stratum_dir(integer(), pos_integer()) :: Path.t()

The directory for one stratum of a run.

unseal(seed, depth)

@spec unseal(integer(), pos_integer()) :: :ok

Record on disk that this stratum's gate has been opened.

Writes the marker unsealed/2 looks for. The marker survives prepare/2, so a stratum stays open once opened. Always returns :ok, including when the write fails because the stratum directory does not exist.

unsealed?(seed, depth)

@spec unsealed?(integer(), pos_integer()) :: boolean()

Whether this stratum's gate has been opened from the console.