Skip to content

Modding Guide

TinyGenerals is data-driven — game rules, factions, and visuals are defined in JSON files and sprite sheets. This guide covers the format and structure you need to create custom content.


A ruleset is a single JSON file that defines all game rules: terrain, units, buildings, factions, combat, and vision. The default ruleset is “Classic TinyGenerals.”

{
"id": "my-ruleset",
"name": "My Custom Ruleset",
"faction_mode": "optional",
"faction_selection": {
"required": false,
"default_behavior": "prompt",
"allow_mirror_matches": true,
"auto_balance": false
},
"factions": { ... },
"terrain_types": { ... },
"overlay_types": { ... },
"unit_types": { ... },
"building_types": { ... },
"vision_rules": { ... },
"combat_rules": { ... }
}
ValueBehavior
"none"No factions. All players use the shared unit_types.
"optional"Players may pick a faction for unique units.
"required"Every player must select a faction before the game starts.

The top-level unit_types defines generic fallback units used only when no faction is selected. In faction games, each faction’s own unit_types completely replaces any shared unit with the same key:

{
"unit_types": {
"INFANTRY": { "name": "Infantry", "role": "INFANTRY", "health": 10, ... },
"VEHICLE": { "name": "Vehicle", "role": "ARMOR", "health": 15, ... }
},
"factions": {
"ALLIANCE": {
"unit_types": {
"INFANTRY": { "name": "Militia", "role": "INFANTRY", "health": 10, ... },
"VEHICLE": { "name": "Battle Tank", "role": "ARMOR", "health": 18, ... },
"HOWITZER": { "name": "Howitzer", "role": "RANGED", "health": 10, ... }
}
}
}
}

An Alliance player gets Militia, Battle Tank, and Howitzer — never the generic Infantry or Vehicle.


Maps are faction-agnostic — the map creator doesn’t know which factions will play. Spawn points use archetypes (a role + power tier) that resolve to the correct faction unit at game start.

Every unit definition includes a role field:

"HOWITZER": {
"name": "Howitzer",
"role": "RANGED",
"health": 10,
"min_attack_range": 2,
"max_attack_range": 4,
...
}
RoleDescriptionCurrent units
INFANTRYFoot soldiers, can capture buildingsMilitia, Trooper, Shock Trooper
ARMORHeavy armored vehicles (tanks)Battle Tank, Assault Tank
RANGEDLong-range indirect fire (artillery)Howitzer, Rocket Battery
ANTI_ARMOR(Reserved) Anti-tank / tank destroyers
RECON(Reserved) Fast scouts with high vision
VEHICLE(Reserved) Light vehicles (APCs, IFVs)
ENGINEER(Reserved) Builders, repair, field fortifications
AIR_DEFENSE(Reserved) Anti-air units
FIGHTER(Reserved) Air superiority aircraft
BOMBER(Reserved) Ground attack aircraft
NAVAL(Reserved) Surface warships

Units in the same role can have different power tiers:

"SHOCK_TROOPER": {
"name": "Shock Trooper",
"role": "INFANTRY",
"tier": 2,
"move_points": 4,
"allow_move_after_attack": true,
...
}
TierMeaningExample
1 (default)Basic unitTrooper
2Advanced variantShock Trooper
3Elite variant(future)

Units without a tier field are tier 1.

When a game starts, each spawn point resolves like this:

  1. Direct match — If the spawn value is a unit ID (e.g. "HOWITZER"), use it directly
  2. Role + tier scan — Find a faction unit with matching role and tier
  3. Tier fallback — No match at requested tier? Try the next lower tier, down to T1
  4. Last resort — No unit matches the role at all? Spawn the faction’s Infantry

Example: A map spawn is set to INFANTRY_2 (Infantry, tier 2):

FactionHas T2 infantry?Result
FederationYes (Shock Trooper)Spawns Shock Trooper
AllianceNoFalls back to T1 → Spawns Militia

In the map editor’s Spawns mode:

  1. Select a player slot (P1–P8)
  2. Pick a role — Infantry, Armor, or Artillery
  3. Pick a tier — T1, T2, or T3
  4. Click hexes to place spawn points

The spawn marker shows the role letter and tier (e.g. I for Infantry T1, R2 for Artillery T2).


A faction lives inside the ruleset’s factions block. Here’s the minimal structure:

"MY_FACTION": {
"id": "MY_FACTION",
"name": "The Ironclads",
"description": "Heavy defensive faction with powerful armor.",
"version": "1.0.0",
"difficulty": "intermediate",
"playstyle_tags": ["defensive", "armor-heavy"],
"emblem": { "type": "emoji", "value": "🛡️" },
"asset_pack": "default",
"unlock_requirements": {
"default_unlocked": true,
"premium_only": false,
"required_trophies": []
},
"preview": {
"tagline": "Unbreakable line",
"showcase_units": ["INFANTRY", "HEAVY_TANK"]
},
"unit_types": {
"INFANTRY": {
"name": "Garrison",
"role": "INFANTRY",
"symbol": "G",
"health": 12,
"move_points": 2,
"attack": 3,
"soft_attack": 3,
"hard_attack": 2,
"target_type": "SOFT",
"vision_range": 2,
"can_traverse_all": true,
"can_capture": true,
"cost": 150,
"min_attack_range": 1,
"max_attack_range": 1,
"max_attacks_per_turn": 1,
"allow_attack_after_move": true
}
},
"building_types": {},
"bonuses": {
"starting_gold_bonus": 0,
"income_multiplier": 1.0,
"unit_cost_multiplier": 1.0,
"vision_range_bonus": 0
}
}
FieldTypeDescription
namestringDisplay name (also used for sprite lookup)
rolestringArchetype — see roles table above
tierintPower tier within role (omit or 0 for tier 1)
symbolstring1-2 character fallback when no sprite is available
healthintMaximum hit points
move_pointsintMovement points per turn
attackintLegacy attack value
soft_attackintDamage vs soft targets (infantry)
hard_attackintDamage vs hard targets (vehicles)
target_typestring"SOFT" or "HARD" — what this unit counts as when defending
vision_rangeintHow far this unit can see (in hexes)
can_traverse_allboolCan cross mountains and other impassable-to-vehicles terrain
can_captureboolCan capture buildings
costintGold cost to produce
min_attack_rangeintMinimum attack range (1 = adjacent)
max_attack_rangeintMaximum attack range (1 = melee only)
attack_costintMovement points consumed per attack (0 = free)
max_attacks_per_turnintHow many times this unit can attack per turn
allow_move_after_attackboolCan move after attacking
allow_attack_after_moveboolCan attack after moving
  • Include at least one unit with "role": "INFANTRY" — this is the last-resort spawn fallback
  • Cover the core roles (INFANTRY, ARMOR, RANGED) so maps with varied archetypes work
  • Give each unit a unique symbol (1-2 characters) for the fallback renderer
  • Faction bonuses are multipliers — 1.0 means no change from baseline

Themes control the visual appearance: terrain tiles, unit sprites, building graphics, and animations. The game ships with a default theme and falls back to colored shapes for any missing assets.

A theme is a directory containing a manifest and sprite subdirectories:

my-theme/
theme.json ← Manifest (declares assets and animations)
terrain/
GRASS_1.png
GRASS_2.png
FOREST_1.png
...
units/
MILITIA.png
BATTLE_TANK.png
HOWITZER.png
...
buildings/
BASE.png
FACTORY.png
...

Sprites are matched by the unit’s name field (uppercased, spaces → underscores):

Unit nameSprite filename
MilitiaMILITIA.png
Battle TankBATTLE_TANK.png
Shock TrooperSHOCK_TROOPER.png

If the name-based sprite isn’t found, the game tries the unit type ID (e.g. INFANTRY.png). If neither exists, it renders a colored circle with the unit’s symbol text.

For detailed sprite dimensions and hex geometry, see the Graphics Guide.


RoleAlliance T1Alliance T2Federation T1Federation T2
InfantryMilitiaTrooperShock Trooper
ArmorBattle TankAssault Tank
ArtilleryHowitzerRocket Battery
Map spawn: "RANGED_2" (Artillery, tier 2)
├─ Faction has RANGED tier 2? → Yes → Spawn it
│ → No → Try tier 1
├─ Faction has RANGED tier 1? → Yes → Spawn it
│ → No → Spawn Infantry T1
└─ Done

As new units and factions are added, the archetype system picks them up automatically — existing maps don’t need updates.