A plug-and-play solution for running GURPS combat, with tracking, VTT, dicerolling, and more
Rust 77%
22%
Shell <1%

README.md

Fantactical #

Fantactical is a pre-alpha desktop combat tracker and hex-grid virtual tabletop for GURPS 4e. It is a single Rust application built with Bevy and bevy_egui, with an optional authoritative WebSocket server/client mode.

The application starts with an empty, valid encounter. Use Import Sheet to choose a .gcs or JSON file; network clients instead wait for the server's authoritative state.

Current status #

The codebase currently implements the following. Automated tests cover the underlying rules and state paths; rendered interaction still requires the QA called out below.

Area Implemented behavior
GCS import Selected attributes, current/max HP and FP, skills, recursively nested trait/equipment weapons, equipped armor DR, portrait bytes/path, and source path for local reload
Turn flow Immutable snapshots across maneuver selection, attack setup/confirmation, attack roll, defense, injury, non-combat resolution, and turn completion, including rewindable combat-prompt context
Maneuvers 27 maneuver variants, status/posture/encumbrance availability filters, target and reach validation, Aim/Evaluate accumulation, and supported Extra Effort combinations
Combat math 3d6 checks, authoritative random hit locations, range bands, hit-location modifiers, maneuver/modifier breakdowns, weapon-skill-based Parry/Block, damage, DR, wounding, shock, major wounds, HT follow-ups, consciousness, death, and leg crippling
Injury model 27 extended humanoid hit locations plus structured GM follow-up flags for rules that are not represented as actor state
VTT and panels Premium and Lite interfaces over one rules engine, hex grid, movable and six-way facing portrait/fallback tokens, HP bars, maneuver arrows/self-rings, token-to-token range ruler, character cards, event log, GM controls, and phase surfaces
History and persistence Validated immutable history, rewind-by-index, non-overwriting round/checkpoint snapshots, separate event-log file, portrait-path preservation without embedded image bytes, and final save on clean application exit
Networking CLI-selectable server/client roles, token authentication, first-client GM assignment, GM ownership-roster controls, authoritative intent handling, reconnect, and state/settings/event-log synchronization

cargo test --locked --all-targets is the release-oriented verification command. See docs/TESTING.md for the current test inventory.

Fantactical Premium is the fallback for a fresh install. A saved local edition preference takes precedence on later launches. Premium uses a board-first command shell, authoritative token motion, tactical state cues, and a compact combat surface. Fantactical Lite preserves the original low-effects layout and direct interaction style. Both editions emit the same intents and use the same game state, history, persistence, permissions, and network authority.

Important residual gaps #

Implemented does not mean rules-complete or production-ready:

  • Critical-hit and critical-miss table results are selected and reported, but several non-damage effects (drop weapon, hit self/ally, lose balance, and similar results) are not applied mechanically.
  • Standalone Feint/Feign declarations are recorded as non-damaging actions, but their contests and margin effects are not resolved automatically.
  • Attack arc checks, hit-location sub-location rolls, the Critical Head Blow table, paralysis, infection, appearance changes, and other location-specific consequences are emitted as explicit GM follow-up flags rather than automatically resolved.
  • Shock modifiers currently become active immediately instead of waiting for the injured actor's next turn, and the Change Posture maneuver only automates recovery from knockdown.
  • The UI exposes GM controls for extra turns, extra attacks, Enhanced Time Sense, and network actor ownership, but not for editing PC/NPC identity.
  • Lite still ships only the Mil-Sim theme; Appearance switches presentation, motion, effects, and scale rather than selecting another Lite color theme.
  • There is no in-app network configuration screen.
  • Network transport reconnects automatically, but the server assigns a new client identity and does not restore the prior GM role or actor ownership.
  • The GUI and network flow need real-session and cross-platform QA. Linux is the primary development platform; Windows and macOS have not been verified here.

Build and run #

Prerequisites #

  • Rust 1.88 or newer (declared by the crate and required by the locked image dependency)
  • Bevy's platform libraries; see the official Bevy setup guide

Run an offline session:

cargo run --release

Choose Fantactical Lite for this launch, or force Reduced Motion in either edition:

cargo run --release -- --ui lite
cargo run --release -- --reduced-motion

The in-app Appearance menu switches Premium/Lite, Full/Reduced Motion, effects quality, and interface scale. Those preferences are stored in fantactical_ui.json in the working directory; they never enter encounter history or network snapshots. Command-line presentation flags override the loaded preference in memory and are not written merely by launching the app.

The default mode is off, the default port is 9002, and the default session token is fantactical. Inspect all launch options with:

cargo run --release -- --help

Network mode #

Start a server reachable from other machines:

cargo run --release -- \
  --network-mode server \
  --host 0.0.0.0 \
  --port 9002 \
  --token replace-this-token

Connect a client:

cargo run --release -- \
  --network-mode client \
  --host 192.0.2.10 \
  --port 9002 \
  --token replace-this-token

The first authenticated client is the GM. Later clients begin with no actor ownership; the GM can assign their actors from the connected-client roster in GM Config.

Saves and resume #

Offline and server processes write files named <session>_round<round>.json and <session>_log.json. Choose their location and resume validated files with the following options. Missing output directories are created as needed.

cargo run --release -- \
  --session-name friday \
  --save-dir saves \
  --load-history saves/friday_round4.json \
  --load-log saves/friday_log.json

Client processes never write session persistence files and reject --load-history/--load-log; they receive authoritative state from the server. Round snapshot names are create-once and are not overwritten. If a resumed session saves a round whose canonical file already exists, Fantactical preserves that input and writes <session>_round<round>_checkpoint<N>.json instead.

When --load-history is supplied without --load-log, Fantactical also loads an existing <session>_log.json from --save-dir. Pass --load-log to select a different log explicitly.

CLI test tool #

cargo run --bin ftctl -- dice
cargo run --bin ftctl -- check-roll 12 15
cargo run --bin ftctl -- damage-roll 2 --adds 1
cargo run --bin ftctl -- test-injury \
  --hp 12 --location torso --damage 5 --damage-type cut
cargo run --bin ftctl -- available-maneuvers --posture standing
cargo run --bin ftctl -- range-penalty 15
cargo run --bin ftctl -- hex-distance 0 0 3 2
cargo run --bin ftctl -- save-state /tmp/fantactical-state.json --round 1
cargo run --bin ftctl -- load-state /tmp/fantactical-state.json

save-state also uses create-once semantics and fails if its destination already exists.

Tests #

cargo test --locked --all-targets

Documentation #

License #

MIT

If you find a bug, please report it on the canonical Tangled repository or submit a pull request there. The GitHub repository is a public mirror.