Skip to content

Repository files navigation

ctchargen

Status: Beta

A Go CLI that generates rules-accurate Classic Traveller characters.

Ruleset baseline: Books 1–3 only — the FFE reprints of the © 1977 text. Character generation is Book 1 pp. 4–25; Books 2 and 3 are consulted only where Book 1 points at them. Every implemented rule carries its printed-page cite, and every place the text is silent or ambiguous has a recorded reading.

Install

A binary, needing no Go toolchain. This is the line for an Apple-silicon Mac:

curl -Lo ctchargen https://github.com/philoserf/ctchargen/releases/download/v1.0.0-beta.2/ctchargen-v1.0.0-beta.2-darwin-arm64
chmod +x ctchargen

For another machine, substitute darwin-amd64, linux-amd64 or linux-arm64 in the filename. The tag is named rather than releases/latest, which resolves only to a non-prerelease and so resolves to nothing here. A checksums.txt is attached beside the binaries.

curl is what the line uses because a browser download is quarantined and needs xattr -d com.apple.quarantine ctchargen before it will run. The binaries are also unsigned, so a recent macOS may refuse one regardless — if it does, the tool is not broken; install it with Go instead:

go install github.com/philoserf/ctchargen/cmd/ctchargen@v1.0.0-beta.2

Go excludes prereleases from @latest, so a prerelease installs by name. That is the intended friction.

Do not install v1.0.0-alpha.1 or v1.0.0-alpha.2. Both predate the rebuild at 41a213a and are builds of a different implementation — they have a replay subcommand and flags this tool does not. Their release notes are accurate about the tag they head and describe nothing below.

From a clone:

go build ./cmd/ctchargen

Status

All six services generate, the book's own worked character (pp. 23–25) replays against the engine, the record has a schema, and characters can be written to files, generated in batches and read back. new without --auto walks the procedure a question at a time, showing each throw between the questions.

Every command lists its own flags, which is where the current set lives:

ctchargen --help          # the commands
ctchargen new --help      # one command's flags, with their values and defaults

Using it

Four commands. new generates one character; batch generates many from one base seed; render reads a record back as a sheet or as the transcript; and version writes the build.

ctchargen new --auto --seed 145 --service merchants --sheet
ctchargen new --auto --name "Alexander Jamison" -o jamison.json
ctchargen new --auto --history                  # the transcript, throw by throw
ctchargen new --seed 145 --sheet                # asks at every choice point

ctchargen batch --count 20 --auto --seed 145 --service merchants
ctchargen batch --count 20 --auto --seed 145 --service merchants -o characters/

ctchargen render characters/00000000000000000145.json
ctchargen render --history characters/00000000000000000145.json

Batch members number from zero, so the first of that batch is the character the first new above generated, and render shows the same sheet.

batch with no -o writes NDJSON to standard output, one record to the line, which is the shape that pipes — and it streams, so the first record reaches the pipe before the run finishes rather than after:

ctchargen batch --count 100 --auto --seed 145 | jq -r '[.upp, .service, .terms] | @tsv'

batch requires --auto, because it has nobody to ask.

A batch says what it did, on standard error so it cannot corrupt the JSONL:

$ ctchargen batch --count 100 --auto --seed 1000 --service scouts > roster.jsonl
100 written, 76 died

That is the rules working — the default serve strategy re-enlists until the service throws him out, so the survival throw eventually gets most of them. When what you want is twenty scouts for the starport rather than a hundred outcomes, --survivors passes over the dead and goes on to the next seed:

$ ctchargen batch --count 20 --auto --seed 1000 --service scouts --survivors > roster.jsonl
20 written, 68 passed over for dying

It does not reroll anybody. Each character written is still exactly the character his own seed makes, so new --seed <that> brings him back — the seeds are simply no longer consecutive.

A session that stops before the end offers the way back in. A half-built character is not a record, so it cannot be written out — but the seed and the answers already given are enough to walk back to the question you stopped on, and those are what a long session cannot retype:

$ ctchargen new --seed 7 --service other
...
Re-run the same command with --seed 7 --answers 1,2,1 to pick up at this question.

--answers replays those and then asks the next question as normal. It is the same command deliberately: an answer is a number in the list a question offered, so a re-run that drops --service or --name asks a different sequence and spends the answers on it. A list longer than the run has questions for is refused rather than half-applied, and --answers cannot be given with --auto, which answers every question itself.

Without --service the tool picks one, and with --auto that pick is the policy's rather than yours. The sheet says so on its headline, and so does the record. Naming a service does not guarantee you get it either: a failed enlistment throw sends the character to the draft, which may put him somewhere else entirely — the sheet says that too.

Three flags steer --auto where the procedure offers a choice, on both new and batch. docs/POLICY.md carries a row per choice point saying what each one does:

Flag Values
--career serve (default) · retire · oneterm
--skills advanced (default) · service · personal
--muster cash (default) · goods · spartan

Nothing is overwritten without --force. -o onto an existing file is refused, and so is a batch any of whose members would replace one — the whole batch, before a byte is written, so a refusal leaves the directory as it was.

Death is an outcome and not an error: a character killed by a survival throw (Book 1 p. 5) gets a complete record like anyone else, and no flag rerolls him.

The documents

File What it holds
docs/PRD.md The delivered v1 contract — historical, kept for why the tree has this shape.
docs/ERRATA.md Every recorded reading, with its page cite and its stamping condition.
docs/POLICY.md The --auto decision table, one row per choice point.
docs/COVERAGE.md Every implemented rule mapped to its page cite, its implementation and its test.
docs/character.schema.json What the tool writes, with a minimal and a complete example beside it.
docs/PRERELEASE.md What each tag ships with open, and the review that preceded it.
CLAUDE.md Authority, source precedence, and the working rules for agents.

The gate

task

go mod tidy -diff, go vet, golangci-lint, NilAway, go test -race, and a coverage ratchet that holds each package's count of uncovered statements, and prettier over the non-Go files. CI runs exactly this.

The toolchain is unpinned on purpose, and golangci-lint runs with default: all, so a linter added upstream arrives switched on. Formatting is gofumpt, run inside golangci-lint rather than beside it, so there is one definition of formatted rather than two that can disagree.

Licence

MIT.

About

Classic Traveller character generator (Books 1-3, 1977 text) — Go CLI

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages