ctchargen

module
v1.0.0-beta.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: MIT

README

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

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.1/ctchargen-v1.0.0-beta.1-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.1

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 — 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. 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.

Directories

Path Synopsis
Package chargen walks Book 1's character generation procedure, pp.
Package chargen walks Book 1's character generation procedure, pp.
cmd
ctchargen command
Command ctchargen generates rules-accurate Classic Traveller characters from Book 1 pp.
Command ctchargen generates rules-accurate Classic Traveller characters from Book 1 pp.
Package dice draws the die rolls Classic Traveller's character generation procedure calls for, from a seeded stream.
Package dice draws the die rolls Classic Traveller's character generation procedure calls for, from a seeded stream.
Package render projects a generated character into the shapes a reader wants: JSON, a character sheet in the book's own style, and a transcript of the generation record.
Package render projects a generated character into the shapes a reader wants: JSON, a character sheet in the book's own style, and a transcript of the generation record.
Package rules holds every table of Book 1 pp.
Package rules holds every table of Book 1 pp.
tools
ratchet command
Command ratchet holds each package's count of uncovered statements at or below a checked-in number.
Command ratchet holds each package's count of uncovered statements at or below a checked-in number.
Package traveller holds the domain of Classic Traveller character generation: the alphabets Book 1 prints, the values it works in, and the sums that say "exactly one of".
Package traveller holds the domain of Classic Traveller character generation: the alphabets Book 1 prints, the values it works in, and the sums that say "exactly one of".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL