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.
This section provides an overview of the project's architecture and class structure for developers.
All available npm scripts and what they do. Commands below are for PowerShell.
# 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:guiBuild time: ~40-50ms typically
Build output: dist/bundle.js (~218KB) or dist/bundle-gui.js
# 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# 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 typechecknpm run typecheck- validates TypeScript typesnpm run lint- validates code style and catches errorsnpm run test- validates functionalitynpm run build- validates production bundle creation
- Husky automatically runs on
git commit:lint-staged- lints only staged files with auto-fixnpm run typecheck- validates types across entire project
- If any pre-commit step fails, the commit is rejected
- ESLint errors: Run
npm run lint:fixto 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
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.
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
To start the interactive GUI version of the simulation:
npm run start:guiAlternatively, you can run it directly with tsx:
tsx src/mainGUI.ts --guiThe 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
============================================================
The GUI system is built using the Entity-Component-System (ECS) pattern, where each screen is implemented as an entity with specific 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:
renderFunctionandhandleInputFunction - Provides
render()andhandleInput()methods
ScreenRenderSystem (src/gui/ScreenRenderSystem.ts)
- System responsible for rendering the currently active screen
- Key methods:
renderActiveScreen(): Renders the entity with IsActiveComponenthandleActiveScreenInput(): Routes input to the active screensetActiveScreen(): Changes which screen is active
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
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
- Modularity: Each screen is a separate entity with its own components
- Extensibility: Easy to add new screens by creating new entities
- Consistency: All screens follow the same ECS pattern
- Separation of Concerns: Render logic separated from input handling
- Testability: Individual screens can be tested in isolation
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
ActionQueueComponentin 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 simulationChoiceComponent) - Extensible: New actions can be added by extending the switch/dispatch logic
Architecture:
- Queries entities with
ActionQueueComponentto 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
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
MenuComponentandInputComponent - 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
MenuComponentfor menu state management - Integrates with
ActionQueueComponentfor action dispatching - Filters entities using
IsVisibleComponentto ensure only active menus respond - Coordinates with
ActionSystemfor 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
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 byChoiceMenuViewSystem. - Each visible choice is shown with a numbered shortcut (
[1],[2],[3]) for quick selection; selection actions are emitted asCHOICE_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.
Both systems follow the Entity-Component-System pattern providing:
- Modularity: Each system has a single, well-defined responsibility
- Testability: Systems can be tested in isolation with mocked components
- Extensibility: New actions and input types can be added easily
- Performance: Efficient querying using component filters
- Reliability: Comprehensive test coverage ensures stable behavior
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.
The GUI integrates seamlessly with the existing ECS architecture:
- Each screen is an entity with
ScreenComponentand optionallyIsActiveComponent - The
ScreenRenderSystemqueries 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"
The simulation creates a dedicated player entity with:
PlayerComponent- Marks the entity as player-controlledChoiceComponent- Contains available choices when decisions are neededDataSetEventComponent- Holds the current event/decision stateHistoricalFigureComponent- Contains character information
- The
DilemmaSystemgenerates choices and adds them to the player'sChoiceComponent - The GUI automatically displays pending choices in the main interface
- Player uses
corchoicescommand to access the decision screen - Player selects a choice by number (1-N)
- The choice is set in the player's
DataSetEventComponent - The
DilemmaResolutionSystemprocesses the choice and records it in the chronicle
- 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
src/gui/TextBasedGui.ts- Advanced GUI implementation using separate ECS architecturesrc/gui/GuiEcsManager.ts- Manages separate GUI ECS instance, systems, and screen entitiessrc/gui/ScreenRenderSystem.ts- System for rendering active screenssrc/gui/IsActiveComponent.ts- Component marking the active screensrc/gui/ScreenComponent.ts- Component containing screen logicsrc/gui/menu/ActionComponent.ts- Component that holds an action identifier for menu itemssrc/gui/view/InputComponent.ts- Component that stores the last user input for menu screenssrc/gui/screens/- Directory containing individual screen implementationssrc/mainGUI.ts- Entry point for GUI mode
This section lists exported classes found in the codebase grouped by area (file path shown) with a short description.
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 processingsrc/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 registrationsrc/ecs/Entity.ts— Entity abstraction (id and components)src/ecs/EntityManager.ts— Manages entities and their componentssrc/ecs/Ecs.ts— High-level ECS facade (factory and orchestration)src/ecs/NameComponent.ts— Component storing a human-readable namesrc/ecs/PlayerComponent.ts— Marks an entity as the player
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(), andnot()combinators - Type Safety: Full TypeScript support with proper inheritance from base
Systemclass - Backward Compatible: Existing systems continue to work unchanged
Core Classes:
src/ecs/FilteredSystem.ts— Abstract system class that accepts anEntityFilterfunction and automatically filters entities before callingprocessFilteredEntity()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 nameEntityFilters.hasComponent(ComponentClass)— Filter entities containing a specific componentEntityFilters.lacksComponent(ComponentClass)— Filter entities missing a specific componentEntityFilters.and(filter1, filter2, ...)— Combine filters with AND logicEntityFilters.or(filter1, filter2, ...)— Combine filters with OR logicEntityFilters.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— UsesEntityFilters.byName('ChronicleScreen')to process only chronicle screen entitiessrc/gui/view/ChoiceMenuViewSystem.ts— UsesEntityFilters.byName('ChoicesScreen')to populate choice menu only for choice screen entities
Architecture Integration:
- FilteredSystem maintains full compatibility with existing
Systembase class - Uses static factory method
createFilteredSystem()to avoid inheritance conflicts - Integrates seamlessly with
SystemManagerand existing ECS infrastructure - Extensive test coverage ensures reliability and proper error handling
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 datasrc/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();Entity component queries now use instanceof semantics with an exact-match preference:
Entity.hasComponent(Base)returns true if the entity has aBasecomponent or any subclass instance.Entity.getComponent(Base)first returns an exactBaseinstance when present; otherwise it returns the first component that is an instance ofBase(e.g., a subclass).- Systems that declare
requiredComponents: [Base]will process entities withBaseor any subclass (e.g.,SubBase extends Base). If you need stricter filtering, add a guard insideprocessEntity().
src/gui/TextBasedGui.ts— Text-based GUI main class (interactive mode entry)src/gui/GuiEcsManager.ts— Manages GUI-specific ECS instance, screens and systemssrc/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
src/gui/rendering/TextComponent.ts— Static text display componentsrc/gui/rendering/DynamicTextComponent.ts— Runtime-generated text via callback; uses strategy pattern with functions accepting GUI and simulation entity managerssrc/gui/rendering/ScreenBufferComponent.ts— Holds terminal buffer content for renderingsrc/gui/rendering/PositionComponent.ts— Positioning data for UI elementssrc/gui/rendering/IsVisibleComponent.ts— Visibility flag for renderable elementssrc/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 logicsrc/gui/rendering/ScreenBufferRenderSystem.ts— Renders the screen buffer to terminalsrc/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 renderingsrc/gui/view/HeaderViewSystem.ts— Updates header area each ticksrc/gui/view/FooterViewSystem.ts— Updates footer/status areasrc/gui/rendering/DynamicTextRenderSystem.ts� UpdatesTextComponentvalues by callingDynamicTextComponent.getText(simulation)for visible entities
src/gui/menu/MenuItem.ts— Menu item model (text + action id)src/gui/menu/MenuComponent.ts— Holds an ordered list ofMenuItems and selection state with wrap-around navigationsrc/gui/menu/ScrollableMenuComponent.ts— Extended MenuComponent with scrolling support for displaying limited items at a time with automatic viewport managementsrc/gui/menu/ScreensComponent.ts— Component that maps screen keys to entity IDs for screen navigation and managementsrc/gui/menu/MenuTextUpdateSystem.ts— Processes MenuComponent into TextComponent for single-line display with selection indicatorsrc/gui/menu/ScrollableMenuTextUpdateSystem.ts— Renders scrollable menus with headers, numbered lines, selection highlights, and scroll indicatorssrc/gui/controller/ActionQueueComponent.ts— Singleton component holding a FIFO queue of pending UI action IDssrc/gui/controller/MainControllerSystem.ts— System that processes UI action IDs from queue with screen switching and application lifecycle management
src/gui/view/InputComponent.ts— Stores last user input for menu screenssrc/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
src/simulation/Simulation.ts— Main simulation (run loop and global state)src/simulation/builder/SimulationBuilder.ts— Builder for simulation instancessrc/time/Time.ts— Simulation time modelsrc/time/TimeComponent.ts— Component holding current time statesrc/time/TimeSystem.ts— System advancing simulation time
src/geography/planet/Continent.ts— Continent model representing landmasses with geographical featuressrc/geography/planet/PlanetComponent.ts— Component encapsulating mutable planet state including status, resource specialization, and continentssrc/geography/galaxy/Sector.ts— Named region of the galaxy tracking planets for regional gameplay systemssrc/geography/galaxy/GalaxyMapComponent.ts— Component modeling the galactic map as a graph of planets connected by space lanes, grouped into sectorssrc/geography/GeographicalUtils.ts— Utility class providing centralized geographical operations including random place computation from galaxy maps with fallback supportsrc/geography/Location.ts— Location model representing a place defined by its geographical feature and planetsrc/geography/feature/FeatureType.ts— Feature type metadatasrc/geography/feature/GeographicalFeature.ts— Geographical feature instancessrc/geography/feature/GeographicalFeaturesFactory.ts— Factory to create featuressrc/geography/feature/GeographicalFeatureTypeRegistry.ts— Registry of feature typessrc/generator/world/WorldGenerator.ts— World generator implementationsrc/generator/galaxy/GalaxyGenerator.ts— Standalone procedural galaxy map generator creating sector-based 2D spiral galaxy layoutssrc/generator/galaxy/GalaxyMapData.ts— Output interface for galaxy map generation defining the structure of generated sector data
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 generationsrc/generator/galaxy/GalaxyMapData.ts— Interface defining galaxy map output structure with typed sector datasrc/data/geography/galaxy/GalaxyGenData.ts— Configuration data class for galaxy generation parameters with runtime validationsrc/data/geography/galaxy/loadGalaxyGenConfig.ts— Loader function reading galaxy generation configuration from JSONdata/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:
- Creates a central sector at galactic origin (0, 0, 0)
- Distributes remaining sectors across concentric rings around the center
- Calculates optimal ring count using square root of remaining sectors
- Positions sectors at calculated radii with even angular distribution
- Generates deterministic names for each sector using
NameGenerator - 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
RandomComponentfor 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
SectorandPositiondomain classes
Implementation Details:
- Accepts
RandomComponent,NameGenerator, andGalaxyGenDatavia 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
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 implementationsrc/data/geography/worldgen/loadWorldGenData.ts— Loader function that reads and validates world generation configuration from JSONdata/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
TypeUtilsensures configuration integrity - Immutability: Configuration instances are frozen after creation to prevent accidental modification
- Backward Compatibility: Static constants remain in
WorldGeneratormarked as@deprecatedfor legacy code - Follows Project Patterns: Matches existing data loading patterns like
GeographicalFeatureDataandloadGeographicalFeatures
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:
WorldGenDataprovides getter methods for all configuration values (e.g.,getNumSectors(),getPlanetsPerSector())- Runtime validation throws
TypeErrorwith detailed messages for invalid data - Null object pattern implementation via
WorldGenData.createNull()returns singleton with zero values WorldGeneratorconstructor acceptsWorldGenDataparameter and uses configuration instead of static constants- Loaded by
SimulationBuilderduring world initialization
The realm system manages dynasties, territories, factions, and political dynamics. The module is organized into subdirectories for better maintainability.
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 dynastysrc/realm/territory/TerritoryClaimComponent.ts— Component attached to planets tracking which realms claim them; manages multiple simultaneous claims and identifies the controlling realm
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 Patternsrc/realm/faction/FactionManagerComponent.ts— Singleton component managing all factions in the simulation; provides CRUD operations and returns defensive copies to prevent external mutation
src/realm/RealmStateComponent.ts— Singleton component managing global dynasty state including resources (Treasury, Stability, Legitimacy, Hubris), modifiers, failure thresholds, and historical trackingsrc/realm/RealmResource.ts— Enum defining the four core dynasty resources that drive gameplay mechanicssrc/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 descriptionsrc/realm/FailureThresholds.ts— Interface defining critical threshold values for each resource; when resources drop below thresholds, failure conditions triggersrc/realm/ResourceHistoryEntry.ts— Interface for tracking historical changes to resources over timesrc/realm/RealmComponent.ts— Component representing an individual realm entity with name, leader, and founding year
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/andtest/realm/faction/
src/data/DataSetComponent.ts— Component for dataset storage on entitiessrc/data/DataSetEvent.ts— Data-set event modelsrc/data/DataSetEventComponent.ts— Component wrapping dataset eventssrc/data/loadEvents.ts— Utility function that loads events from JSON file into DataSetEvent instancessrc/data/geography/feature/GeographicalFeatureData.ts— Represents geographical feature data loaded from JSON with runtime validation and type safetysrc/chronicle/ChronicleEvent.ts— Chronicle event modelsrc/chronicle/ChronicleComponent.ts— Component that stores chronicle entriessrc/chronicle/EventType.ts— Event type metadata and helperssrc/chronicle/EventCategory.ts— Defines event categories (Political, Social, Economic, etc.) as frozen constants with TypeScript type support
src/historicalfigure/HistoricalFigureComponent.ts— Component storing figure data and snapshot helperssrc/historicalfigure/HistoricalFigureLocationComponent.ts— Component tracking the location of historical figures (currently empty/placeholder)src/historicalfigure/HistoricalFigureBirthSystem.ts— System that manages birth of historical figuressrc/historicalfigure/HistoricalFigureLifecycleSystem.ts— Manages lifecycle events for figuressrc/historicalfigure/HistoricalFigureInfluenceSystem.ts— Applies influence changes to figures
src/behaviour/ChoiceComponent.ts— Component holding current choice optionssrc/behaviour/ComputeChoicesSystem.ts— System that generates and computes available choices for entities based on events and game statesrc/behaviour/SelectChoiceSystem.ts— System that processes player choice selection and applies consequences
src/naming/MarkovChain.ts— Markov-chain utility used by name generationsrc/naming/NameGenerator.ts— High-level name generator using syllable setssrc/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
MainInterfaceScreen.ts- Main menu with navigation and pending choicesStatusScreen.ts- Detailed simulation status displayChoicesScreen.ts- Dilemma choice selection and processingChronicleScreen.ts- Historical events display
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
============================================================
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...
- 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
- 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
- Uses Node.js
readlinefor 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
This project uses ESLint (flat config) to lint both JavaScript and TypeScript.
- Config file:
eslint.config.mjs(ESLint v9 flat config) - Scope: lints
src/**/*.jsandsrc/**/*.ts; Jest globals enabled for files intest/ - Ignored by default:
dist/,coverage/,node_modules/
Run locally:
npm run lint # report issues
npm run lint:fix # attempt automatic fixesNotes:
- 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.