Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

96 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rtrav

A command-line character generator for Marc Miller's Classic Traveller (Book 1: Characters and Combat).

Given a fresh 18-year-old recruit, rtrav rolls a Universal Personality Profile (UPP), enlists or drafts the character into one of six careers, runs successive four-year terms (with survival checks, commissions, promotions, skill acquisition, and aging), then musters the character out with cash and equipment. The result is a fully detailed Traveller character sheet printed to stdout.


Features

  • Rolls a random UPP (2d6 per stat, displayed as a six-digit hex string)
  • Assigns a random gender and a random gender-aware personal name
  • Selects a career by user request, statistical best-fit, or draft
  • Simulates career terms: survival, commission, promotion, reenlistment, and aging
  • Awards skills from four career-specific tables (personal development, service, advanced education 1 & 2)
  • Caps skills at INT + EDU per Book 1 rules
  • Applies age-band stat penalties at ages 34, 50, and 66+, with an aging-crisis survival throw when a stat is reduced to 0 (Medical skill helps; failure is fatal)
  • Musters out with alternating cash and benefit rolls
  • Pays an annual retirement pension for 5+ terms of eligible service (Book 1)
  • Grants career rank titles (Ensign → Admiral, Lt → General, etc.)
  • Grants nobility titles for Social stat ≥ 11 (Knight/Dame through Duke/Duchess)
  • Generates multiple characters in one run
  • Lets you raise the minimum 1d6 result to skew luck
  • Reproducible generation via a --seed
  • Human-readable or JSON output (--format), plus optional PDF export (one formatted sheet per page)
  • Structured logging (on stderr) via RUST_LOG for per-roll trace output

Requirements

  • Rust toolchain (stable, 2024 edition) — install via rustup

Build & Run

cargo build                  # debug build
cargo build --release        # release build (opt-level 3, panic=abort)
cargo run -- [OPTIONS]       # run character generator
cargo test                   # run all tests
cargo clippy                 # lint

CLI Reference

A set of tools for Marc Miller's Classic Traveller RPG

Usage: rtrav [OPTIONS]

Options:
      --career <CAREER>                          Career selection [default: random]
  -c, --count <COUNT>                            Number of characters to generate [default: 1]
  -m, --min <MIN>                                Minimum die roll for random 1d6 [default: 1]
      --species <NAME:STR,DEX,END,INT,EDU,SOC>   Optional species definition
      --pdf [<PATH>]                             Write characters to a PDF [default when given: rtrav.pdf]
      --seed <SEED>                              Seed the RNG for reproducible generation
      --format <FORMAT>                          Output format: text or json [default: text]
  -h, --help                                     Print help
  -V, --version                                  Print version

Flags

Flag Short Default Description
--career <CAREER> random Career to attempt enlistment in
--count <N> -c 1 Number of characters to generate
--min <1-6> -m 1 Floor for all simulated 1d6 rolls
--species <NAME:STR,DEX,END,INT,EDU,SOC> human (none) Species name plus six UPP modifiers (STR,DEX,END,INT,EDU,SOC), added/subtracted from the UPP after it's rolled. The name is printed under the character's summary line.
--pdf [<PATH>] off; rtrav.pdf when flag is bare Also write every generated character to a PDF, one formatted sheet per page. Give a path (--pdf sheets.pdf) or use the bare flag to write rtrav.pdf in the current directory. Omit the flag entirely for stdout-only output.
--seed <N> OS-random Seed the RNG with a u64 for reproducible output: the same seed (with the same other flags) regenerates the exact same character(s) on any machine. With --count, all N characters share the one seeded stream, so the whole run is reproducible. Omit for OS-random generation.
--format <text|json> text stdout format. text prints human-readable character summaries; json prints a single JSON array of all generated characters (ideal for piping into other tools). Logs always go to stderr, so --format json yields clean, valid JSON on stdout. --pdf is independent and works with either.
--help -h Print help and exit
--version -V Print version and exit

Career values

Value Notes
Navy Enlistment favors INT ≥ 8 / EDU ≥ 9
Marines Enlistment favors STR ≥ 8 / END ≥ 8
Army Enlistment favors DEX ≥ 6 / END ≥ 5
Scouts Enlistment favors INT ≥ 6 / STR ≥ 8
Merchants Enlistment favors STR ≥ 7 / INT ≥ 6
other No enlistment check — always accepted
random Picks statistically best-fit career, then attempts enlistment

