Skip to content

codinghistorian/animal_fighters_club

Repository files navigation

FFC Prototype

Runnable Rust/Bevy prototype for an original low-poly 3D arcade arena brawler. It uses Bevy primitives only: no external art, no copied layouts, and no third-party physics crate.

Run

cargo run

The window opens at 1280x720.

Web build

One-time setup:

rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.121

Build the static web folder:

./scripts/build_web.sh

The default web build is online-capable and uses the web-online feature. For an offline-only browser build, run:

WEB_FEATURES=web ./scripts/build_web.sh

Serve locally:

python3 -m http.server 8000 --directory web_dist

Open http://127.0.0.1:8000, then click or press Enter to start user mode. The browser starts at the same Bevy play menu as native, so Local Play is always available. When server is present, the launcher validates the WebTransport settings before WASM startup; the in-game Online Multiplayer path requests a fresh short-lived token only after the player submits a nickname.

Production web online play uses WebTransport and server-issued Lightyear Netcode tokens so the private Netcode key is never included in the WASM artifact. Start a WebTransport server with a production TLS certificate/key and a token endpoint listener:

cargo run --bin ffc-server --no-default-features --features server -- \
  --transport webtransport \
  --bind 0.0.0.0:4433 \
  --cert /etc/letsencrypt/live/game.example.com/fullchain.pem \
  --key /etc/letsencrypt/live/game.example.com/privkey.pem \
  --public-addr 203.0.113.10:4433 \
  --token-bind 127.0.0.1:40001 \
  --token-cors-origin https://example.github.io

Expose the token HTTP endpoint over HTTPS in production, usually by reverse proxying https://game.example.com/connect-token to 127.0.0.1:40001. The proxy must allow POST and OPTIONS, and the server's --token-cors-origin should match the GitHub Pages origin that hosts web_dist/.

Launch the GitHub Pages build with URL parameters:

https://example.github.io/animal_fighters_club/?server=game.example.com:4433&name=player

The browser accepts server=host:port, tokenEndpoint=https://.../connect-token, name=player, clientId=123, and optional cert=<64 hex sha256>. If tokenEndpoint is omitted, it defaults to https://${server}/connect-token; if clientId is omitted, a browser client ID is generated and stored in localStorage. The cert hash is intended for local/self-signed WebTransport testing; production CA-issued certificates usually do not need it.

For local self-signed browser testing, generate an ECDSA P-256 certificate. Browser WebTransport certificate-hash mode rejects common RSA self-signed certs:

mkdir -p target/local-webtransport
openssl ecparam -name prime256v1 -genkey -noout \
  -out target/local-webtransport/key.pem
openssl req -new -x509 \
  -key target/local-webtransport/key.pem \
  -out target/local-webtransport/cert.pem \
  -days 14 \
  -subj "/CN=127.0.0.1" \
  -addext "subjectAltName=IP:127.0.0.1,DNS:localhost"
openssl x509 -in target/local-webtransport/cert.pem -outform der \
  | openssl dgst -sha256 -binary \
  | xxd -p -c 64

Use the printed digest as cert= and run the local server with the same cert:

cargo run --bin ffc-server --no-default-features --features server -- \
  --transport webtransport \
  --bind 127.0.0.1:4433 \
  --cert target/local-webtransport/cert.pem \
  --key target/local-webtransport/key.pem \
  --public-addr 127.0.0.1:4433 \
  --token-bind 127.0.0.1:40001 \
  --token-cors-origin http://127.0.0.1:8000

Example local browser URL:

http://127.0.0.1:8000/?server=127.0.0.1:4433&tokenEndpoint=http%3A%2F%2F127.0.0.1%3A40001%2Fconnect-token&name=p1&clientId=101&cert=<64 hex sha256>

The Online Multiplayer status should move through requesting token, connecting transport, joining, and lobby. If it shows token error or disconnected, check the browser console and the server log for the detailed token or WebTransport failure.

Multiplayer v1 status

Native online multiplayer v1 currently has:

  • Lightyear UDP/netcode native server and client.
  • WebTransport browser client support with server-issued short-lived Netcode tokens for production web builds.
  • Two-player online duel slot assignment.
  • Client-requested authoritative match starts for online dev/user-mode flows, including single-player human-vs-server-bot matches from local setup.
  • Server-side slot validation for relayed PlayerCommand input.
  • Server-authoritative shared headless gameplay stepping at 60 Hz from the latest accepted input per slot, including accepted character/style/equipment loadout tuning.
  • Native client local reconciliation and remote interpolation for movement snapshots.
  • Server-authoritative authored combat through the shared gameplay stack, including movement, jumps/dashes, guard, grabs, authored attacks, specials, items, hazards, health/stamina changes, knockouts, ring-outs, respawn, stocks, scores, and match results.
  • Stable compact action technique IDs in server snapshots so native clients can drive authored attack/action visuals from authoritative online state.
  • Configurable server arena selection with server-authoritative arena hazard damage/knockback and replicated hazard activity/cooldown snapshots.
  • Compact server-authoritative arena item pickup/use/throw/bomb state with replicated item snapshots and held-item relations.
  • Server-authored special lifecycle snapshots for casts, including owner, exact generic/Bee/Penguin skill kind, phase, position, velocity, radius, and active state.
  • Native clients spawn/update/despawn visual proxies from authoritative special and active-hitbox snapshots, using exact Bee/Penguin scene assets where the snapshot has enough identity.
  • Native clients apply authoritative match config, action, technique, vitals, stock, score, and match snapshots plus item/special state while local mutating gameplay systems are disabled in online mode. Local prediction replays pending input with the server-accepted loadout.
  • Native online sessions show a debug status overlay with connection state, slot, RTT/jitter, snapshot/combat/core-action/relation/item/special/hitbox/hazard counts, inferred missed snapshots/packet loss, server tick time, remote command counts, input acknowledgements, and correction distance.
  • Client gameplay scheduling is extracted into src/gameplay.rs, which now owns the ordered fixed-tick gameplay simulation sets and online local-authority gate used by the native client path.
  • ffc-net-smoke assertions and metrics for welcome, slot assignment, remote commands, snapshots, combat snapshots, core action snapshots, network health, server tick time, item snapshots, special snapshots, hitbox snapshots, hazard snapshots, and local corrections.
  • Dockerfile/Compose support for running ffc-server.

Remaining caveat: the online authority now runs the shared headless gameplay stack, but client presentation is not complete parity yet. Exact Bee/Penguin skill scene assets are used for many online special proxies, active hitboxes are replicated as debug-style visual proxies, and authored technique IDs drive local fighter visuals, but richer transient feedback such as hit sparks, SFX/camera shake events, and multi-part special presentation still need a dedicated server-authored event stream.

Start a local server:

cargo run --bin ffc-server --no-default-features --features server -- --bind 127.0.0.1:40000

Use --arena 1, --arena 2, or --arena 3 to run online matches on a hazard arena; arena 0 is the default Crown Ring.

Run a native online client against the server:

cargo run --features online -- --online --server 127.0.0.1:40000 --client-id 1 --name p1

For two local clients, use different --client-id and --name values. The first two clients are assigned online duel slots 0 and 1.

Headless connection and input-relay smoke test. Run the two smoke clients overlapping:

cargo run --bin ffc-server --no-default-features --features server -- --bind 127.0.0.1:40000
cargo run --bin ffc-net-smoke --features online -- --server 127.0.0.1:40000 --client-id 1 --name p1 --ticks 900
cargo run --bin ffc-net-smoke --features online -- --server 127.0.0.1:40000 --client-id 2 --name p2 --ticks 300

A passing smoke prints event=match_start on both clients and a net_smoke_summary with nonzero remote_commands, snapshots, and combat_snapshots/core_action_snapshots, plus network/correction metrics such as rtt_ms, jitter_ms, missed_snapshots, snapshot_loss, server_tick_ms, prediction_replays, pending_inputs, last_replayed_inputs, item_snapshots, special_snapshots, hitbox_snapshots, hazard_snapshots, local_corrections, and last_correction. The smoke binary exits nonzero if those required network events do not happen. Add --require-items to require item snapshots, and add --require-hazards to each smoke client when testing against a server started with --arena 1, --arena 2, or --arena 3.

The dedicated server container builds the server binary with:

cargo build --release --no-default-features --features server --bin ffc-server

Run the server locally with Docker Compose or OrbStack:

docker compose up -d --build ffc-server
docker compose logs -f --tail=200 ffc-server

The Compose service publishes UDP port 40000, copies the repository assets/ folder into the runtime image, restarts with unless-stopped, and uses Docker's local logging driver with rotation.

For LAN or internet friend testing on a Linux desktop:

