Skip to content

athrane/herodotus

Repository files navigation

Herodotus

A procedural world-building and history generation tool written in TypeScript. The project simulates the creation and evolution of civilizations, geographic features, and historical events using an Entity-Component-System (ECS) architecture.

Development

This section provides an overview of the project's architecture and class structure for developers.

Scripts

All available npm scripts and what they do. Commands below are for PowerShell.

Build Commands

# Primary build - bundles src/main.ts to dist/bundle.js
npm run build

# GUI build - bundles src/mainGUI.ts to dist/bundle-gui.js  
npm run build:gui

Build time: ~40-50ms typically Build output: dist/bundle.js (~218KB) or dist/bundle-gui.js

Development & Execution

# Run main CLI simulation directly (development)
npm run dev
# OR equivalently:
npm run start:main
npm run start:ts

# Run interactive GUI
npm run start:gui

# Run built bundle (production-like)
npm start  # runs dist/bundle.js via Node

Testing & Validation

# Run all tests (typically ~15 seconds)
npm run test

# Lint code (must pass for commits)
npm run lint

# Fix auto-fixable lint issues
npm run lint:fix

# TypeScript type checking (no compilation)
npm run typecheck

Validation Sequence (ALWAYS run before committing)

  1. npm run typecheck - validates TypeScript types
  2. npm run lint - validates code style and catches errors
  3. npm run test - validates functionality
  4. npm run build - validates production bundle creation

Pre-commit Hooks

  • Husky automatically runs on git commit:
    1. lint-staged - lints only staged files with auto-fix
    2. npm run typecheck - validates types across entire project
  • If any pre-commit step fails, the commit is rejected

Common Build Issues & Solutions

  • ESLint errors: Run npm run lint:fix to auto-fix many issues
  • TypeScript errors: Check for missing imports, type mismatches
  • Module resolution: Ensure ES module syntax (import/export) is used
  • Test timeouts: Tests run in Node environment, typically complete in ~15s

Text-Based GUI

The Herodotus simulation includes an interactive text-based GUI built on an Entity-Component-System (ECS) architecture. Each screen is implemented as an entity with components, providing a modular and extensible interface for controlling your player character and making decisions that shape history.

Features

The ECS-based GUI provides the following functionality:

  • Interactive Decision Making: Make choices for your player character when decisions arise
  • Real-time Simulation Control: View live simulation updates with automatic screen refresh
  • Modular Screen System: Each screen is a separate entity with its own components
  • Chronicle Viewing: Review recent historical events and decisions

Running the GUI

To start the interactive GUI version of the simulation:

npm run start:gui

Alternatively, you can run it directly with tsx:

tsx src/mainGUI.ts --gui

Interface Layout

The GUI features a clean, fixed-layout interface that refreshes every 2 seconds:

============================================================
Year: 0035 | Simulation: Running | Player: Player Character | Status: No pending decisions | Herodotus 1.0.0
============================================================

============================================================

*** DECISION REQUIRED *** (when applicable)
[1] Economic Reform
    Type: Economic
    /* Lines 191-192 omitted */
    → Your choice will affect the kingdom's prosperity...

[2] Maintain Status Quo
    Type: Economic  
    /* Lines 196-197 omitted */
    → The situation may worsen, but no disruption...

============================================================
Commands: [H]elp [S]tatus [C]hoices Ch[r]onicle [Q]uit
============================================================

ECS Architecture

The GUI system is built using the Entity-Component-System (ECS) pattern, where each screen is implemented as an entity with specific components:

Components

IsActiveComponent (src/gui/IsActiveComponent.ts)

  • Marks an entity as the currently active screen
  • Only one entity should have this component at a time
  • Used by the ScreenRenderSystem to determine which screen to render

ScreenComponent (src/gui/ScreenComponent.ts)

  • Contains the render logic and input handling for a screen
  • Takes two functions: renderFunction and handleInputFunction
  • Provides render() and handleInput() methods

Systems

ScreenRenderSystem (src/gui/ScreenRenderSystem.ts)

  • System responsible for rendering the currently active screen
  • Key methods:
    • renderActiveScreen(): Renders the entity with IsActiveComponent
    • handleActiveScreenInput(): Routes input to the active screen
    • setActiveScreen(): Changes which screen is active

Screen Entities

Each screen is implemented as a separate entity with appropriate components:

  • MainInterfaceScreen: Main menu with navigation options and pending choices
  • StatusScreen: Displays detailed simulation status and historical figures
  • ChoicesScreen: Handles choice selection and validation
  • ChronicleScreen: Displays recent historical events from the chronicle

Management Classes

GuiEcsManager (src/gui/GuiEcsManager.ts)

  • Manages a separate ECS instance specifically for GUI components and systems
  • Creates and manages all screen entities with their components
  • Maps screen names to entity IDs and handles screen initialization
  • Provides independent update frequency from the simulation

