{"id":50885938,"url":"https://github.com/kbennett2000/bsg-adventure","last_synced_at":"2026-06-15T17:01:35.712Z","repository":{"id":358265914,"uuid":"1240311549","full_name":"kbennett2000/bsg-adventure","owner":"kbennett2000","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-16T14:02:12.000Z","size":328,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-16T15:48:49.384Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kbennett2000.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-16T01:57:27.000Z","updated_at":"2026-05-16T14:02:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kbennett2000/bsg-adventure","commit_stats":null,"previous_names":["kbennett2000/bsg-adventure"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kbennett2000/bsg-adventure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Fbsg-adventure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Fbsg-adventure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Fbsg-adventure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Fbsg-adventure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kbennett2000","download_url":"https://codeload.github.com/kbennett2000/bsg-adventure/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Fbsg-adventure/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34372130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-15T17:01:34.841Z","updated_at":"2026-06-15T17:01:35.707Z","avatar_url":"https://github.com/kbennett2000.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BSG Adventure\n\n```\n                          ╔═══════════════════╗\n                          ║░░░░░░░░░░░░░░░░░░░║\n                          ║░░░░░░░░░░░░░░░░░░░║\n                          ╚═════════╤═════════╝\n                                    │\n ╔══════════════════════════════════╧═════════════════════════════════╗\n ║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓║═══▶\n ║▓▓▓▓▓▓B A T T L E S T A R   G A L A C T I C A     B S G - 7 5▓▓▓▓▓▓▓║═══▶\n ║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓║═══▶\n ╚══════════════════════════════════╤═════════════════════════════════╝\n                                    │\n                          ╔═════════╧═════════╗\n                          ║░░░░░░░░░░░░░░░░░░░║\n                          ║░░░░░░░░░░░░░░░░░░░║\n                          ╚═══════════════════╝\n\n                       A   D   V   E   N   T   U   R   E\n```\n\n![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)\n![Zero dependencies](https://img.shields.io/badge/dependencies-none-brightgreen)\n![Standard library only](https://img.shields.io/badge/stdlib-only-informational)\n![License: MIT](https://img.shields.io/badge/license-MIT-green)\n\nA terminal-based parser text adventure. Parody of *Battlestar Galactica* (2004\nreboot) from the POV of a Specialist 3rd Class assigned to environmental systems.\n\nYou mop. You reroute coolant. You refill the XO's \"water\" bottle. The senior\nstaff have melodramatic affairs around you. They do not remember your name. You,\nthrough a series of small competent decisions and one (1) misplaced napkin, will\nprobably save the fleet.\n\nOr get spaced. Or end up in a Cylon love triangle.\n\nSo say we all.\n\n---\n\n## Install\n\nRequires **Python 3.11+**. Standard library only — no pip, no venv, no\ndependencies.\n\n```bash\ngit clone \u003crepo\u003e\ncd bsg-adventure\npython3 main.py\n```\n\nThat's it.\n\n### Save files\n\nSaves live in `./saves/\u003cplayer_name\u003e/` next to the game. They survive reboots.\nOverride the location with `BSG_SAVE_DIR`:\n\n```bash\nBSG_SAVE_DIR=/var/lib/bsg python3 main.py\n```\n\nEach save is JSON, written atomically (tmp + fsync + rename), versioned for\nfuture format migrations.\n\n### Multi-session over LAN\n\nThe host machine runs the server; anyone on the same network can connect\neither from their own terminal (TCP) or from a web browser (HTTP). Both\nlisteners run in the same process and share the player-name registry —\nthe game is otherwise identical. No internet exposure, no auth, no TLS —\n**LAN only**.\n\n**Start the server:**\n\n```bash\npython3 main.py --serve\n```\n\nDefaults: TCP on `0.0.0.0:4404`, HTTP on `0.0.0.0:4405`, up to 8 concurrent\nsessions across both transports combined, 30-minute idle timeout. Override\nvia flags or env vars:\n\n```bash\nBSG_PORT=4404 BSG_WEB_PORT=4405 BSG_MAX_SESSIONS=12 python3 main.py --serve\n```\n\nOr:\n\n```bash\npython3 main.py --serve \\\n  --bind 192.168.1.10 --port 4404 \\\n  --web-bind 192.168.1.10 --web-port 4405 \\\n  --max-sessions 12\n```\n\nPass `--no-tcp` or `--no-web` to disable either listener (running both\ntogether is the default, but you don't have to).\n\n**Connect from a terminal on the LAN:**\n\n```bash\nnc \u003chost-ip\u003e 4404            # most common\nncat \u003chost-ip\u003e 4404\ntelnet \u003chost-ip\u003e 4404        # also works; CRLF line endings are handled\n```\n\n**Connect from a browser on the LAN:**\n\nOpen `http://\u003chost-ip\u003e:4405/` in any modern browser (Firefox, Chromium,\nSafari, anything from the last decade). The page is a green-on-black\nterminal that mirrors the TCP experience exactly. Server-Sent Events\npush output; POSTs submit commands; tab close hangs up the session\ncleanly via `sendBeacon`.\n\nThe browser client is 100% offline — no external scripts, no webfonts,\nno telemetry, no CDN. Everything in `web/` is served by the local host.\n\n**Policies:**\n\n- One session per player name across BOTH transports. If you're connected\n  via telnet as \"Kara\", a browser tab also trying \"Kara\" gets the in-\n  character refusal and disconnects (and vice versa).\n- Player names sanitized to `[A-Za-z0-9_]{1,32}` before they touch the\n  filesystem.\n- 30-minute idle timeout on TCP; 30-minute idle plus a 60-second grace\n  period after browser disconnect on HTTP (so a tab refresh can reattach).\n- The session autosaves on the way out, so a timed-out player loses at\n  most ~10 turns of progress.\n- Reconnecting with the same name auto-resumes from `auto.json`.\n\n**Operational notes:**\n\n- Save files live in `BSG_SAVE_DIR` (default: `./saves`). Set this to a\n  location outside the install directory so package upgrades don't blow\n  away player progress.\n- A systemd unit template is in [systemd/bsg-adventure.service](systemd/bsg-adventure.service).\n  Install:\n  ```bash\n  sudo useradd --system --home-dir /var/lib/bsg-adventure --create-home bsg\n  sudo mkdir -p /var/lib/bsg-adventure/saves\n  sudo chown -R bsg:bsg /var/lib/bsg-adventure\n  sudo cp -r . /opt/bsg-adventure\n  sudo cp systemd/bsg-adventure.service /etc/systemd/system/\n  sudo systemctl enable --now bsg-adventure\n  ```\n- Server stdout is connect/disconnect/session-error events. `journalctl\n  -u bsg-adventure -f` to tail.\n\n**LAN ONLY.** This server has no auth, no TLS, no rate limiting. Do not\nport-forward it. If the host has a public IP, bind to a specific LAN\ninterface (`--bind 192.168.1.10`) so the listener doesn't bind to the\nWAN interface.\n\n```\n        ╔══════════════════════════╗\n        ║                          ║\n        ║    ◀──────●──────▶       ║\n        ║                          ║\n        ╚══════════════════════════╝\n               BY YOUR COMMAND.\n```\n\n---\n\n## Command reference\n\n### Core verbs\n\n| Verb | Synonyms | What it does |\n|---|---|---|\n| `look` | `l` | Describe where you are (long form) |\n| `examine \u003cthing\u003e` | `x`, `inspect`, `check`, `read` | Get a closer look |\n| `go \u003cdirection\u003e` | `move`, `walk`, also raw `n`/`s`/`e`/`w`/`up`/`down` | Move |\n| `take \u003citem\u003e` | `get`, `grab`, `pick` | Pick something up |\n| `drop \u003citem\u003e` | `leave`, `discard` | Put something down |\n| `inventory` | `i`, `inv` | What you're carrying |\n| `talk to \u003cperson\u003e` | `speak`, `ask` | Start a conversation |\n| `talk to \u003cperson\u003e about \u003cX\u003e` | `ask \u003cperson\u003e about \u003cX\u003e` | Get specific |\n| `use \u003citem\u003e` | | Interact with an item |\n| `use \u003citem\u003e on \u003ctarget\u003e` | | Use one thing on another |\n| `give \u003citem\u003e to \u003cperson\u003e` | `hand` | Hand something over |\n| `eat \u003citem\u003e` | `bite` | Eat. With consequences. |\n| `drink \u003citem\u003e` | `sip`, `chug` | Drink. With more consequences. |\n| `wait` | `z` | Let a turn pass |\n| `save [slot]` | | Save your game to a named slot |\n| `load [slot]` | `restore` | Load it back |\n| `help` | `?` | This list, briefly |\n| `quit` | `exit`, `q` | Leave the ship |\n\n### Flavor verbs\n\n| Verb | What it does |\n|---|---|\n| `salute` | Render proper respect. Nobody returns it. Dignity -1. |\n| `frak` | Express yourself. Burns a turn. Different lament every time. |\n\n### Movement shortcuts\n\n- `n` / `s` / `e` / `w` — single-letter directions\n- `up` / `down` — for ladders, lifts, catwalks\n- `out` — many rooms have an `out` exit that takes you back to the corridor\n- Articles (`the`, `a`, `an`) and small connectors (`to`, `at`, `with`) are\n  stripped — `go to the east` works the same as `e`\n\n### Save slots\n\n- Slot names are sanitized to `[A-Za-z0-9_]{1,32}`\n- `save` with no slot saves to `default`\n- `load` with no slot loads from `default`\n- `auto` is reserved — written on quit and every 10 turns\n- Reconnecting with the same player name auto-resumes from `auto.json`\n\n---\n\n## The world\n\nThirteen rooms across three decks of the *Galactica*:\n\n- **Deck 5 (where you live):** Environmental Control, Corridor C-12,\n  Mess Hall, The Head\n- **Mid-deck:** Corridor B-7, Pilots' Rec Room, Hangar Deck,\n  Baltar's Lab\n- **Officer country:** Corridor A, Sickbay, Adama's Quarters, Brig,\n  Observation Deck, CIC\n\nGet to CIC. Get there with something in your pocket. Don't get spaced on\nthe way.\n\n### The people\n\nYou will meet, in no particular order: Colonel Tigh (XO, drinks \"water\"),\nCrewman Hadrian (your bunkmate, knows everyone's business), Admiral Adama\n(speaks in proverbs), Lieutenant Thrace (offers triad, arm wrestling, or\nmaking out — pick one), Captain Apollo (will mistake you for someone he has\nfeelings about), Doctor Baltar (already mid-conversation when you arrive — with\nwhom is unclear), A Number Six (your shift supervisor, probably), and\nPresident Roslin (will ask if you're hiding a prophecy; you might be).\n\n### The three endings\n\nThree ways for the day to go.\n\n- **HERO** — accidentally save the fleet by delivering a small, crumpled,\n  load-bearing piece of paper to the right person at the right time.\n- **SPACED** — learn too much about the XO's recreational habits and his\n  command-meeting calendar. Out the airlock. Tearfully.\n- **CYLON LOVE TRIANGLE** — get romantically entangled with your shift\n  supervisor. And the next one. They look the same. They hate each other.\n  You're fine with this.\n\n---\n\n## Hacking\n\n### Architecture\n\nClean engine/content split. `engine/` is generic interactive-fiction\nmachinery — no BSG references; reusable for any parser game. `content/` is\nall the BSG flavor.\n\n### Add a room\n\nAppend a `Room(...)` to `content/rooms.py` and `register_room(...)` it.\nDone. The engine discovers it via the registry.\n\n### Add an NPC\n\nDefine an `NPC(...)` with an `on_talk(world, topic)` callback in\n`content/npcs.py` and `register_npc(...)` it. Topics are matched against\nkeys in your dialogue dict.\n\n### Add an item\n\nSame pattern in `content/items.py`. Items can have `on_use`, `on_eat`,\n`on_drink` callbacks. Dynamic descriptions are supported by attaching a\n`_dynamic_description` function to the item (see the napkin).\n\n### Add ambient flavor\n\nAppend a line (or a callable taking `world`) to `content/flavor.py` and\nregister it in `install()`. The engine rolls a die every turn and prints\none at low probability.\n\n### Tests\n\nStdlib-only — no pytest required.\n\n```bash\npython3 run_tests.py            # all\npython3 run_tests.py parser     # one suite by substring\n```\n\nTests cover: parser, save/load atomicity (full WorldState round-trip),\nthe opening quest flow, every ending variant (Hero / Spaced via three\npaths / Cylon Love Triangle / Love Quadrangle / Forbidden Knowledge /\nDownload Complete), all five side quests start-to-completion, every\nNPC's default dialogue, ambient events on and off, first-visit / revisit\nroom logic, romance state machine, multiplayer LAN server (concurrent\nsessions, same-name refusal, idle timeout, CRLF handling), achievements\npersistence, balance tuning (Promotion Material reachability), the time\n+ duty + hunger systems, the hidden Cylon resurrection mechanic, the\nQuorum Press Conference minigame, and every code-critique fix to date.\n\nThe total count is reported by `run_tests.py` at the end of a run — see\nthat number for the current ground truth rather than a stale figure here.\n\n---\n\n## Known bugs\n\nSo say we all.\n\n---\n\n## Legal\n\n**Unofficial fan parody.** BSG Adventure is a non-commercial fan work. It is\nnot affiliated with, endorsed by, or sponsored by Universal/NBCUniversal.\n*Battlestar Galactica*, its characters, and related marks are the property of\ntheir respective owners. The original engine and game code in this repository\nare released under the MIT License — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkbennett2000%2Fbsg-adventure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkbennett2000%2Fbsg-adventure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkbennett2000%2Fbsg-adventure/lists"}