docker compose up -d --build ffc-server
sudo ufw allow 40000/udp

Forward router UDP port 40000 to the desktop's LAN IP. Friends should connect to public-ip-or-ddns-name:40000; same-LAN clients can connect to the desktop's LAN address. If port forwarding does not work because of CGNAT or double NAT, use a private tunnel such as Tailscale for playtests.

Controls

Setup:

  • Enter: start the selected match
  • 1: timed team score rules
  • 2: free-for-all score rules
  • 3: stock ring-out rules
  • A: cycle arena selection
  • Demo battle is locked to one player fighter and one bot fighter.
  • Z/X: cycle player/bot styles
  • T/Y: cycle player/bot equipment
  • R: reroll replay seed while staying in setup

Player:

  • Arrow keys: move.
  • Z: aim toward the bot; tap to grab.
  • X: strong attack, throw held item, heavy throw during grab hold.
  • C: light attack, pick up item when empty-handed, dash attack while dashing, jump attack while airborne, swing held item.
  • V: jump, quick stand while knocked down.
  • Double-tap a movement key: dash, recovery roll while knocked down.
  • X + C: guard / block.
  • While grabbed, X + C plus movement away: escape attempt; X + C: brace against throws.

Native Dev Hotkeys:

  • Shift+U: enter user mode from the dev setup screen.
  • F2: toggle map editor while in setup.
  • H: toggle hitbox, hurtbox, item, special, impact-source, reaction, technique-window, and feedback-cue debug overlays.
  • Shift+Up/Down: pan the gameplay camera forward/back.
  • Shift+Left/Right: rotate the gameplay camera.
  • Shift+Cmd/Ctrl+Up/Down: raise/lower the gameplay camera.
  • Shift+mouse wheel: zoom the gameplay camera.
  • Shift+R: reset the gameplay camera.
  • Shift+C: cycle gameplay camera filter/action-effect look.
  • Shift+Cmd/Ctrl+F: toggle whether the saved single-player camera follows Player 1.
  • Shift+Cmd/Ctrl+L: load the saved single-player camera preset into the live dev camera.
  • Shift+Cmd/Ctrl+S: save the current gameplay camera angle as the single-player camera preset.
  • Dev hotkeys are native-only and are blocked while user mode is active.

Debug:

  • H: toggle hitbox, hurtbox, item, special, impact-source, reaction, technique-window, and feedback-cue debug overlays
  • 1: timed team score rules
  • 2: free-for-all score rules
  • 3: stock ring-out rules
  • R: reset/rematch with the current rules
  • Enter on results: return to setup with the current selections

Pickups:

  • Guard Battery: restores health and stamina.
  • Foam Mallet: carried melee prop with four durability uses. Swing it or throw it.
  • Pop Bomb: carried explosive. Throw it or drop it to arm a short fuse.
  • Spark Lobber: ranged carried prop with a long light poke.
  • Breeze Buoy: utility prop that consumes itself for stamina.
  • Stone Crate: heavy prop with stronger close swings and throws.
  • Guard Kite: shield-like prop with a short bash and high durability.

Specials:

  • Pulse Dart: straight stamina projectile.
  • Trip Plate: placed trap.
  • Snap Wave: close expanding shockwave.
  • Drift Field: short lingering hazard.

Styles:

  • Anchor: slower, sturdier guard economy, stronger ring-control throws, a longer-invulnerable brace step, and stamina-paid heavy startup armor with longer whiff recovery.
  • Vector: faster pressure movement and attacks, weaker guard economy, a shorter dash-attack flow, and a narrow dash-attack-to-light branch window.
  • Catalyst: mid-range preference, stronger special usage bias, cheaper/faster special cycling, and stamina-disrupting special hits.
  • Style identity is visible through compact HUD taglines and small in-match accent rings.

Equipment:

  • Dash Coil: boosts dash attack knockback on cooldown.
  • Aerial Spur: boosts jump attack damage on cooldown.
  • Counter Cell: boosts perfect-guard counter damage on cooldown.
  • Heavy Seal: boosts heavy attack knockback on cooldown.
  • Equipment identity is shown through HUD effect text and a small fixed back-chip accent.

Guard Batteries are collected automatically by walking into them. Carried items must be picked up with grab.

Prototype Notes

  • The demo starts as one controlled fighter versus one bot in a circular stone arena with red target markings, side blocks, a rear primitive billboard, and a dark void.
  • Arena definitions now provide spawn points, item anchors, ring-out bounds, camera hints, platform blocks, and hazard marker data. Crown Ring, Split Causeway, Low Tide Steps, and Crank Yard can be selected from setup.
  • The camera follows the center of living fighters from a high angled arcade view, while single-player user mode follows the controlled fighter with the saved single-player camera preset.
  • Movement uses simple acceleration, friction, gravity, jump, dash stamina cost, radius-aware platform support, ledge jump grace, side pushout, limited wall bounce, and manual ground checks.
  • Combat uses Rust-side technique definitions for startup, active, recovery, cancel/branch windows, stamina hooks, and impact payloads across the light chain, dash attack, jump attack, heavy attacks, guard counter, item actions, specials, and stateful grab/throw.
  • Heavy hits, combo finishers, throws, thrown items, and Pop Bombs can launch fighters into reaction profiles, limited ground/wall bounce, knockdown, and get-up states.
  • Knocked-down fighters wake automatically after a short pause and gain brief invulnerability while standing.
  • Guard reduces front-facing damage and knockback, but stamina pressure can cause guard break. A precisely timed guard prevents chip damage and starts a quick counter.
  • Guard plus dash performs a short defensive step, and knocked-down fighters can quick stand or recovery roll instead of waiting for automatic get-up.
  • Primitive hit sparks, guard flashes, dash trails, dust puffs, respawn beams, and item effects make combat state easier to read without external assets.
  • Simple arena items add arcade match chaos: stamina recovery, a carried melee prop, and a carried short-fuse bomb.
  • Specials add a small projectile, trap, shockwave, and lingering hazard layer with stamina costs, cooldowns, owner grace, and reset cleanup.
  • Arena hazards now include launch pulses, slowing snare fields, and bumper nodes that apply neutral shared-impact pressure without awarding ring-out credit.
  • HUD rows tint and show EDGE danger when fighters drift near arena ring-out bounds or fall toward the lower blast plane.
  • Fighters use shared action rules with style tuning for movement, stamina economy, guard pressure, attack timing, throw pressure, and bot range preference. The current demo exposes one 3D-Arena-Brawler-style player control layout against one bot.
  • Equipped modifiers each affect one move, show effect text/cooldown status in the fighter HUD row, flash on trigger, route a feedback cue, and add a small visual accent.
  • Move, item, special, style, and equipment tuning now live in internal Rust-side definition structs for faster iteration.
  • Match flow now runs through countdown, fighting, time-up, results, reset/rematch, and return-to-setup phases.
  • Rule presets cover timed team score, timed free-for-all score, and stock ring-out matches.
  • Team-score rules now use centralized even-vs-odd team membership and explicit friendly-fire policy to block self/teammate strikes, grabs, item hits, bombs, and specials from awarding damage or ring-out credit.
  • A lightweight local setup shell selects, previews, and applies the mode, arena, one bot opponent, fighter styles, equipment, and replay seed before countdown.
  • Local match telemetry tracks ring-outs, uncredited falls, item hits, throw hits, guard breaks, and total damage for result/debug displays.
  • Ring-outs happen when a fighter falls below the arena or gets pushed too far out. The last attacker gets a score point; stock rules also remove lives and can end the match.
  • Bots pick the nearest opponent, move toward them, strafe, attack in range, and sometimes dash.
  • Bots can chain light attacks, sometimes grab at close range, occasionally use dash or jump attacks, and make role-aware item choices: ranged pokes at distance, explosives near mid-range, utility when stamina is missing, heavy props for ring pressure, and defensive props for guarding.
  • Bots now quick-stand or recovery-roll from knockdown, guard against readable incoming attacks, avoid active arena hazards/enemy specials/thrown items/armed bombs, and use style/equipment-aware personality weights with small deterministic mistakes.
  • The HUD now shows phase/rule state, results, held item status, special/equipment cooldowns, debug overlays, timer, scores, health, and stamina.

Next Steps

See docs/prototype_todos.md for the reference-informed combat roadmap and planning backlog.

  • Continue platform collision tuning for walls, ramps, and ledges.
  • Continue tuning balance, readability, and setup polish from live playtests.
  • Add richer arena hazard patterns and bot avoidance.
  • Expand style and equipment identity so archetypes and modifiers change more than raw tuning.

About

Online Multiplayer 3D Arena Brawler

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages