ctchargen
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
go install github.com/philoserf/ctchargen/cmd/ctchargen@v1.0.0-alpha.4
Go excludes prereleases from @latest, so an alpha 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:
ctchargen batch --count 100 --auto --seed 145 | jq -r '[.upp, .service, .terms] | @tsv'
batch requires --auto, because it has nobody to ask.
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. 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.