TextBasedGui (src/gui/TextBasedGui.ts)

  • Advanced GUI class using separate ECS architecture for screen management
  • Uses its own ECS instance decoupled from the simulation ECS
  • Handles global navigation commands with independent update frequencies
  • Manages GUI systems through GuiEcsManager for responsive UI

Architecture Benefits

  1. Modularity: Each screen is a separate entity with its own components
  2. Extensibility: Easy to add new screens by creating new entities
  3. Consistency: All screens follow the same ECS pattern
  4. Separation of Concerns: Render logic separated from input handling
  5. Testability: Individual screens can be tested in isolation

Key Menu Systems

MainControllerSystem (src/gui/controller/MainControllerSystem.ts)

MainControllerSystem (renamed from ActionSystem) is responsible for processing UI action IDs and managing application flow for the GUI ECS.

Core Functionality:

  • Processes actions from the ActionQueueComponent in FIFO order
  • Handles screen navigation actions (e.g., NAV_MAIN, NAV_STATUS, NAV_CHOICES, NAV_CHRONICLE) and choice selection actions (CHOICE_SELECT_<index>)
  • Delegates screen switching operations to setActiveScreen()

Supported Actions:

  • Navigation: NAV_MAIN, NAV_STATUS, NAV_CHOICES, NAV_CHRONICLE
  • Choice selection: CHOICE_SELECT_<index> (mapped to choice handling in the simulation ChoiceComponent)
  • Extensible: New actions can be added by extending the switch/dispatch logic

Architecture:

  • Queries entities with ActionQueueComponent to find pending actions
  • Processes actions sequentially from the queue and clears the queue to avoid reprocessing
  • Delegates screen switching via internal setActiveScreen() method
  • Handles unknown actions gracefully (no-op)

Note: Application termination (quit) is handled directly by TextBasedGui key input processing, not through the controller system.

Test Coverage:

  • Covered by unit tests that exercise navigation, queue handling, and menu selection behavior
  • Tests include edge cases such as rapid successive actions, large queues, and malformed actions; they verify FIFO processing and queue cleanup

InputSystem (src/gui/view/InputSystem.ts)

The InputSystem handles user input processing for menu navigation and interaction:

Input Support:

  • Navigation: A/D keys ('a'/'d') and arrow keys ('left'/'right') for menu item selection (was previously W/S)
  • Selection: 'enter' key to activate the selected menu item
  • Wrap-around: Automatic cycling between first and last menu items

Core Functionality:

  • Queries entities with both MenuComponent and InputComponent
  • Processes navigation input by calling selectPrevious()/selectNext() on MenuComponent
  • Handles selection input by adding the selected item's action to ActionQueueComponent
  • Maintains menu state and selection index through MenuComponent

Integration:

  • Works with MenuComponent for menu state management
  • Integrates with ActionQueueComponent for action dispatching
  • Filters entities using IsVisibleComponent to ensure only active menus respond
  • Coordinates with ActionSystem for downstream action processing

Test Coverage:

  • Extensive test suite covering navigation, selection, and edge cases
  • Tests single-item menus, many-item navigation patterns
  • Verifies wrap-around behavior and state preservation
  • Includes rapid input processing and error handling scenarios

Scrollable Choice Menu

This project now includes a scrollable choice menu used by the main interface to present player choices. Key points:

  • Displays only 3 choices at a time and automatically scrolls to keep the selected item visible.
  • Navigation: A/D keys or Left/Right arrow keys to move between choices; Enter to select.
  • Choices are read from the player's ChoiceComponent (simulation ECS) and converted to menu items by ChoiceMenuViewSystem.
  • Each visible choice is shown with a numbered shortcut ([1], [2], [3]) for quick selection; selection actions are emitted as CHOICE_SELECT_<index>.
  • Rendering is handled by ScrollableMenuTextUpdateSystem, which shows headers, numbered lines, selection highlight, and scroll indicators.
  • Implementation files: src/gui/menu/ScrollableMenuComponent.ts, src/gui/menu/ScrollableMenuTextUpdateSystem.ts, src/gui/view/ChoiceMenuViewSystem.ts.
  • Tests cover scrolling logic, formatting, and integration with ChoiceComponent.

Architecture Benefits

Both systems follow the Entity-Component-System pattern providing:

  1. Modularity: Each system has a single, well-defined responsibility
  2. Testability: Systems can be tested in isolation with mocked components
  3. Extensibility: New actions and input types can be added easily
  4. Performance: Efficient querying using component filters
  5. Reliability: Comprehensive test coverage ensures stable behavior

Available Commands

