# 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 ```sh 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:` 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 , 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 ```sh 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 , bound to localhost only. SQLite lives in the `halcyon_data` volume: ```sh 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): ```sh 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. ```sh 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).