Failed enlistment falls back to the draft (1d6 → career).

Examples

# Generate three random characters
cargo run -- --count 3

# Attempt Navy enlistment
cargo run -- --career Navy

# Marines with lucky dice (minimum roll of 3)
cargo run -- --career Marines --min 3

# Watch every die roll
RUST_LOG=debug cargo run -- --career Scouts

# Non-human character: a Vargr with +1 STR, -1 DEX, +2 INT, -2 SOC
cargo run -- --species "Vargr:1,-1,0,2,0,-2"

# Export three characters to a PDF (one sheet per page)
cargo run -- --count 3 --pdf party.pdf

# Bare flag writes rtrav.pdf in the current directory
cargo run -- --pdf

# Reproducible run: the same seed regenerates the same character(s)
cargo run -- --seed 999 --career Scouts

# Emit JSON (logs go to stderr, so stdout stays valid JSON)
cargo run -- --count 5 --format json 2>/dev/null | jq .

Logging

Set RUST_LOG to see per-roll detail. Logs are written to stderr, so they never interfere with character output on stdout (notably --format json):

RUST_LOG=debug cargo run    # shows each roll and check
RUST_LOG=trace cargo run    # maximum verbosity

Character Generation Flow

flowchart TD
    A([Start]) --> B[Roll UPP\n2d6 per stat]
    B --> C[Assign Gender]
    C --> D{--career flag?}

    D -- named career --> E[Attempt Enlistment]
    D -- random --> F[rank_career\npick best-fit]
    F --> E

    E -- success --> G[Serve Career]
    E -- fail --> H[Draft\n1d6 → career]
    H --> G

    G --> I{Term Loop}

    I --> J[Survival Roll\n2d6 vs threshold]
    J -- fail --> K[Term ends early\nfailed_survival = true]
    J -- pass --> L{Rank < 1?}

    L -- yes --> M[Commission Roll]
    L -- no --> N{Rank 1-5?}
    M --> N

    N -- yes --> O[Promotion Roll]
    N -- no --> P[Skill Rolls]
    O --> P

    P --> Q[Aging Check\nif age crosses band]
    Q --> Q1{Stat reduced to 0?}
    Q1 -- yes --> Q2[Aging Crisis\n8+ or death, Medical DM]
    Q2 -- died\nmark DECEASED --> S[Muster Out]
    Q2 -- survived --> R{Reenlist?}
    Q1 -- no --> R

    R -- yes, or forced --> I
    R -- no / retired --> S
    K --> S

    S --> T[Cash Rolls\nGambling +1]
    S --> U[Benefit Rolls\nRank > 4: +1]
    S --> R2[Retirement Pay\n5+ terms, eligible careers]
    T --> V([Output Character\ntext or JSON])
    U --> V
    R2 --> V
    V --> W{--pdf flag?}
    W -- yes --> X([Write PDF\none page per character])
    W -- no --> Y([Done])
Loading

Architecture

graph LR
    main["main.rs\nCLI parsing · throw_die\nentry loop"]
    character["character.rs\nUPP · Character · Term\nGender · StatKind\nAgingEffects · Hits"]
    career["career.rs\nCareer trait · TermParameters\nMusterOut trait\nBENEFITS · CASH tables"]
    skill["skill.rs\nSkill enum\nthrow_for_skill\nPersonal Dev / Service /\nAdv Education tables"]
    equipment["equipment.rs\nGearTypes enum\nGearUtils trait"]
    pdf["pdf.rs\nwrite_characters_pdf\nformatted PDF sheets"]

    main --> character
    main --> career
    main --> pdf
    career --> character
    career --> equipment
    skill --> character
    skill --> career
    equipment --> character
    pdf --> character
Loading

Modules

Module Responsibility
main.rs Parses CLI args, owns the RNG, runs the outer character loop. All die rolls funnel through throw_die(rng, min) — the single place that knows how a 1d6 is actually thrown.
character.rs Defines UPP (six stats), Character (full state aggregate), StatKind (typed stat delta), AgingEffects (age-band stat loss), and Hits (STR+DEX+END threshold).
career.rs Career selection (Career trait on UPP), term simulation (generate_term_impl + per-career TermParameters), and mustering out (MusterOut trait). BENEFITS and CASH are [CareerType][throw-1] lookup tables — keep CareerType variant order in sync with them.
skill.rs Four 6×6 skill tables (rows = career, columns = 1d6 result). throw_for_skill selects the table via a first 1d6 roll, then indexes it. Advanced Education 2 requires EDU > 7.
equipment.rs GearTypes enum and GearUtils::add_equipment, which increments a count in Character.equipment.
pdf.rs write_characters_pdf renders the generated characters to a formatted PDF (one page each) using printpdf with the built-in Helvetica font — no external font assets. Reuses Character::display_name() so the sheet heading matches the stdout printout.

The term loop in generate_career pulls a fixed-size batch of die throws up front per term and passes them by index — match this pattern when extending term logic rather than reaching for the RNG mid-calculation.


Data Schemas

classDiagram
    class UPP {
        +u32 strength
        +u32 dexterity
        +u32 endurance
        +u32 intelligence
        +u32 education
        +u32 social
        +random_upp(rng, min) UPP
        +Display: 6-hex-digit string
    }

    class Character {
        +String name
        +UPP upp
        +HashMap~Skill,u8~ skills
        +Vec~Term~ terms
        +i32 hits
        +i32 cash
        +i32 pension
        +u32 age
        +HashMap~GearTypes,u8~ equipment
        +CareerType prior_service
        +u32 rank
        +bool retired
        +bool dead
        +Gender gender
        +bool failed_survival
        +printout()
        +nobility_title() Option~String~
        +describe_rank() Option~String~
        +retirement_pay() i32
    }

    class Term {
        +u32 term_num
        +u32 length
        +CareerType career
        +u32 rank
        +bool fail
        +bool reenlist
        +bool commissioned
        +bool promoted
    }

    Character "1" *-- "1" UPP
    Character "1" *-- "0..*" Term
Loading

UPP — Universal Personality Profile

Each of the six stats is rolled as 2d6 (range 2–12) and displayed as a hex digit (0–9, A–F). The string "A87556" means STR=10, DEX=8, END=7, INT=5, EDU=5, SOC=6.

Stat Abbr Role
Strength STR Physical power; melee, labor
Dexterity DEX Coordination; ranged combat
Endurance END Stamina; physical checks
Intelligence INT Reasoning; sets skill cap (with EDU)
Education EDU Formal learning; sets skill cap (with INT)
Social SOC Standing; nobility at 11+

Character

hits (STR + DEX + END) must remain ≥ 10 for the character to continue serving. age starts at 18 and increments by Term.length each term. name is a random gender-aware personal name. pension is the annual retirement pay in Credits (0 if not eligible; see Mustering Out). dead is set if the character dies in an aging crisis (see Aging).

Term

length is 4 years for a completed term, 2 years if the survival roll fails. Terms are appended to Character.terms in order, giving a full service history.

TermParameters (internal)

Each career has a TermParameters struct controlling ten values that drive generate_term_impl:

Field Meaning
survival_attribute Which UPP stat is checked for survival
survival 2d6 threshold to pass survival
survival_threshold Stat value granting +2 to survival roll
commission_attribute UPP stat checked for commission
commission 2d6 threshold for commission
commission_threshold Stat value granting +1 to commission roll
promotion_attribute UPP stat checked for promotion
promotion 2d6 threshold for promotion
promotion_threshold Stat value granting +1 to promotion roll
reenlist 2d6 threshold to reenlist voluntarily

Skills

Skill List

Administration AirRaft BladeCombat Brawling
Bribery Computer Electronics Engineering
Forgery ForwardObserver Gambling GunCombat
Gunnery JackOfAllTrades Leader Mechanical
Medical Navigation Pilot ShipsBoat
Steward Streetwise Tactics VaccSuit
Vehicle

Skills are stored as HashMap<Skill, u8> — the value is the skill level (0-based, increments on repeat acquisition). Total skills are capped at INT + EDU.

Skill Tables

Each term, one skill roll is made (Scouts get two). A first 1d6 selects the table; a second 1d6 selects the skill within it:

1d6 Table selected
1–2 Personal Development — stat increases or combat skills
3–4 Service Skills — career-specific baseline skills
5 Advanced Education 1 — technical/tactical skills
6 Advanced Education 2 (EDU > 7 required) — high-end professional skills

If EDU ≤ 7 and a 6 is rolled, Advanced Education 1 is used instead. See src/skill.rs for the full 6×6 tables per career.


Equipment & Mustering Out

GearTypes

Gear Notes
LowPassage Steerage-class interstellar travel
MidPassage Middle-class interstellar travel
HighPassage First-class interstellar travel
Blade Melee weapon
Gun Ranged weapon
TravellersAid Travellers' Aid Society membership
ScoutShip Free access to a Scout/Courier vessel
FreeTrader Shares in a Free Trader

Mustering-Out Rules

After the career ends, the character alternates between cash rolls and benefit rolls — one roll per term served, plus one bonus roll per five terms:

  • Cash rolls: indexed into a per-career CASH[career][throw-1] table (1d6). Having Gambling skill adds +1 to the roll.
  • Benefit rolls: indexed into a per-career BENEFITS[career][throw-1] table (1d6). Rank > 4 adds +1 to the roll. Results are either a stat bonus (StatKind delta) or equipment (GearTypes).

Retirement Pay

Characters who serve five or more terms in the Navy, Marines, Army, or Merchants draw an annual pension (Scouts and Other are not eligible), per Book 1:

Terms served Annual pension
5 Cr 4,000
6 Cr 6,000
7 Cr 8,000
8 Cr 10,000
9+ +Cr 2,000 per additional term

The pension is stored on Character.pension and shown as a Pension line in the printout, PDF, and JSON output.


Aging

At the end of each term where the character crosses an age threshold, stat penalties are applied via AgingEffects:

Age band Stats affected 2d6 throw to avoid −1
34, 38, 42, 46 STR, DEX, END 8, 7, 8
50, 54, 58, 62 STR, DEX, END 9, 8, 9
66+ (every 4 yrs) STR, DEX, END 9, 9, 9 (−2 each if threshold missed)

All stat reductions saturate at 0. Aging crisis: if aging reduces a stat (STR, DEX, END, or INT) to 0, the character must make a survival throw of 8+ to avoid death — the character's Medical skill level is added as a DM. On success the stat is restored to 1; on failure the character dies (Character.dead is set and a DECEASED marker appears in the output). Separately, if hits (STR+DEX+END) drops below 10, the character is too frail to continue serving (a non-fatal stop).


Rank & Nobility Titles

Career Rank Titles

Rank Army Marines Navy Merchants
1 Lieutenant Lieutenant Ensign 4th Officer
2 Captain Captain Lieutenant 3rd Officer
3 Major Force Commander Lt Commander 2nd Officer
4 Lt Colonel Lt Colonel Commander 1st Officer
5 Colonel Colonel Captain Captain
6 General Brigadier Admiral Captain

Scouts and Other careers do not have rank titles.

Nobility Titles (Social ≥ 11)

SOC Female Male
11 Dame Knight
12 Baroness Baron
13 Marquesa Marquis
14 Countess Count
15 Duchess Duke

PDF Export

By default rtrav prints each character to stdout only. Pass --pdf to also write a PDF, with one formatted character sheet per page:

cargo run -- --count 3 --pdf party.pdf   # write three sheets to party.pdf
cargo run -- --pdf                        # bare flag writes rtrav.pdf in the CWD

Each page contains:

  • the character's name / rank line (bold heading, same text as the stdout printout), plus a DECEASED marker if the character died in an aging crisis
  • species and gender
  • the UPP hex string plus a labeled STR/DEX/END/INT/EDU/SOC breakdown
  • age, term count, hits, and cash
  • the annual pension, if the character earned one
  • an alphabetized skill list (Skill-level), word-wrapped to fit the page
  • an alphabetized equipment list (Item xN)

The PDF is generated with printpdf using the built-in Helvetica font, so no external font files are bundled or required. Running without the flag leaves behavior unchanged (stdout only).


Screenshot

Screenshot


License

rtrav is free software distributed under the GNU General Public License v3. See COPYING.md for the full license text.

About

A command-line character generator for Marc Miller's Classic Traveller (Book 1: Characters and Combat).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages