Skip to content

Code Style

Ink230 edited this page Sep 10, 2026 · 5 revisions

Irongoon code style and architecture

Dependency direction

Irongoon uses a shallow, leaf-based dependency tree:

Severed Chains event bus
  -> Irongoon event adapter
    -> Randomizer strategy coordinator
      -> focused service or randomizer leaf
        -> parser, data, or configuration leaf

Parents select and invoke behavior. Leaves perform one bounded operation and do not route back through a parent or depend on unrelated sibling features.

Ownership

  • Irongoon owns lifecycle and Severed Chains event adaptation
  • Randomizer selects configured strategies and applies leaf results
  • services.randomizer owns terminal randomization algorithms
  • services owns stateful domain services such as Additions, Dragoon spells, and canonical data tables
  • services.data owns data-source selection and loading
  • parse.external owns file syntax parsing
  • parse.game owns table-to-game conversion
  • parse.schema owns table validation
  • IrongoonConfig applies the current campaign configuration snapshot; profiles and their persisted snapshots are owned by the configuration profile and campaign leaves
  • data owns closed option enums and serialized identifiers
  • models owns domain and transport values

Put new behavior in the narrowest existing owner. Do not accumulate algorithms in Irongoon, parsing in Randomizer, or source-selection policy in parsers.

Stateful services

Stateful services, parsers, data sources, and randomizer leaves use eager process-wide singletons:

public final class ExampleService {
    private static final ExampleService INSTANCE = new ExampleService();

    public static ExampleService getInstance() {
        return INSTANCE;
    }

    private ExampleService() {
    }
}

Obtain collaborators through Type.getInstance(). Do not add public constructors, dependency-injection infrastructure, service locators, or mutable test setters solely for convenience.

Use ordinary records, models, enums, and event values when process-wide identity is not required.

Java conventions

  • Use package root lod.irongoon
  • Keep one top-level production type per file
  • Use four-space indentation and opening braces on declaration lines
  • Allow brace-free single-line guards such as if (characterId < 0) return;
  • Use braces for multiline conditionals and loops
  • Use final for parameters and locals that are not reassigned
  • Use var when the initializer makes the concrete type obvious
  • Use this. where it clarifies instance ownership
  • Use exhaustive switch expressions for closed configuration modes
  • Keep stock or passthrough behavior explicit
  • Use records for small immutable transports and enums for closed policies
  • Implement Data<T> when an enum exposes a serialized scalar
  • Preserve Severed Chains terminology at the integration boundary

Randomness contract

  • Campaign and fixed modes must derive reproducible values from IrongoonConfig.seed
  • Add stable domain modifiers such as character, monster, shop, level, encounter, or submap IDs
  • Keep shared bounded, distribution, and percentage calculations in StatsRandomizer
  • Use unseeded randomness only for modes explicitly documented to reroll per battle, transformation, spawn, or use
  • Treat changes to seed salts or modifier composition as compatibility changes for existing seeds

Configuration and external data

  • Keep configuration state in IrongoonConfig
  • Validate explicit bounds and closed policies during configuration loading
  • Validate external tables at the source boundary with dataset, source, row, and field context
  • Do not silently fall through to another source after an explicitly selected source fails validation
  • Treat DataTables as the owner of canonical loaded tables and copy mutable collections across ownership boundaries
  • Clear and rebuild service caches during initialization so save reloads cannot retain stale state

Errors and logging

  • Throw IllegalStateException when initialized runtime data is missing, invalid, inconsistent, or used too early
  • Add domain context when translating lower-level failures
  • Use LogManager.getFormatterLogger(Type.class) and formatter-style placeholders
  • Do not use System.out.println, swallowed exceptions, or context-free generic errors

Scope and validation

  • Prefer the smallest coherent change and avoid unrelated formatting cleanup
  • Preserve bundled CSV compatibility data and patch assets unless the task requires changing them
  • Use the Gradle wrapper for compilation and packaging
  • Run gradlew.bat compileJava for production Java changes
  • Run gradlew.bat assemble for dependency, registry, resource, patch, or packaging changes
  • Treat builds as integration checks, not proof of combat, graphics, audio, save, or other runtime behavior
  • Inspect git diff, run git diff --check, and preserve unrelated worktree changes before committing

Clone this wiki locally