# HEXWORK — *A Lantern in the Deep* A 3D first-person NetHack-descendant in the terminal. Witcher-style low fantasy on the surface, retro-futurist machine-ruin underneath, rendered as truecolor 3D ASCII at 60fps on the CPU. No GPU required. Runs over SSH. Full design bible: [`docs/`](docs/README.md) (vision, player experience, lore, systems, architecture, renderer spec, implementation checklist). ## Build ```sh cargo build --locked --release # binary: target/release/hexwork ./target/release/hexwork --help ./target/release/hexwork --seed 140 ``` Requires Rust 1.75.0 or newer. The normal developer toolchain tracks stable; select the minimum explicitly with `cargo +1.75.0 ...`. Use the committed lockfile (`--locked`) and build from the complete checkout, including `vendor/`. HEXWORK maintains a metadata-only Rust 1.75 backport of the exact bincode 2.0.1 sources; upstream declares a newer minimum. See [vendor/README.md](vendor/README.md) for source/license verification, maintenance obligations and feature/platform limits. Do not bypass Cargo's Rust-version checks. The verification scripts require Python 3.11+; the CI-workflow verifier also requires PyYAML 6.0.2. These are development checks, not game-runtime dependencies. Configured CI checks stable and 1.75 separately; configured platforms are not a claim of an unexecuted platform certification. No network calls at runtime, ever. ## Fluid player correction status The approved Fluid HEXWORK correction contract is documented in [`docs/fluid-player-design.md`](docs/fluid-player-design.md). It freezes one signed-`i64` millimetre actor authority, checked `i128` projection into a bounded `i32` voxel margin, the `10 Hz`/three-substep simulation, player geometry and integer heading, action durations and pacing/death separation, pending-effect ordering, resumable Inn work, and bounded lit observations. This is an **approved target, not implemented behavior**. The current executable remains the legacy grid executable until implementation advances; `cargo build --locked --release` does not deliver Fluid. The future v5 schema break is not emitted and no migration is authorized. The optional local SDL2 `0.38`/fontdue `0.9.3` path is intended but not delivered; ordinary terminal SSH remains the intended client route. For a safe baseline build, use a disposable detached worktree for the annotated local `legacy-grid-final` commit. This does not modify the current checkout, push anything, or migrate saves: ```sh git worktree add --detach ../hexwork-fluid-player-legacy-grid-final \\ 3f81e656bead175606b31a87cb15c9068f0d1c2b cd ../hexwork-fluid-player-legacy-grid-final git rev-parse HEAD # must print the exact commit above cargo build --locked --release ./target/release/hexwork --help ``` Return to the original checkout and remove only that disposable worktree when finished (`cd -` then `git worktree remove ../hexwork-fluid-player-legacy-grid-final`); do not run `git push`, merge, rebase, or a save/schema migration as part of this check. The previous first-person acceptance failure remains open: the first-session observation reported that the advertised Contracts key terminated play. The normal-brightness first-person acceptance route below is still **pending human execution**; automated tests and SVG/terminal artifacts do not certify font or monitor readability or full HEXWORK completion. ## Terminal support matrix | Capability | Full | Degraded | Floor | |---|---|---|---| | Color | 24-bit truecolor | 256-color LUT | no-color density + bold (fully playable) | | Unicode | box-drawing + half-blocks | ASCII-only (`#\|/-+`) | same, asserted playable | | Size | 120×36+ recommended | 80×24 minimum | below 80×24: kind refusal + resize hint | | Input | keys + mouse-drag orbit | keys only | keys only, full game completable | | Latency | local 60fps | SSH adaptive (30fps floor) | turn-step mode (`Shift` advances one tick) | ## `HEXWORK_*` environment overrides | Variable | Effect | |---|---| | `HEXWORK_FORCE_TC=1` | Force truecolor path even if the probe disagrees | | `HEXWORK_FORCE_256=1` | Force 256-color LUT path | | `HEXWORK_NO_COLOR=1` | Force no-color density + bold path | | `HEXWORK_FORCE_ASCII=1` | Force ASCII-only glyphs (no box-drawing / half-blocks) | | `HEXWORK_SCALE=0.5` | Fixed render scale (disables adaptive scaling) | Every automatic degradation is logged once to the message log. Set an override to silence the corresponding notice. ## Controls The current slice's approved recovery controls (validation tracked in [the interface audit](docs/player-interface-audit.md)): | Context | Default keys | Effect | |---|---|---| | Perspective | Arrows / WASD / hjkl | Forward/back and strafe relative to horizontal camera heading | | Perspective | `Tab` | Toggle first-person/chase; each preserves its own angles | | Play | `x` | Spatial look; movement keys rotate 15° or tilt 10°, without a turn | | Spatial look | `0`; `x` / Esc | Reset tilt and snap heading to nearest octant; leave look | | Perspective | `<` / `>` | Tilt down/up; chase changes orbit elevation | | Play/map | `M` | Toggle north-up tactical and restore saved perspective | | Tactical | PageUp / PageDown; Home | Inspect Z slice; return to player floor | | Tactical on player floor | Movement; Space / `.` | World-direction movement; wait through ordinary action semantics | | Inventory | `i`; Enter / `x` | Open/close carried inventory; retain item detail/Wield/Tinker workflow | | Play | `g`; Enter | Pick up own-cell loot; contextual interaction | | Normal play | Esc / `q` | Save and quit; look/modal/map is dismissed first | Story tactical pauses between accepted actions; Classic remains live. Movement on an inspected nonplayer floor is refused without a turn. New/load presentation faces down Milthollow's road (world −Y); old headless/replay direction commands remain world-relative. Six new camera actions are optional in legacy keymaps: missing actions stay unbound, and loaded bindings drive in-game hints. With no Home binding, close/reopen M to recenter; look-mode movement provides tilt. Keyboard is sufficient. `HEXWORK_MOUSE=1` opts into terminal mouse capture: left-drag rotates/tilts perspective by 2° per column/row; capture is disabled on exit. Capture command success is not terminal capability acknowledgement. Live resize suspends gameplay below 80×24 and restores the same view on recovery. ### Normal-brightness acceptance route (human result pending) Use the newly rebuilt, identified release binary—not an older installed copy. At normal terminal brightness, face the road, look left/right/up, exit look, identify an available service/exit/item by glyph, approach it, toggle chase and back, enter tactical, move on the player floor, inspect another floor and Home back, then return to perspective. Record binary identity, terminal dimensions, color mode, screenshots and explicit pass/fail. This is AC10; automated tests or SVG exports do not certify font/monitor readability or the full HEXWORK game. Opt-in `HEXWORK_INTERFACE_METADATA=/new/path.jsonl` writes presentation-only frame diagnostics outside save state (the path must not exist), including run, seed, executable FNV identity, effective preferences, camera, player and canonical hash/history counters. Keep this private unless intentionally sharing it. `HEXWORK_PTY_ARTIFACT_DIR` enables sanitized test JSON/SVG exports; SVG uses a recorded monospace cell aspect and is only a diagnostic aid. ### Paying the Tinker for identification At Milthollow's Tinker interaction point, press the displayed Confirm key (default `Enter`) to browse carried items. Select an unidentified item, inspect it, then press Confirm to open a separate **15-copper** confirmation. A fresh Confirm pays; `Esc` cancels without charging. The browser shows your wallet, exact item ID and Known/Unidentified status, using your loaded key bindings. Full payment identifies that item template for the current run without advancing a simulation tick. Knowledge and payment survive save/load/replay; Story death still restores the existing checkpoint, so a later purchase may be rolled back. Underpayment, trading, mending and generic item-use effects are not implemented. Existing v4 saves remain readable; older binaries reject the new identification action tag rather than silently ignoring it. ### Wielding a weapon Open Inventory with `i`, select a carried weapon, and press `Enter` to inspect it. In detail, `Enter` opens a separate Wield confirmation; a fresh `Enter` confirms, and `Esc` cancels. The UI uses your loaded bindings when remapped and shows the current `[wielded]` item. Wielding records one zero-tick action without consuming or moving the item. The starter Ratcatcher pike now reaches **two cells total** in a clear cardinal lane on the same floor. Walls, blocked headroom and intervening living actors block that extended strike. Ordinary adjacent attacks keep their existing rules; a surviving enemy only returns a strike within its own one-cell reach. Damage remains 3. This does not yet implement shaft/grate/vertical reach, armor effects, consumables or the complete combat system. New readers retain existing v4 saves/replays. Older binaries reject v4 records containing the new Wield action tag; forward-reader compatibility is not promised.