2026-10-01 22:16:48 +02:00
2026-10-01 22:16:48 +02:00
2026-09-19 20:21:47 +02:00
2026-10-01 22:16:48 +02:00
2026-09-19 20:21:47 +02:00
fix
2026-09-20 14:16:41 +02:00
2026-10-01 22:16:48 +02:00
2026-10-01 22:16:48 +02:00
2026-09-19 20:21:47 +02:00
fix
2026-09-20 14:16:41 +02:00
fix
2026-09-20 14:16:41 +02:00
2026-10-01 22:16:48 +02:00
fix
2026-09-20 14:16:41 +02:00
2026-10-01 22:16:48 +02:00
fix
2026-09-20 14:16:41 +02:00
2026-10-01 22:16:48 +02:00
2026-09-19 20:21:47 +02:00
2026-10-01 22:16:48 +02:00
fix
2026-09-20 14:16:41 +02:00
fix
2026-09-20 14:16:41 +02:00
2026-10-01 22:16:48 +02:00
2026-10-01 22:16:48 +02:00
fix
2026-09-20 14:16:41 +02:00
fix
2026-09-20 14:16:41 +02:00
2026-09-19 20:21:47 +02:00
2026-10-01 22:16:48 +02:00

Halcyon

A once-a-day asteroid-belt game. Players cannot fly their ships: they write a program for the ship's flight computer, launch it, and then exchange 1 KB a day with it over a "wormhole" link. The belt is run once a day, deterministically.

  • docs/manual.md -- the HC-33 flight computer programmer's manual (source; served at /manual.md)
  • /manual.pdf -- the same, typeset separately (see below)
  • examples/programs/ -- sample ship programs (assembly)

Run it

go run ./cmd/server -db sqlite:halcyon.db            # API + website on :8080
go run ./cmd/daily  -db sqlite:halcyon.db            # run one day of the belt (once a day, e.g. from cron)

-db is sqlite:<path> or a postgres:// URL. The world parameters (-seed, -ticks-per-day, -asteroids, ...) or their HALCYON_* variables must be the same for server and daily, and cannot be changed for an existing database.

Open http://localhost:8080/, create an account (you are shown an access key once; it is your login), write a program, seal it into your ship, launch it, then run daily.

Docker

docker build -t halcyon .
docker run -d -p 8080:8080 -v whdata:/data halcyon                       # SQLite on a volume
docker run --rm -v whdata:/data halcyon daily                            # run one day of the belt
docker run -d -p 8080:8080 -e DATABASE_URL=postgres://user:pw@db/halcyon halcyon   # or Postgres
docker build --target test .                                             # run the tests
scripts/render-manual.sh                                                 # (re)render /manual.pdf, separately

With Compose, Caddy serves the site over HTTPS at sandbox.clearsky.dev (edit caddy/Caddyfile for another name; DNS must point at the host and ports 80/443 must be open). The app itself is also on http://localhost:8070, bound to localhost only. SQLite lives in the halcyon_data volume:

docker compose up -d --build
scripts/daily.sh          # one day's run via `docker exec`; schedule it once a day

The world settings are environment variables (HALCYON_SEED, HALCYON_TICKS_PER_DAY, HALCYON_CYCLES_PER_TICK, HALCYON_PROGRAM_BYTES, HALCYON_RAM_BYTES, HALCYON_COMM_BYTES, HALCYON_ASTEROIDS); flags of the same name override them. In Compose they are set once on the web service, so scripts/daily.sh always matches the server. Change them only before a world has run.

DATABASE_URL sets the database for both server and daily (default in the image: sqlite:/data/halcyon.db); the -db flag overrides it. The image also contains asm.

The manual PDF

docs/manual.md is rendered to a PDF: typeset with WeasyPrint, then made to look like a well-used ring-binder copy -- coffee rings, three binder holes, and several generations of photocopying. The PDF keeps a live text layer, bookmarks and links. The look is generated by docs/pdf/render.py and is deterministic; --intensity (default 0.5) sets how worn the copies look, and docs/pdf/check_legibility.py OCRs every page against the text layer to check the result.

Rendering takes a couple of minutes, so it is not part of the app image: deploys stay fast, and the PDF is rendered separately into a volume that the server reads on every request (a new render is live at once, no restart):

docker compose up -d --build                  # deploy the app (fast)
scripts/render-manual.sh > /tmp/render-manual.log 2>&1 &   # then re-render the PDF in the background
scripts/manual-pdf.sh                         # or: render to docs/generated/ for a locally run server

Until it has been rendered once, /manual.pdf is a 404 that says so. The Markdown source is always at /manual.md.

Admin console

/admin is a separate operator console: a list of accounts (ships, code slots, credits, last login and activity) and an audit log of what players did. It has its own login: one password, taken from the environment. With no password set the console is off.

echo 'HALCYON_ADMIN_PASSWORD=a-long-passphrase-of-12-or-more-characters' > .env   # git-ignored
docker compose up -d
  • Sessions: a signed, HttpOnly, SameSite=Strict cookie scoped to /admin, valid for 8 hours. Changing the password ends every session. Wrong guesses are rate-limited per client address (5 failures, then a 15-minute lockout). Use it over HTTPS (Caddy does that) so the cookie is marked Secure.
  • What is audited: register, login, code_save, code_clear, program_upload, launch and uplink, each with the account, time, client address and the specifics: the slot, name, size and SHA-256 of saved code; the slot or program launched; the full data of every uplink (at most 1 KB). Actions that fail are not recorded. A login is signing in with an access key through the site's login form (or POST /login); other requests are authenticated by the key alone. The admin can read the code an account has saved, but not change anything.
  • Map: the MAP tab is a top-down 2D chart of the belt: asteroids (coloured by dominant ore), the station, drifting containers and every ship, each with its velocity vector drawn as an arrow (longer = faster). Drag to pan, wheel or pinch to zoom, click an object for details, or pick a ship from the list to fly to it. Positions are those at the start of the current day and are served by GET /admin/api/map. Clicking a ship also draws the path it would coast along with its engine off from there, from GET /admin/api/map/orbit.
  • Retention: the log is never pruned. It is the audit_log table, so it can be queried or trimmed with SQL.

Layout

path what
fixed, rng deterministic Q32.32 maths and PRNG
vm the ship CPU and its assembler (cmd/asm)
world, sim, market belt generation, the daily run, ore prices
store, runner SQLite/Postgres persistence and the daily run
api, web, cmd/server HTTP API and the browser front end
examples, docs embedded sample programs and manual

Tests

go test ./... covers everything; set HALCYON_TEST_PG=postgres://... to also run the API test against PostgreSQL (it wipes that database's public schema).

S
Description
No description provided
Readme 370 KiB
Languages
Go 60.5%
JavaScript 30.1%
CSS 7.8%
Dockerfile 0.7%
Shell 0.5%
Other 0.4%