# 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`. Built files load assets from the source checkout (`BS_ASSET_DIR` and the engine's asset path are baked in at configure time), so run them where they were built. ### Windows (MSYS2, recommended) Install [MSYS2](https://www.msys2.org/) and open the **MSYS2 UCRT64** shell: ```sh pacman -S --needed git mingw-w64-ucrt-x86_64-{gcc,cmake,ninja,pkgconf,glfw,glm,libepoxy,sqlite3,nlohmann-json} git clone ssh://git@git.gabjimmy.com:30009/Gabe/Vibe-HD-2D.git git clone Brandling-Safari # side by side with Vibe-HD-2D cd Brandling-Safari cmake -S . -B build -G Ninja ninja -C build # safari_client.exe, safari_server.exe, safari_bot.exe ./build/safari_client.exe ``` The executables find `glfw3.dll`, `libepoxy-0.dll`, `libsqlite3-0.dll` and `libwinpthread-1.dll` on the UCRT64 shell's `PATH`. To start the game from Explorer or a shortcut, copy them next to the executables once: ```sh ldd build/safari_client.exe build/safari_server.exe | grep -i /ucrt64/ | awk '{print $3}' | sort -u | xargs -I{} cp {} build/ ``` ### Windows (Visual Studio + vcpkg) In an **x64 Native Tools Command Prompt for VS 2022**: ```bat git clone https://github.com/microsoft/vcpkg C:\vcpkg && C:\vcpkg\bootstrap-vcpkg.bat C:\vcpkg\vcpkg install glfw3 glm libepoxy sqlite3 nlohmann-json --triplet x64-windows cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo ^ -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake cmake --build build build\safari_client.exe ``` vcpkg copies the DLLs next to the executables. The MinGW build is verified (zero warnings); the MSVC build has not been compiled yet, so expect possible small fixes there. Windows notes: the client has no console window. Your identity and a hosted game's `world.db` and `server.log` live in `%APPDATA%\brandling-safari`. The first time a server runs, allow it through Windows Defender Firewall on private networks so LAN players can join (UDP 27420 for the game, 27421 for discovery). The art tools in `tools/` work on Windows with Python 3 (`pip install requests pillow numpy`); only Company_Sprite_Gen's part-library generator (`pixellab_library.py`) is Linux/macOS-only. ## 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 `, `weather `, `give capsules `, `spawn `, `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,
fishing, captures, followers,
clock + weather] DB[(SQLite)] G <--> DB end subgraph Client [safari_client] N[NetClient] --> W[World: prediction,
interpolation, effects] W --> R[HD-2D Renderer] A[App: screens + UI] --> N end N <-- "ENet UDP: inputs, actions /
snapshots, events" --> G S[shared/: Protocol, Zone,
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.