The GUI accepts both full commands and single-letter shortcuts:

  • [H]elp / h - Show available commands and interface help
  • [S]tatus / s - Display detailed simulation status
  • [C]hoices / c - View and make decisions for current choices
  • Ch[r]onicle / r - Display recent historical events (last 10)
  • [Q]uit / q - Exit the simulation

Navigation note: The main menu now uses the A and D keys for scrolling between menu items (or left/right arrow keys); previous W/S navigation has been replaced.

How It Works

ECS Integration

The GUI integrates seamlessly with the existing ECS architecture:

  • Each screen is an entity with ScreenComponent and optionally IsActiveComponent
  • The ScreenRenderSystem queries for active screen entities
  • Screen entities can be created, modified, and destroyed dynamically
  • All screen logic is encapsulated in their respective component functions The header shows all essential information in one line:
  • Year: Current simulation year (zero-padded to 4 digits)
  • Simulation: Running/Stopped status
  • Player: Character name
  • Status: Number of pending decisions or "No pending decisions"
  • App Title: "Herodotus 1.0.0"

Player Integration

The simulation creates a dedicated player entity with:

  • PlayerComponent - Marks the entity as player-controlled
  • ChoiceComponent - Contains available choices when decisions are needed
  • DataSetEventComponent - Holds the current event/decision state
  • HistoricalFigureComponent - Contains character information

Decision Making Process

  1. The DilemmaSystem generates choices and adds them to the player's ChoiceComponent
  2. The GUI automatically displays pending choices in the main interface
  3. Player uses c or choices command to access the decision screen
  4. Player selects a choice by number (1-N)
  5. The choice is set in the player's DataSetEventComponent
  6. The DilemmaResolutionSystem processes the choice and records it in the chronicle

Automatic Updates

  • The GUI refreshes the display every 2 seconds automatically
  • No scrolling - the screen clears and redraws for clean presentation
  • Pending decisions appear immediately in the main interface
  • All commands return to the main screen after completion

Architecture

Key Files

  • src/gui/TextBasedGui.ts - Advanced GUI implementation using separate ECS architecture
  • src/gui/GuiEcsManager.ts - Manages separate GUI ECS instance, systems, and screen entities
  • src/gui/ScreenRenderSystem.ts - System for rendering active screens
  • src/gui/IsActiveComponent.ts - Component marking the active screen
  • src/gui/ScreenComponent.ts - Component containing screen logic
  • src/gui/menu/ActionComponent.ts - Component that holds an action identifier for menu items
  • src/gui/view/InputComponent.ts - Component that stores the last user input for menu screens
  • src/gui/screens/ - Directory containing individual screen implementations
  • src/mainGUI.ts - Entry point for GUI mode

Class Documentation

This section lists exported classes found in the codebase grouped by area (file path shown) with a short description.

Core ECS

  • src/ecs/Component.ts — Component base class (marker for entity components)
  • src/ecs/System.ts — Base System class (logic executed against entities)
  • src/ecs/FilteredSystem.ts — Extended System class with automatic entity filtering capabilities; eliminates boilerplate code by pre-filtering entities before processing
  • src/ecs/EntityFilters.ts — Utility class providing reusable entity filter functions (byName, hasComponent, lacksComponent) with combinators (and, or, not)
  • src/ecs/SystemManager.ts — Manages systems lifecycle and registration
  • src/ecs/Entity.ts — Entity abstraction (id and components)
  • src/ecs/EntityManager.ts — Manages entities and their components
  • src/ecs/Ecs.ts — High-level ECS facade (factory and orchestration)
  • src/ecs/NameComponent.ts — Component storing a human-readable name
  • src/ecs/PlayerComponent.ts — Marks an entity as the player
FilteredSystem Architecture

The Herodotus project includes an advanced system architecture that eliminates boilerplate code when systems need to process only specific entities. This is implemented through the FilteredSystem class and EntityFilters utility.

FilteredSystem Benefits:

  • Eliminates Boilerplate: No more manual entity filtering in processEntity() methods
  • Reusable Filters: Common filters like name-based filtering are standardized
  • Composable: Multiple filters can be combined using and(), or(), and not() combinators
  • Type Safety: Full TypeScript support with proper inheritance from base System class
  • Backward Compatible: Existing systems continue to work unchanged

Core Classes:

  • src/ecs/FilteredSystem.ts — Abstract system class that accepts an EntityFilter function and automatically filters entities before calling processFilteredEntity()
  • src/ecs/EntityFilters.ts — Static utility class providing common filter functions and combinators

FilteredSystem Usage Example:

// Before: Manual filtering with boilerplate
class MySystem extends System {
    processEntity(entity: Entity): void {
        const nameComponent = this.entityManager.getComponent(entity, NameComponent);
        if (nameComponent?.name !== 'SpecificScreen') {
            return; // Boilerplate filtering code
        }
        // Actual processing logic...
    }
}

