Files
brandling-safari/README.md
Gabe 238cd9c6c3 Brandling Safari: LAN safari-zone creature catching on the HD-2D engine
- Authoritative 30 Hz server (ENet, SQLite): accounts, characters, wild AI, tall grass /
  water / wandering spawns, fishing, captures, follower chains, clock + weather, LAN beacon
- Client: title / join / character creator / play screens, prediction + reconciliation,
  interpolated players and parties, HD-2D UI (dialogue with Professor Henry, HUD, collection)
- Shared deterministic zone generator, movement, protocol, roster, appearance
- Paper-doll character art pipeline (PixelLab base walk + per-item animation edits)
- Brandling roster from the company list (logo colors, jev types), Blender safari props
- Headless test bot
2026-10-02 15:20:27 -05:00

203 lines
9.7 KiB
Markdown

# Brandling Safari
A small LAN-multiplayer creature-catching game set in a safari zone, rendered in the
**HD-2D** style (Octopath Traveler / Triangle Strategy) with the
[Vibe-HD-2D](../Vibe-HD-2D) engine. The creatures, **Brandlings**, are named after real
companies: each one's sprite is generated by [Company_Sprite_Gen](../Company_Sprite_Gen) from
the company name, recolored with its logo's brand colors, and its type comes from the jev
business classifier.
![catching with a party in tow](docs/catch.jpg)
| | |
| --- | --- |
| ![title](docs/title.jpg) | ![creator](docs/creator.jpg) |
| ![Professor Henry](docs/henry.jpg) | ![night rain at the dock](docs/night.jpg) |
| ![dusk with a full party](docs/dusk.jpg) | ![fishing from the dock](docs/dock.jpg) |
| ![collection](docs/collection.jpg) | |
## Features
- **Meadow Safari Zone**: terraced grass diorama with a north ridge and an east plateau, a lake
with a fishing dock, a pond, eight tall-grass patches, an entrance gate and plaza with
Professor Henry's field tent, lamps, fences and props (3D models built in Blender, pixel-art
ground and foliage).
- **Character creator** (persists server-side): masculine / feminine base body, 6 skin tones, 5
hair styles, 3 hats, 5 tops, 4 bottoms, 3 shoes, any color per slot. Every combination is
animated by one shared walk cycle (see [Character art](#character-art)).
- **Professor Henry** greets new rangers with an intro, hands out 30 Safari Capsules and
restocks them later.
- **Finding Brandlings**: they burst out of tall grass around you as you walk (more often when
running), swim in the lake and surface with splashes, wander the meadow, and bite on a
fishing line cast from the shore or dock.
- **Catching**: throw a Safari Capsule (closer and calmer = better odds; hooked ones are dazed);
misses can make them flee. Caught Brandlings join your party (up to 6) and follow behind you
in a chain, bouncing and turning to face their direction; the rest go to your box.
- **Multiplayer**: dedicated authoritative server, LAN discovery, client-side prediction with
server reconciliation, interpolated remote players and their parties, chat bubbles.
- **World state from the server**: time of day (dawn, day, dusk, night with stars, moon and
lantern light) and weather (clear, cloudy, rain, thunderstorm, mist) change the look on every
client and which Brandlings appear.
## Building
Requirements: CMake >= 3.21, a C++20 compiler, OpenGL 4.5, `libepoxy`, SQLite 3, and the
engine checked out next to this repo (`../Vibe-HD-2D`; set `HD2D_ENGINE_DIR` otherwise, or
leave it missing to fetch it from `HD2D_ENGINE_GIT`). GLFW, GLM, stb, cgltf and Dear ImGui
come from the engine; nlohmann/json is used from the system or fetched, ENet is always fetched.
```sh
# Arch / CachyOS
sudo pacman -S cmake ninja glfw glm libepoxy stb sqlite nlohmann-json
cmake -S . -B build -G Ninja
ninja -C build # safari_client, safari_server, safari_bot
```
Server only (no GPU needed): `cmake -S . -B build_srv -G Ninja -DBS_BUILD_CLIENT=OFF`.
## Playing
```sh
./build/safari_client # title screen: Host & Play, or Join a LAN Game
```
- **Host & Play** starts `safari_server` next to the client on port 27420 and joins it.
Other players on the network see it under **Join a LAN Game** (UDP broadcast on 27421) or
can type the host's address.
- A dedicated server: `./build/safari_server [--port 27420] [--name "My Safari"]`.
| Input | Action |
| --- | --- |
| WASD / arrows | Move (camera-relative); hold Shift to run |
| Space | Throw a Safari Capsule at the targeted Brandling |
| F | Cast a line toward the water you face / reel in when it bites |
| E | Talk to Professor Henry (advances dialogue too) |
| Tab | Your Brandlings: Enter moves one between party and box |
| Enter / T | Chat |
| Q / E, right-drag, wheel | Camera |
| Esc | Menu |
| F1 / F12 | Render tuning panels / screenshot |
Your identity is a random token in `~/.local/share/brandling-safari/client.json`; the server
keys your ranger, Brandlings and dex to it. `--profile NAME` uses a separate identity (several
clients on one machine).
### Server
```
safari_server [--port N] [--name NAME] [--db PATH] [--seed N] [--zone meadow]
[--roster PATH] [--manifest PATH] [--day-minutes M] [--hour H]
[--weather clear|cloudy|rain|storm|fog]
```
Everything is saved in SQLite (`~/.local/share/brandling-safari/world.db` by default):
players (name, look, position, capsules, intro), caught Brandlings, the per-player dex, the
world clock and weather. Console commands: `help`, `players`, `time <h>`,
`weather <name>`, `give <player> capsules <n>`, `spawn <species>`, `save`, `quit`.
`safari_bot` is a headless test client that walks into tall grass, throws capsules, fishes and
talks to Henry, printing everything it receives.
## Architecture
```mermaid
flowchart LR
subgraph Server [safari_server, 30 Hz, authoritative]
G[Game: players, wild AI, spawns,<br/>fishing, captures, followers,<br/>clock + weather]
DB[(SQLite)]
G <--> DB
end
subgraph Client [safari_client]
N[NetClient] --> W[World: prediction,<br/>interpolation, effects]
W --> R[HD-2D Renderer]
A[App: screens + UI] --> N
end
N <-- "ENet UDP: inputs, actions /<br/>snapshots, events" --> G
S[shared/: Protocol, Zone,<br/>Movement, Roster, Appearance] --- G
S --- W
```
- `shared/` is compiled into both programs. `Zone` generates the whole zone deterministically
from `(zone id, seed)`, so the server (collision, spawn points, water, tall grass) and every
client (terrain, props, decoration) agree on the map and only the id + seed go over the
wire. `Movement` is the single player-movement function: the server applies it once per
input command, the client predicts with it and replays unacknowledged inputs when a
snapshot arrives.
- Clients send only intents: movement inputs (unreliable, last 4 resent) and actions (throw,
cast, reel, talk, party, chat). The server validates everything (ranges, capsules, speed
budget, walkability) and broadcasts snapshots (unreliable, 30 Hz) and events (reliable:
emerge, splash, throw, capture result, bite, hook, catch...).
- Protocol: `shared/Protocol.h`. Bump `kProtocolVersion` on any wire change.
| Path | What |
| --- | --- |
| `shared/` | protocol, ENet wrapper + LAN discovery, zone generator, movement, roster + appearance |
| `server/` | dedicated server: game simulation, spawns, creatures, SQLite persistence |
| `client/` | `App` (screens, HUD, input), `World` (scene, entities, effects), `CharacterSprite` (paper-doll compositor), `NetClient` |
| `bot/` | headless test client |
| `assets/` | characters, creatures (roster + sprites), models (`safari.glb`), dialogue |
| `tools/` | art + data pipelines (below) |
| `art_src/` | raw PixelLab output, caches and reports the tools build from |
## Content pipelines
The game ships the generated assets; the tools are only needed to change them. API keys go in
`.env` here or in the parent folder (see `.env.example`).
### Brandlings (`tools/build_roster.py`)
```sh
python3 tools/build_roster.py --csv ../clickup_web_design_export.csv [--limit N] [--jobs 16]
```
Reads only the company name, domain, staging-site URL (logo search only, never stored), service
list and logo-note colour words from the ClickUp export (the export itself holds private client
data and is never committed). For each company: logo fetched from its domain, then its staging
site (`Company_Sprite_Gen/logo_fetch.py`; a logo shared by 2+ companies is a template and is
dropped), type from the jev classifier, sprite from Company_Sprite_Gen coloured from the logo
(else the logo note's colour words, else a per-creature shade of the type colour), plus
deterministic rarity, catch/flee rates and habitat. Writes `assets/creatures/roster.json`,
`assets/creatures/sprites/`, a review `contact_sheet.png` and `art_src/creatures/report.json`
(counts, logo hit rate by method, type histogram). Logos, classifier answers and renders are
cached in `art_src/creatures/cache/`, so reruns only do new work. Later this will read from the
ClickUp API instead of the CSV.
Species ids are permanent, because saved creatures store them and the loader requires
`id == index`. The first run numbers species by slug; later runs keep every slug's id, append
new companies after the last id, and keep companies that left the CSV as `"retired": true`
entries (sprite kept) so no id is reused. A `--limit N` test run leaves unsampled species as
they were. Retired Brandlings stay in players' collections but no longer spawn.
### Character art
`tools/character_art.py` builds the player layer library and Professor Henry with PixelLab;
see [docs/CHARACTER_ART.md](docs/CHARACTER_ART.md) for how one base walk cycle animates every
outfit and skin tone with 20 generations per item.
### 3D props
```sh
python3 tools/safari_textures.py # painted textures -> assets/models/src/
blender -b -P tools/blender_safari.py # -> assets/models/safari.glb
```
Gate, tent, fences, lamps, signposts, dock, crates, barrels, benches and hay bales, following
the engine's glTF conventions (see the engine README). Trees, bushes and rocks come from the
engine's `scenery.glb`.
## Verification
Captures render in a hidden window into an offscreen target, so nothing appears on screen and
keyboard input cannot disturb them:
```sh
./build/safari_server --port 27500 --db /tmp/test.db &
./build/safari_client --screenshot /tmp/play.png --frames 600 --connect 127.0.0.1:27500 \
--auto-create --auto-dialogue --profile test --move 0.8,0.6 --run --key 450:space
```
`--look body,skin,hair,hat,top,bottom,shoes[,colors]` sets the look, `--screen creator`
captures the creator, `--hour` / `--weather` set the title backdrop, `--key frame:key`
presses space / e / f / tab / enter on a frame, `--show` makes the window visible.