// After: Automatic filtering, no boilerplate
class MySystem extends FilteredSystem {
    constructor(entityManager: EntityManager) {
        super(entityManager, EntityFilters.byName('SpecificScreen'));
    }
    
    processFilteredEntity(entity: Entity): void {
        // Only actual processing logic - filtering handled automatically
    }
}

Available Entity Filters:

  • EntityFilters.byName(name) — Filter entities with specific NameComponent name
  • EntityFilters.hasComponent(ComponentClass) — Filter entities containing a specific component
  • EntityFilters.lacksComponent(ComponentClass) — Filter entities missing a specific component
  • EntityFilters.and(filter1, filter2, ...) — Combine filters with AND logic
  • EntityFilters.or(filter1, filter2, ...) — Combine filters with OR logic
  • EntityFilters.not(filter) — Invert a filter with NOT logic

Filter Composition Example:

// Complex filtering: entities named 'Player' that have ChoiceComponent but lack TimeComponent
const complexFilter = EntityFilters.and(
    EntityFilters.byName('Player'),
    EntityFilters.hasComponent(ChoiceComponent),
    EntityFilters.not(EntityFilters.hasComponent(TimeComponent))
);

class ComplexSystem extends FilteredSystem {
    constructor(entityManager: EntityManager) {
        super(entityManager, complexFilter);
    }
    
    processFilteredEntity(entity: Entity): void {
        // Only processes entities matching the complex filter
    }
}

Implementation Examples:

  • src/gui/view/ChronicleViewSystem.ts — Uses EntityFilters.byName('ChronicleScreen') to process only chronicle screen entities
  • src/gui/view/ChoiceMenuViewSystem.ts — Uses EntityFilters.byName('ChoicesScreen') to populate choice menu only for choice screen entities

Architecture Integration:

  • FilteredSystem maintains full compatibility with existing System base class
  • Uses static factory method createFilteredSystem() to avoid inheritance conflicts
  • Integrates seamlessly with SystemManager and existing ECS infrastructure
  • Extensive test coverage ensures reliability and proper error handling
Builder Pattern Classes

The Herodotus project uses the Builder pattern to construct complex ECS instances with proper dependency management and construction order. The builder architecture provides a structured way to create simulation and GUI instances with all required components, systems, and entities.

Core Builder Classes:

  • src/ecs/builder/Builder.ts — Abstract base class defining the builder interface for ECS construction with methods for building entities, systems, components, and data
  • src/ecs/builder/BuilderDirector.ts — Director class that orchestrates the building process by coordinating the execution order of builder methods (build(), buildData(), buildComponents(), buildSystems(), buildEntities())

Simulation Builder:

  • src/simulation/builder/SimulationBuilder.ts — Concrete builder implementation for creating simulation ECS instances with world generation, historical figures, time systems, player entities, and choice management

GUI Builder:

  • src/gui/builder/GuiBuilder.ts — Concrete builder implementation for creating GUI ECS instances with screen management, input handling, rendering systems, and menu components that integrate with a simulation ECS instance

Architecture Benefits:

  • Separation of Concerns: Each builder handles construction of a specific domain (simulation vs. GUI)
  • Proper Initialization Order: Director ensures components are built before systems that depend on them
  • Dependency Injection: GUI builder accepts simulation ECS instance for integration
  • Extensibility: New builders can be created by extending the abstract Builder class
  • Testability: Individual builders can be tested in isolation with mock dependencies

Usage Example:

// Create simulation
const simBuilder = SimulationBuilder.create();
simBuilder.build();
simBuilder.buildData();
simBuilder.buildComponents();
simBuilder.buildSystems();
simBuilder.buildEntities();
const simulationEcs = simBuilder.getEcs();

// Create GUI with simulation integration
const guiBuilder = GuiBuilder.create(simulationEcs);
const guiDirector = BuilderDirector.create(guiBuilder);
const guiEcs = guiDirector.build();
Component matching and inheritance

Entity component queries now use instanceof semantics with an exact-match preference:

  • Entity.hasComponent(Base) returns true if the entity has a Base component or any subclass instance.
  • Entity.getComponent(Base) first returns an exact Base instance when present; otherwise it returns the first component that is an instance of Base (e.g., a subclass).
  • Systems that declare requiredComponents: [Base] will process entities with Base or any subclass (e.g., SubBase extends Base). If you need stricter filtering, add a guard inside processEntity().

GUI / Rendering

  • src/gui/TextBasedGui.ts — Text-based GUI main class (interactive mode entry)
  • src/gui/GuiEcsManager.ts — Manages GUI-specific ECS instance, screens and systems
  • src/gui/GuiHelper.ts — Stateless utility class providing common GUI functionality including debug entity creation and debug text posting for both ECS and non-ECS implementations
Rendering primitives
  • src/gui/rendering/TextComponent.ts — Static text display component
  • src/gui/rendering/DynamicTextComponent.ts — Runtime-generated text via callback; uses strategy pattern with functions accepting GUI and simulation entity managers
  • src/gui/rendering/ScreenBufferComponent.ts — Holds terminal buffer content for rendering
  • src/gui/rendering/PositionComponent.ts — Positioning data for UI elements
  • src/gui/rendering/IsVisibleComponent.ts — Visibility flag for renderable elements
  • src/gui/rendering/ScreenBufferClearSystem.ts — Clears the screen buffer once per frame before text rendering; follows Single Responsibility Principle by separating buffer clearing from text updating logic
  • src/gui/rendering/ScreenBufferRenderSystem.ts — Renders the screen buffer to terminal
  • src/gui/rendering/ScreenBufferTextUpdateSystem.ts — Updates text entries in the screen buffer; implements Observer pattern within ECS architecture to monitor entities with text, position, and visibility components, then writes their content to the ScreenBufferComponent singleton for rendering
  • src/gui/view/HeaderViewSystem.ts — Updates header area each tick
  • src/gui/view/FooterViewSystem.ts — Updates footer/status area
  • src/gui/rendering/DynamicTextRenderSystem.ts � Updates TextComponent values by calling DynamicTextComponent.getText(simulation) for visible entities

Menu components & systems

  • src/gui/menu/MenuItem.ts — Menu item model (text + action id)
  • src/gui/menu/MenuComponent.ts — Holds an ordered list of MenuItems and selection state with wrap-around navigation
  • src/gui/menu/ScrollableMenuComponent.ts — Extended MenuComponent with scrolling support for displaying limited items at a time with automatic viewport management
  • src/gui/menu/ScreensComponent.ts — Component that maps screen keys to entity IDs for screen navigation and management
  • src/gui/menu/MenuTextUpdateSystem.ts — Processes MenuComponent into TextComponent for single-line display with selection indicator
  • src/gui/menu/ScrollableMenuTextUpdateSystem.ts — Renders scrollable menus with headers, numbered lines, selection highlights, and scroll indicators
  • src/gui/controller/ActionQueueComponent.ts — Singleton component holding a FIFO queue of pending UI action IDs
  • src/gui/controller/MainControllerSystem.ts — System that processes UI action IDs from queue with screen switching and application lifecycle management

View components & systems

  • src/gui/view/InputComponent.ts — Stores last user input for menu screens
  • src/gui/view/InputSystem.ts — Processes user input for menus with navigation support for A/D keys ('a'/'d') and arrow keys ('left'/'right'), plus 'enter' selection that dispatches actions to MainControllerSystem

Simulation & Time

  • src/simulation/Simulation.ts — Main simulation (run loop and global state)
  • src/simulation/builder/SimulationBuilder.ts — Builder for simulation instances
  • src/time/Time.ts — Simulation time model
  • src/time/TimeComponent.ts — Component holding current time state
  • src/time/TimeSystem.ts — System advancing simulation time

Geography & World Generation

  • src/geography/planet/Continent.ts — Continent model representing landmasses with geographical features
  • src/geography/planet/PlanetComponent.ts — Component encapsulating mutable planet state including status, resource specialization, and continents
  • src/geography/galaxy/Sector.ts — Named region of the galaxy tracking planets for regional gameplay systems
  • src/geography/galaxy/GalaxyMapComponent.ts — Component modeling the galactic map as a graph of planets connected by space lanes, grouped into sectors
  • src/geography/GeographicalUtils.ts — Utility class providing centralized geographical operations including random place computation from galaxy maps with fallback support
  • src/geography/Location.ts — Location model representing a place defined by its geographical feature and planet
  • src/geography/feature/FeatureType.ts — Feature type metadata
  • src/geography/feature/GeographicalFeature.ts — Geographical feature instances
  • src/geography/feature/GeographicalFeaturesFactory.ts — Factory to create features
  • src/geography/feature/GeographicalFeatureTypeRegistry.ts — Registry of feature types
  • src/generator/world/WorldGenerator.ts — World generator implementation
  • src/generator/galaxy/GalaxyGenerator.ts — Standalone procedural galaxy map generator creating sector-based 2D spiral galaxy layouts
  • src/generator/galaxy/GalaxyMapData.ts — Output interface for galaxy map generation defining the structure of generated sector data
Galaxy Generation

The Herodotus project includes a standalone GalaxyGenerator tool that procedurally generates 2D spiral galaxy maps with sectors distributed across a galactic disc. This generator creates the spatial foundation for world-building by establishing named sectors positioned in concentric rings.

Core Classes:

  • src/generator/galaxy/GalaxyGenerator.ts — Standalone generator class implementing radial sector distribution algorithm with deterministic random generation
  • src/generator/galaxy/GalaxyMapData.ts — Interface defining galaxy map output structure with typed sector data
  • src/data/geography/galaxy/GalaxyGenData.ts — Configuration data class for galaxy generation parameters with runtime validation
  • src/data/geography/galaxy/loadGalaxyGenConfig.ts — Loader function reading galaxy generation configuration from JSON
  • data/geography/galaxy/galaxy.json — JSON configuration file containing galaxy generation parameters

Configuration Parameters: The galaxy.json file defines:

  • numberSectors — Total number of sectors to generate in the galaxy (default: 100)
  • galaxySize — Maximum radius of the galaxy in arbitrary spatial units (default: 10)

Generation Algorithm:

  1. Creates a central sector at galactic origin (0, 0, 0)
  2. Distributes remaining sectors across concentric rings around the center
  3. Calculates optimal ring count using square root of remaining sectors
  4. Positions sectors at calculated radii with even angular distribution
  5. Generates deterministic names for each sector using NameGenerator
  6. Increases sector density in outer rings (6, 12, 18, 24... sectors per ring)

Architecture Benefits:

  • Standalone Tool: Independent generator that can be used outside the main simulation
  • Deterministic Generation: Uses RandomComponent for reproducible galaxy layouts with seed support
  • Configurable Parameters: JSON-based configuration without code changes
  • Type Safety: Runtime validation ensures configuration integrity
  • Immutable Output: Generated data follows immutability patterns
  • Follows Project Patterns: Implements standard factory methods and dependency injection

Usage Example:

// Load configuration
const config = loadGalaxyGenConfig();

// Create generator with dependencies
const randomComponent = RandomComponent.create(randomSeed);
const nameGenerator = NameGenerator.create();
const generator = GalaxyGenerator.create(randomComponent, nameGenerator, config);

// Generate galaxy map
const galaxyMap = generator.generateGalaxyMap();
// Returns: { sectors: [ { id, name, position: { x, y, z } }, ... ] }

Output Structure:

interface GalaxyMapData {
  sectors: {
    id: string;        // Unique identifier (e.g., "sector-1")
    name: string;      // Procedurally generated name
    position: {
      x: number;       // X coordinate in galactic space
      y: number;       // Y coordinate in galactic space  
      z: number;       // Z coordinate (always 0 for 2D galaxy)
    };
  }[];
}

Key Features:

  • Empty sectors without planets (establishes spatial foundation only)
  • 2D galactic disc structure (Z coordinate always 0)
  • Radial distribution with increasing density in outer rings
  • Deterministic procedural naming via NameGenerator
  • JSON-configurable sector count and galaxy size
  • Compatible with existing Sector and Position domain classes

Implementation Details:

  • Accepts RandomComponent, NameGenerator, and GalaxyGenData via constructor
  • Uses dependency injection for testability
  • Returns plain data objects (not domain entities) for flexibility
  • Integrates with existing geography domain model
  • Follows Standalone Class Pattern from Random Number Generation guidelines
Data-Driven World Generation

The Herodotus project uses a data-driven approach for world generation configuration, separating generation parameters from code logic. This architecture enhances configurability and maintainability by externalizing world generation settings into JSON files.

Core Classes:

  • src/data/geography/worldgen/WorldGenData.ts — Type-safe data class containing world generation parameters with runtime validation, immutability enforcement, and null object pattern implementation
  • src/data/geography/worldgen/loadWorldGenData.ts — Loader function that reads and validates world generation configuration from JSON
  • data/geography/world/worldgen.json — JSON configuration file containing all world generation parameters

Configuration Parameters: The worldgen.json file defines the following parameters:

  • numSectors — Number of sectors to generate in the galaxy (default: 3)
  • planetsPerSector — Number of planets to generate per sector (default: 64)
  • featuresPerContinent — Number of geographical features per continent (default: 50)
  • continentsPerPlanet — Number of continents per planet (default: 5)
  • featuresPerPlanetContinent — Number of geographical features per planetary continent (default: 64)

Architecture Benefits:

  • Improved Configurability: World generation can be adjusted by editing JSON without code changes or recompilation
  • Separation of Concerns: Clear boundary between world generation logic and configuration data
  • Type Safety: Runtime validation using TypeUtils ensures configuration integrity
  • Immutability: Configuration instances are frozen after creation to prevent accidental modification
  • Backward Compatibility: Static constants remain in WorldGenerator marked as @deprecated for legacy code
  • Follows Project Patterns: Matches existing data loading patterns like GeographicalFeatureData and loadGeographicalFeatures

Usage Example:

// Load configuration from JSON
const config = loadWorldGenData();

// Create world generator with configuration
const nameGenerator = NameGenerator.create();
const worldGenerator = WorldGenerator.create(nameGenerator, config);

// Generate world using configuration
const galaxyMap = worldGenerator.generateGalaxyMap();

Customization: To modify world generation parameters, simply edit data/geography/world/worldgen.json:

{
  "numSectors": 5,
  "planetsPerSector": 128,
  "continentsPerPlanet": 7,
  ...
}

No code changes or recompilation required!

Implementation Details:

  • WorldGenData provides getter methods for all configuration values (e.g., getNumSectors(), getPlanetsPerSector())
  • Runtime validation throws TypeError with detailed messages for invalid data
  • Null object pattern implementation via WorldGenData.createNull() returns singleton with zero values
  • WorldGenerator constructor accepts WorldGenData parameter and uses configuration instead of static constants
  • Loaded by SimulationBuilder during world initialization

Realm Management

The realm system manages dynasties, territories, factions, and political dynamics. The module is organized into subdirectories for better maintainability.

Territory Management (src/realm/territory/)
  • src/realm/territory/ClaimStatus.ts — Enum defining territorial claim statuses (Core = fully controlled, Claimed = primary claimant, Contested = multiple claimants)
  • src/realm/territory/TerritoryComponent.ts — Component tracking a realm's territorial extent via a map of planet IDs to ClaimStatus values; represents the spatial domain of a dynasty
  • src/realm/territory/TerritoryClaimComponent.ts — Component attached to planets tracking which realms claim them; manages multiple simultaneous claims and identifies the controlling realm
Faction Management (src/realm/faction/)
  • src/realm/faction/Faction.ts — Domain class representing political or social factions within realms; tracks name, influence (0-100), and allegiance (0-100); implements Null Object Pattern
  • src/realm/faction/FactionManagerComponent.ts — Singleton component managing all factions in the simulation; provides CRUD operations and returns defensive copies to prevent external mutation
Realm State & Resources
  • src/realm/RealmStateComponent.ts — Singleton component managing global dynasty state including resources (Treasury, Stability, Legitimacy, Hubris), modifiers, failure thresholds, and historical tracking
  • src/realm/RealmResource.ts — Enum defining the four core dynasty resources that drive gameplay mechanics
  • src/realm/ModifierSourceType.ts — Enum defining sources of resource modifications (PlayerAction, NonPlayerAction, FactionInfluence, Event)
  • src/realm/ModifierType.ts — Enum defining types of modifications applied to resources (Flat, Percentage, Multiplier)
  • src/realm/RealmModifier.ts — Interface describing active modifications to realm resources including source, type, value, duration, and description
  • src/realm/FailureThresholds.ts — Interface defining critical threshold values for each resource; when resources drop below thresholds, failure conditions trigger
  • src/realm/ResourceHistoryEntry.ts — Interface for tracking historical changes to resources over time
  • src/realm/RealmComponent.ts — Component representing an individual realm entity with name, leader, and founding year
Realm Generation
  • src/generator/realm/RealmGenerator.ts — Generates initial realm configuration for the galaxy, assigning territorial claims and establishing the starting political landscape

Architecture Notes:

  • Territory and faction systems are organizationally separated for clarity and future expansion
  • Components follow immutability patterns with frozen instances where appropriate
  • All classes implement static factory methods (create()) for consistent instantiation
  • Realm state uses singleton pattern for global dynasty management
  • Test files mirror source structure in test/realm/territory/ and test/realm/faction/

Data & Chronicle

  • src/data/DataSetComponent.ts — Component for dataset storage on entities
  • src/data/DataSetEvent.ts — Data-set event model
  • src/data/DataSetEventComponent.ts — Component wrapping dataset events
  • src/data/loadEvents.ts — Utility function that loads events from JSON file into DataSetEvent instances
  • src/data/geography/feature/GeographicalFeatureData.ts — Represents geographical feature data loaded from JSON with runtime validation and type safety
  • src/chronicle/ChronicleEvent.ts — Chronicle event model
  • src/chronicle/ChronicleComponent.ts — Component that stores chronicle entries
  • src/chronicle/EventType.ts — Event type metadata and helpers
  • src/chronicle/EventCategory.ts — Defines event categories (Political, Social, Economic, etc.) as frozen constants with TypeScript type support

Historical Figures

  • src/historicalfigure/HistoricalFigureComponent.ts — Component storing figure data and snapshot helpers
  • src/historicalfigure/HistoricalFigureLocationComponent.ts — Component tracking the location of historical figures (currently empty/placeholder)
  • src/historicalfigure/HistoricalFigureBirthSystem.ts — System that manages birth of historical figures
  • src/historicalfigure/HistoricalFigureLifecycleSystem.ts — Manages lifecycle events for figures
  • src/historicalfigure/HistoricalFigureInfluenceSystem.ts — Applies influence changes to figures

Behaviour / Dilemmas

  • src/behaviour/ChoiceComponent.ts — Component holding current choice options
  • src/behaviour/ComputeChoicesSystem.ts — System that generates and computes available choices for entities based on events and game state
  • src/behaviour/SelectChoiceSystem.ts — System that processes player choice selection and applies consequences

Naming & Utilities

  • src/naming/MarkovChain.ts — Markov-chain utility used by name generation
  • src/naming/NameGenerator.ts — High-level name generator using syllable sets
  • src/naming/SyllableSets.ts — Collection of syllable sets for procedural name generation with linguistic variety (exported as interface and constants)
  • src/util/TypeUtils.ts — Runtime type checks and validators (ensureInstanceOf, ensureString, etc.)
  • src/util/log/LogHelper.ts — Simple logging helpers used across the project

Screen Implementations

  • MainInterfaceScreen.ts - Main menu with navigation and pending choices
  • StatusScreen.ts - Detailed simulation status display
  • ChoicesScreen.ts - Dilemma choice selection and processing
  • ChronicleScreen.ts - Historical events display

Integration Points

The GUI uses a dual-ECS architecture:

  • Simulation ECS: Handles game logic, entities with PlayerComponent, ChoiceComponent, etc.
  • GUI ECS: Separate instance for screen management with independent update frequencies
  • Reads/writes simulation state through the main ECS instance
  • Monitors simulation state through singleton components
  • Utilizes the existing chronicle system for event recording
  • Provides clean screen management through the separate GUI ECS pattern
  • Maintains decoupled architecture with responsive UI updates

Example Session

============================================================
Year: 0001 | Simulation: Running | Player: Player Character | Status: 1 pending decision(s) | Herodotus 1.0.0
============================================================

============================================================

*** DECISION REQUIRED ***

[1] Economic Reform
    Type: Economic
    Description: The kingdom's treasury is running low. You must decide how to address the financial crisis.
    → Your choice will affect the kingdom's prosperity and your subjects' loyalty.

[2] Maintain Status Quo
    Type: Economic
    Description: Keep current economic policies and hope for the best.
    → The situation may worsen, but no immediate disruption will occur.

============================================================
Commands: [H]elp [S]tatus [C]hoices Ch[r]onicle [Q]uit
============================================================

Command: c

============================================================
                    DECISION REQUIRED
============================================================

[1] Economic Reform
    Type: Economic
    Description: The kingdom's treasury is running low. You must decide how to address the financial crisis.
    Consequence: Your choice will affect the kingdom's prosperity and your subjects' loyalty.

[2] Maintain Status Quo
    Type: Economic
    Description: Keep current economic policies and hope for the best.
    Consequence: The situation may worsen, but no immediate disruption will occur.

============================================================
Choose your decision (1-2), or 'back': 1

You chose: Economic Reform
This decision will shape your reign...
Press Enter to continue...

Interface Design

Clean Screen Management

  • Uses ANSI escape codes to clear screen and reset cursor position
  • Fixed layout that updates in place rather than scrolling
  • Consistent bordered sections using = characters
  • Professional appearance with organized information display

User Experience

  • No Scrolling: Screen clearing prevents information from scrolling off
  • Immediate Feedback: All commands show results and wait for acknowledgment
  • Visual Consistency: All screens use the same layout and styling
  • Efficient Navigation: Single-letter shortcuts for quick access
  • Clear Information Hierarchy: Header, content, and command areas clearly separated

Technical Notes

  • Uses Node.js readline for input handling
  • Simulation runs with 2-second background intervals for better readability
  • Player input is processed synchronously with screen clearing
  • Chronicle events are recorded automatically by the DilemmaResolutionSystem
  • Compatible with the existing test suite and build system
  • ANSI escape codes provide cross-platform screen clearing support

Linting

This project uses ESLint (flat config) to lint both JavaScript and TypeScript.

  • Config file: eslint.config.mjs (ESLint v9 flat config)
  • Scope: lints src/**/*.js and src/**/*.ts; Jest globals enabled for files in test/
  • Ignored by default: dist/, coverage/, node_modules/

Run locally:

npm run lint      # report issues
npm run lint:fix  # attempt automatic fixes

Notes:

  • Rules are intentionally lenient to ease adoption (e.g., fewer errors in tests and JS unused-vars shown as warnings). Tightening can be done later in eslint.config.mjs.
  • For best DX, install the "ESLint" extension in VS Code to see problems inline and auto-fix on save.

About

History generator for fictional worlds

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •