================================================================================
ANCIENT BATTLE ARENA - MOVEMENT & FORMATION SYSTEM DESIGN
================================================================================
This document is GOSPEL for movement, bracing, and formation mechanics.
All future development must follow these principles.

Last Updated: 2026-10-12 (Session 3 - contact lines)

================================================================================
CORE PHILOSOPHY
================================================================================

The game uses a FORMATION-CENTRIC system, NOT soldier-centric.

- Formations (regiments) are the selectable/commandable entities
- Individual soldiers constantly reposition to their assigned slot
- Combat is a SIDE EFFECT of being in melee range
- Physics (push vs brace) determines if soldiers can move
- No special "melee mode" - just soldiers trying to reach their slots

================================================================================
KEY CONCEPTS
================================================================================

1. CENTER OF MASS vs TARGET POSITION
-----------------------------------
- Center of Mass: Calculated from actual soldier positions (DESCRIPTIVE)
  - Where soldiers ARE right now
  - Recalculated every few frames from soldier positions

- Target Position: Where formation is trying to go (PRESCRIPTIVE)
  - Where soldiers SHOULD BE
  - Updated when given move orders or tracking an enemy formation
  - Soldiers calculate their slot relative to this, not center of mass

When stationary: Target = Center of Mass
When moving: Target advances toward destination, soldiers follow

2. SOLDIER SLOT POSITION
------------------------
Each soldier has a unique slot in the formation:

  slot_position = target_position + slot_offset_rotated_by_facing

  - slotIndex: 1 to N (assigned at spawn)
  - rankIndex: 0 = front rank, higher = further back
  - fileIndex: 0 = leftmost in rank, higher = further right

Slot geometry is shared by spawner and controller: gamedata/formation_layout.lua
  forward = (sin f, cos f)      right = (cos f, -sin f)
  lateral = (fileIndex - (RANK_WIDTH-1)/2) * SOLDIER_SPACING
  ahead   = halfDepth - rankIndex * RANK_DEPTH     (halfDepth = (numRanks-1)/2 * RANK_DEPTH)
  offset  = right * lateral + forward * ahead
The target position is the CENTER of the layout; rank 0 is the front, facing the enemy.

Closing ranks: when a soldier dies, everyone behind him in his file steps up one rank.
If a file empties completely, the outer files sidestep inward to close the gap.

3. CONSTANT REPOSITIONING
-------------------------
Soldiers ALWAYS try to move toward their slot position.
This runs every 5 frames (REPOSITION_INTERVAL).

If distance to slot > SLOT_TOLERANCE (8 units):
  Issue move order toward slot

This creates cohesion - soldiers that drift get pulled back.
Physics/collision determines if they can actually reach their slot.

4. FORMATION SPEED
------------------
CRITICAL: Formation target moves FASTER than soldiers (4.0 vs 2.0-2.2).
This ensures soldiers are always chasing their slots during movement,
creating smooth continuous motion instead of stop-start stuttering.

================================================================================
PUSH VS BRACE SYSTEM
================================================================================

When units collide, outcome determined by momentum vs brace:

MOMENTUM (pushing power):
- Base momentum from unit customParams.momentum_value
- Increased when charging (moving fast): momentum * CHARGE_MOMENTUM_BONUS * mass
- Decays after stopping (MOMENTUM_DECAY_RATE per frame)

BRACE (resistance):
- Fixed value from unit customParams.brace_value
- Higher = harder to push

Resolution is done by the CONTACT LINE (see below), not the engine:
allowPushingEnemyUnits = false, so enemies only block each other.

Unit values (customParams):
  Swordsman: brace_value="12", momentum_value="8", mass="1.0"
  Archer:    brace_value="6",  momentum_value="6", mass="0.8"

================================================================================
CONTACT LINE SYSTEM (058_formation_contact.lua) - Session 3
================================================================================

When two enemy formations touch (>= 2 soldiers per side within CONTACT_RANGE = 40),
a contact line is created between them: a point + a normal pointing A -> B.

REPEL: each formation's front rank is anchored FRONT_OFFSET (10) behind the line on
       its own side, facing the enemy. Slots never cross the line, so blocks don't
       try to walk through each other.
GLUE:  the line belongs to both formations. When one side gives ground the line
       moves and the other side's front follows. The contact is only released after
       RELEASE_FRAMES (60) without any soldiers touching (immediately if withdrawing).
PUSH:  every 5 frames the line shifts toward the weaker side:
         drive = sum(momentum of touching soldiers)   only if formation is attacking
         hold  = sum(brace of touching soldiers)       halved while attacking
         both scaled by depth = 1 + 0.15 * min(4, supporters / touchers)
         net = max(0, driveA - holdB) - max(0, driveB - holdA)
         shift = clamp(net / touchers * 0.5, +-1.5 elmos per tick)
       Walking into a braced line doesn't move it; a charge (2x momentum) or a deeper
       formation pushing does. Equal formations both pushing grind in place.
       The line also eases toward where soldiers actually meet (physics stays honest).

REAR-RANK GAP: in a single clash, soldiers in rank > 0 who aren't touching an enemy
       stand REAR_GAP (6) further back, giving the fighting front rank room.
       Disabled when a formation is in contact on more than one side, so a surrounded
       formation isn't squeezed toward the second enemy.

ATTACK APPROACH: an attack order walks the formation to a stand-off point in front of
       the enemy (front-to-front APPROACH_GAP = 20), not to the enemy's center.
       Once touching, the contact line takes over.

================================================================================
ENGAGEMENT LOCK SYSTEM
================================================================================

FORMATION LEVEL (Session 3, 060_formation_controller.lua):
- Move/attack orders given within LOCK_FRAMES (60) of a clash are HELD and executed
  when the lock expires.
- After the lock: a move order (or attack on a different enemy) starts a WITHDRAWAL.
  Every soldier still touching an enemy gives that enemy a free hit
  (GG.Bracing.PenalizeDisengage), the formation stops pushing, keeps facing the
  enemy and backs off at half speed while still touching (full speed once free).
  Pursuers that catch up fight the rear but don't cancel the withdrawal.
- Attack order on the enemy you're already clashing with = lean in and push.
- Stop while clashing = stop pushing and brace in place.

SOLDIER LEVEL (original, still active for non-formation orders):

When soldiers enter melee range (ENGAGE_RANGE = 50):
1. Engagement lock activates for LOCK_DURATION_FRAMES (60 frames = 2 sec)
2. During lock: soldier cannot receive move orders (blocked by AllowCommand)
3. Lock purpose: prevent instant disengagement, create "sticky" melee

After lock expires:
- Soldier CAN move, but will take disengage penalty if enemies still nearby

DISENGAGE PENALTY:
- When soldier moves away with enemies in ENGAGE_RANGE
- Each nearby enemy gets a "free hit"
- Damage = BASE_DISENGAGE_DAMAGE * DISENGAGE_BONUS_DAMAGE (7.5 damage)
- Discourages backing out of melee recklessly

RE-ENGAGEMENT:
- If soldier issues attack/fight command after lock expires
- Lock resets - they're committed to fighting again

================================================================================
ORDER FLOW
================================================================================

1. Player clicks destination
2. Selection widget sees order, lets it proceed
3. Formation controller's AllowCommand intercepts:
   - If it's a formation soldier: BLOCK the order
   - Call GG.Formation.OrderMove(formationID, x, z) instead
4. Formation controller updates formation.moveDestX/Z
5. Every 5 frames, UpdateFormations runs:
   - Advance formation.targetX/Z toward destination
   - Calculate each soldier's slot position
   - Issue internal move orders to soldiers
6. Bracing system's AllowCommand:
   - Checks GG.Formation.isIssuingInternalOrder flag
   - If true: allow the order (it's from formation controller)
   - If false and soldier is engaged: block external orders

================================================================================
ATTACK BEHAVIOR
================================================================================

When formation attacks another formation (Session 3):
1. Set formation.attackTarget = enemyFormationID
2. Formation approaches to a front-to-front stand-off point, facing the enemy
3. Front ranks touch -> contact line created (see CONTACT LINE SYSTEM)
4. Both formations are anchored to the line; soldiers in reach swing
5. Push vs brace moves the line; attrition and depth decide who gives ground

The battle line forms NATURALLY:
- Center of mass shifts as soldiers push/get pushed
- Slots recalculate based on new center
- Soldiers keep trying to reach slots -> natural pressure

================================================================================
FORMATION COHESION SYSTEM  (DISABLED in Session 3 - superseded by contact lines)
================================================================================

Cohesion creates invisible "bonds" between adjacent soldiers in a formation.
Enemies trying to pass between bonded soldiers face resistance.

1. NEIGHBOR BONDS
-----------------
- Soldiers in the same rank with adjacent file positions are "neighbors"
- The gap between neighbors creates a cohesion zone
- Enemies entering this zone face resistance based on cohesion vs momentum

2. COHESION STRENGTH
--------------------
- Each soldier has a cohesion_strength value (customParams)
- The bond strength = average of both soldiers' cohesion
- Higher cohesion = harder for enemies to break through

Unit values (customParams):
  Swordsman: cohesion_strength = "15"  -- Disciplined infantry
  Archer:    cohesion_strength = "8"   -- Loose formation

3. BREAKING THROUGH
-------------------
Calculate: momentumRatio = enemyMomentum / cohesionStrength

- momentumRatio < 0.7: Strong resistance, enemy pushed back with impulse
- momentumRatio 0.7-1.0: Partial resistance, enemy slowed
- momentumRatio >= 1.0: Enemy breaks through freely

This means:
- Walking into a line: pushed back (momentum 8 vs cohesion 15 = 0.53 ratio)
- Charging into a line: breaks through (momentum 16+ vs cohesion 15 = 1.0+ ratio)

4. COHESION MECHANICS
---------------------
- Runs every 2 frames
- Detects enemies in the gap zone between neighbors
- Applies impulse + velocity dampening to push enemies out
- Has cooldown (5 frames) per enemy to prevent jitter
- Only affects the velocity component moving INTO the gap

5. CONSTANTS
------------
Formation Cohesion (070_formation_cohesion.lua):
  COHESION_CHECK_INTERVAL = 2    -- Frames between checks
  NEIGHBOR_MAX_DISTANCE = 35     -- Max dist for neighbor bond
  COHESION_ZONE_DEPTH = 24       -- Width of gap zone
  IMPULSE_STRENGTH = 8.0         -- Push strength
  COHESION_BREAK_THRESHOLD = 0.7 -- Below this = strong resistance
  PUSH_COOLDOWN_FRAMES = 5       -- Frames between pushes per unit

6. FUTURE ABILITIES
-------------------
- Wedge Formation: Soldiers at the point get momentum bonus to break cohesion
- Shield Wall: Increased cohesion_strength while stationary
- Loose Formation: Reduced cohesion but faster movement
- Pike Square: Very high cohesion, nearly unbreakable from front

================================================================================
AUTO-ENGAGE SYSTEM (DEFENSIVE COUNTER-PUSH)
================================================================================

Idle formations automatically push back when taking melee damage.
This is a DEFENSIVE reaction, not proactive aggression.

1. TRIGGER CONDITIONS
---------------------
- Formation is idle (no move/attack orders)
- A soldier in the formation takes melee damage (attacker within 60 units)
- Formation remembers being attacked for 2 seconds

2. BEHAVIOR
-----------
When triggered:
- Formation finds nearest enemy formation
- Nudges target position toward enemy (creates forward pressure)
- Updates facing toward enemy
- Soldiers push toward their slots -> counter-push

When not triggered:
- Target stays at center of mass
- Formation holds position passively

3. CONSTANTS
------------
AUTO_ENGAGE_RANGE = 80        -- Range to detect enemies
AUTO_ENGAGE_CHECK = 15        -- Frames between checks
COUNTER_PUSH_STRENGTH = 0.3   -- How strongly to push back (30%)
DAMAGE_MEMORY_FRAMES = 60     -- How long to remember being attacked (2 sec)

================================================================================
FORMATION ACTOR SYSTEM
================================================================================

Each formation has an invisible "actor" unit at its center that can be clicked
to target the formation with attack orders.

1. ACTOR UNIT (formation_actor.lua)
-----------------------------------
- Invisible unit positioned at formation center of mass
- Can be selected and targeted by players
- Does NOT block movement (blocking = false)
- Does NOT interact physically with soldiers
- High health (10000) so it doesn't die accidentally

2. TARGETING FORMATIONS
-----------------------
To attack an enemy formation:
- Right-click on any enemy soldier -> attacks that soldier's formation
- Right-click on formation actor -> attacks that formation

The AllowCommand intercepts attack orders and translates them to
GG.Formation.OrderAttack(myFormationID, enemyFormationID)

3. ACTOR LIFECYCLE
------------------
- Created when first soldier registers with a formation
- Position updated every REPOSITION_INTERVAL frames
- Destroyed when formation has no soldiers left

================================================================================
COMBAT SYSTEM
================================================================================

Soldiers attack nearby enemies when in melee range AND their formation is engaged
(in contact with an enemy formation) and not routing. Loose soldiers outside any
formation fight freely.

1. AUTO-ATTACK
--------------
- Every 3 frames, soldiers check for enemies within MELEE_RANGE (55 units)
- If enemy soldier found, attack timer counts down
- When timer reaches 0, deal damage and reset timer

2. DAMAGE (Session 3: read from GG.Stats + GG.Ability)
---------
- hit = max(1, melee_damage * meleeDamageMul - target armor)
- swing time = melee_cycle * meleeCycleMul
- Swordsman: 8 dmg, armor 2 -> 6 per hit vs swordsmen, 1.5s cycle, 30 HP = 5 hits
- Damage is applied with Spring.AddUnitDamage (fires UnitDamaged, credits kills)

3. REGISTRATION
---------------
Combat system uses explicit registration from spawner:
  GG.Combat.RegisterSoldier(unitID, teamID)

This avoids timing issues with UnitCreated callbacks.

================================================================================
UNIT COLLISION VOLUMES
================================================================================

Units use cylindrical (circular) collision volumes for smoother interactions:

Swordsman:
  collisionVolumeType = "CylY"
  collisionVolumeScales = "12 24 12"   -- diameter, height, diameter
  collisionVolumeOffsets = "0 12 0"    -- centered vertically

Archer:
  collisionVolumeType = "CylY"
  collisionVolumeScales = "10 22 10"   -- slightly smaller
  collisionVolumeOffsets = "0 11 0"

Circular hitboxes prevent units from getting stuck on square corners.

================================================================================
SPAWNER CONFIGURATION
================================================================================

Current test setup (055_game_spawner.lua):
- 2 regiments per team (120 soldiers each side)
- 60 soldiers per regiment (12 wide x 5 deep)
- Formations face each other across the battlefield
- No initial orders - player must command

SOLDIERS_PER_REGIMENT = 60
RANK_WIDTH = 12
SOLDIER_SPACING = 16
RANK_DEPTH = 20
BATTLE_SEPARATION = 600

================================================================================
CONSTANTS SUMMARY
================================================================================

Formation Controller (060_formation_controller.lua):
  REPOSITION_INTERVAL = 5      -- Frames between updates
  SLOT_TOLERANCE = 8           -- Distance before moving toward slot
  FORMATION_MOVE_SPEED = 4.0   -- How fast formation advances
  SOLDIER_SPACING = 16         -- Elbow-to-elbow
  RANK_DEPTH = 20              -- Front-to-back
  RANK_WIDTH = 12              -- Soldiers per rank
  AUTO_ENGAGE_RANGE = 80       -- Detection range for counter-push
  AUTO_ENGAGE_CHECK = 15       -- Frames between auto-engage checks
  COUNTER_PUSH_STRENGTH = 0.3  -- Counter-push force multiplier
  DAMAGE_MEMORY_FRAMES = 60    -- Remember damage for 2 seconds

Combat Bracing (065_combat_bracing.lua):
  ENGAGE_RANGE = 50            -- Range to become engaged
  DISENGAGE_RANGE = 70         -- Range to break engagement
  LOCK_DURATION_FRAMES = 60    -- 2 seconds at 30 FPS
  DISENGAGE_BONUS_DAMAGE = 1.5 -- 50% bonus on free hits
  BASE_DISENGAGE_DAMAGE = 5    -- Base damage for penalty hit
  MOMENTUM_DECAY_RATE = 0.95   -- Per-frame decay
  CHARGE_VELOCITY_THRESHOLD = 1.5
  CHARGE_MOMENTUM_BONUS = 2.0

Formation Cohesion (070_formation_cohesion.lua):
  COHESION_CHECK_INTERVAL = 2
  NEIGHBOR_MAX_DISTANCE = 35
  COHESION_ZONE_DEPTH = 24
  IMPULSE_STRENGTH = 8.0
  COHESION_BREAK_THRESHOLD = 0.7
  PUSH_COOLDOWN_FRAMES = 5

Formation Contact (058_formation_contact.lua):
  CONTACT_RANGE = 40, CONTACT_MIN_TOUCHES = 2, RELEASE_FRAMES = 60
  LINE_PUSH_RATE = 0.5, MAX_LINE_SHIFT = 1.5, DEPTH_BONUS = 0.15
  DEPTH_RATIO_CAP = 4, PUSHING_BRACE_MUL = 0.5

Formation Controller contact settings:
  FRONT_OFFSET = 10, REAR_GAP = 6, LOCK_FRAMES = 60, STICKY_SPEED_MUL = 0.5

ModRules (gamedata/modrules.lua):
  allowPushingEnemyUnits = false
  allowUnitCollisionOverlap = false

================================================================================
FILE RESPONSIBILITIES
================================================================================

050_regiment_manager.lua (layer 50):
  - Tracks soldiers belonging to regiments
  - Morale tracking and state
  - Provides GG.Regiment API

055_game_spawner.lua (layer 55):
  - Spawns soldiers in formation at game start
  - Sets regimentID, slotIndex, rankIndex, fileIndex rules params
  - Registers soldiers with Formation and Cohesion systems
  - Does NOT issue individual movement orders

060_formation_controller.lua (layer 60):
  - CORE SYSTEM - formation-centric movement
  - Intercepts and blocks external soldier orders
  - Translates orders to formation-level commands
  - Every 5 frames: advances formation, issues slot moves
  - Auto-engage: counter-push when taking melee damage
  - Provides GG.Formation API

065_combat_bracing.lua (layer 65):
  - Engagement lock system
  - Disengage penalties
  - Momentum tracking for charges
  - Allows internal formation orders through
  - Provides GG.Bracing API

070_formation_cohesion.lua (layer 70):
  - Creates bonds between adjacent soldiers
  - Pushes enemies out of gap zones
  - Prevents formations from passing through each other
  - Provides GG.Cohesion API

gui_regiment_selection.lua (widget):
  - Click soldier -> select entire regiment
  - Draws formation center marker
  - Lets orders proceed (synced code handles them)

================================================================================
NATURAL BATTLE FLOW
================================================================================

1. Formations spawn facing each other, idle
2. Player orders their formation to attack
3. Formation target moves toward enemy center of mass
4. Soldiers advance toward their slots (which are moving toward enemy)
5. Front ranks collide - cohesion prevents passing through
6. Idle enemy formation takes melee damage -> auto-engage triggers
7. Both formations now push toward each other
8. Push vs Brace + Cohesion determines battle line position
9. Center of mass shifts as soldiers push/get pushed
10. Slots recalculate, soldiers keep trying to reach them
11. Natural forward pressure from rear ranks
12. Battle resolves through attrition and morale (future)

================================================================================
SESSION 2 CHANGES (2026-02-02)
================================================================================

1. Fixed formation controller not loading (Lua 5.1 doesn't support goto)
2. Fixed soldier registration timing (spawner now explicitly registers)
3. Added circular collision volumes (CylY) for smoother unit interaction
4. Implemented formation cohesion system:
   - Invisible bonds between adjacent soldiers
   - Enemies pushed out of gap zones
   - Momentum can break through cohesion
5. Added auto-engage (defensive counter-push):
   - Idle formations attack back when soldiers take melee damage
   - Triggers after 2 second delay
   - Issues actual attack order on enemy formation
6. Increased formation move speed (4.0) to keep soldiers chasing
7. Added 2 regiments per side for better testing
8. Replaced placeholder model with human-sized block
9. Added Formation Actor units:
   - Invisible clickable unit at formation center
   - Can be targeted for attack orders
   - Moves with formation center of mass
   - Does not block movement or interact physically
10. Fixed combat system:
    - Soldiers auto-attack enemies within melee range (55 units)
    - Uses explicit registration from spawner
    - Only targets other soldiers (not actors)
    - Reads damage from unit customParams

================================================================================
MORALE & ROUTING (210_unit_morale.lua) - Session 3
================================================================================

Regiment morale starts at the soldiers' morale_start (default 80). Lowered by:
  - each man lost: 1.0 (scaled to a 60-man regiment)
  - heavy losses (10% of starting men within 4s): 3/s while it lasts
  - losing the exchange in melee (our dead > 1.5x enemy dead we caused, last 10s): 1/s
  - enemy regiment on our flank: 1.5/s each; in our rear: 3/s each
  - fighting below 50% strength: 0.5/s
  - an allied regiment routing within 500 elmos: -8 at once (cascades)
Recovers 2/s out of contact once no man has been lost for 5s (up to morale_start).

States: STEADY -> ENGAGED (in contact) -> WAVERING (< 40) -> ROUTING (<= 20)
SHATTERED: below 25% of starting men. Cannot rally; leaves the battlefield on
reaching safety (soldiers removed), so a broken army loses the battle.

Routing (GG.Formation.Rout): men touching an enemy take disengage free hits, the
regiment stops fighting and bracing, ignores player orders and runs at 90% speed
to a safe zone 700 elmos behind its army's spawn point. Pursuers (charging at
100%) catch and cut them down.
Rally (GG.Formation.Rally): not shattered, routing >= 5s, out of contact and
morale >= 45 -> STEADY, holds where it stands, takes orders again.

UI: gui_regiment_morale draws a morale bar over each regiment (frame in team
color) with WAVERING / ROUTING / SHATTERED labels (game rules params morale_<id>).

================================================================================
RANGED COMBAT (205_ranged_combat.lua) - Session 3
================================================================================

Archers fire real engine projectiles (weapons/arrow.lua, referenced from the unit
def by name = "arrow"). The engine aims, flies and resolves hits; arrows can miss
or strike the man next to the one aimed at. Lua decides who and how hard:

TARGET REGIMENT (every 15 frames, per archer regiment):
  - none while routing or locked in melee (archers fight hand to hand instead)
  - the player's attack target, if within range + 60 of the regiment center
  - otherwise "fire at will": the nearest enemy regiment in range
  - never a regiment that is in melee with our own troops (no friendly fire yet)
  AllowWeaponTarget only lets the engine pick soldiers of that regiment.

DAMAGE (UnitPreDamaged): ranged_damage * rangedDamageMul - target armor, min 1,
no knockback. Reload = ranged_reload * rangedReloadMul; spread = base spray /
(accuracy * accuracyMul) - applied per archer with SetUnitWeaponState, so
"Shoot Fast" works.

MOVEMENT: an attack order with a ranged regiment advances to 75% of range from
the target (RANGED_STANDOFF) so every rank reaches the whole block, then halts
facing it; it advances again if the target gets 30+ further away. Archers have
movestate = 0 so the engine never sends them after targets on its own.

AIMING: the engine may pick any soldier of the target regiment but prefers those
nearest its center of mass, with per-archer jitter (AllowWeaponTarget priority),
so volleys land as a cloud centered on the block (~+-60 sideways, +-35 deep).
Targets are sticky: archers keep shooting a regiment after it gets engaged by our
troops; fire at will only avoids picking a NEW target already in a melee with us.

FIRE MODES (per archer, every 15 frames; unit highTrajectory = 2, CMD.TRAJECTORY):
  LOB    - high arc (~60 deg). Used when a soldier of ANOTHER friendly regiment
           stands within 11 elmos of the archer's flight path, up to the
           target's near edge. Own comrades don't count (they kneel).
  DIRECT - low and flat (~23 deg). Penetrates: only half the target's armor counts.
  Shooting over our own melee line -> lob; from the flank/rear of an enemy pinned
  against our melee line -> the whole block fires direct.
FRIENDLY FIRE: arrows pass through friends in flight (collideFriendly = false),
so nobody shoots his own front rank; an arrow that LANDS within 7 elmos of a
friendly soldier hits him (Explosion callin). Fire at will avoids melees with
our troops; an ordered target in melee is shot at anyway - at that risk.

ARROW PHYSICS: avoidFriendly = false; v=490, myGravity=0.4 (max reach ~670, just
past range 600, so the two arcs stay moderate). Reload 10s (playtest). Soldier hit volumes are body-sized cylinders
(swordsman 14x20, archer 13x19) - wider than the 9-elmo model - because arrows
landing in the open files between men otherwise hit only ~10%.

TUNING (60 archers vs an idle 60-man sword block at ~470 elmos):
  ~24 arrows/s, ~16% hit, 6 dmg per hit (8 - 2 armor), 7 hits per kill
  -> ~0.55 kills/s: archers wear a regiment down, melee finishes it.
  Dials: ranged_damage, ranged_reload, accuracy (unit def); sprayAngle (arrow).

================================================================================
CAVALRY & SKIRMISH - Session 3
================================================================================

CAVALRY (units/soldier_cavalry.lua, 40 riders, 10 wide): 102 elmos/s top speed
(swords 51, archers 66), so ~76 marching. charge_bonus 2.5: blows struck while
charging do 2.5x before armor. A charge only builds BEFORE contact (bracing:
momentum doesn't start charging while engaged), lasting ~0.5s after impact.
Momentum 20 x 2 (charging) x mass 2 = 80 vs a swordsman's brace -> shoves lines.
Poor brace (6), 7 damage (4 vs armor 3), 80 HP: wins the charge, loses the grind
-> charge, pull out, charge again (flank/rear charges add morale penalties).
Heavy units keep charge momentum longer (decay 0.95^(1/mass) per frame).

IMPACT (058/060): an attacker that clips a corner slides along the contact line
(3 elmos/update) until it faces the enemy squarely. A side that overmatches the
enemy's brace (or is still charging) drives its front INTO the enemy block:
penetration up to 30 elmos (1.5 ranks), +3/tick, eases back 1/tick. Men struck by
a charge get a small knockback impulse. Cavalry vs archers (brace 6): riders get
in among them and break them in ~12s; vs swordsmen (brace 12) they barely get in.

ARCHERS HOLD FIRE ON THE MOVE: fire control treats marching/forming up as no
target and sets the fire state to hold. customParams fire_on_move = "true" (future
horse archers) bypasses it.

SKIRMISH STANCE (command panel): enemy melee that is moving/attacking within 220
elmos of the regiment's edge -> run 300 away at full speed, facing the threat
(GG.Formation.SkirmishMove), halt, shoot. Idle enemies don't trigger it.
Player orders override. Bog/water slow walking units - don't skirmish into ponds.

================================================================================
BATTLE AI (400_battle_ai.lua) - Session 3
================================================================================

Commands every AI-controlled team (whatever AI the start script names). After a
10s deployment pause, every 3s:
  swords   - attack the nearest standing enemy regiment
  archers  - Skirmish stance; shoot at will; advance to range if nothing is close
  cavalry  - hunt enemy archers, else an enemy already locked in melee (flank
             charge), else the nearest; hit and run: after 6s in a melee or once
             wavering, ride 350 back, regroup 4s, charge again
Routing regiments are left alone; regiments in melee keep fighting.
Scripted tests disable it: Spring.SetGameRulesParam("battle_ai_disabled", 1).

================================================================================
PACE, FATIGUE, SPEARMEN, POINTS - Session 3
================================================================================

PACE (060): plain orders = SLOW MARCH (50% of top speed); Alt + any order (click,
drag line, group move) = JOG (80%). Attacks charge (100%) in the last 150 elmos;
skirmish runs 100%; routs 90%. Fatigue also slows every pace (up to -20%).

FATIGUE (215_fatigue.lua), per regiment 0..100, per second:
  standing -0.4, slow march -0.15, jog +0.35, melee +0.5, charge/run/rout +1.0
  Effects at full fatigue (scaled smoothly): melee damage x0.75, swing time
  x1.30, speed x0.80, bracing x0.70, archer reload x1.20; above 85 morale drains
  0.3/s while fighting. UI: thin bar under the morale bar + Winded / Tired /
  Very Tired / Exhausted labels.

SPEARMEN (units/soldier_spearman.lua, 110 pts): brace 22; vs charging attackers
or cavalry hitting their FRONT they brace x3 and riders get no charge bonus;
spear blows x2.5 vs cavalry; weak in a straight fight vs swords; ordinary
from the flank. AI spearmen turn on cavalry within 400.

POINTS: regiment_cost per unit (swords 120, spears 110, archers 100, cavalry
160 for 40 riders). Army strings (army_team0/1) take S/P/A/C; the spawner logs
each army's points. Standard battle: SSSPPAAACC = 1200 points (~560 men).

PERFORMANCE (two 1200-point armies, ~1100 soldiers): cohesion disabled;
002_unit_cache for team/ally/regiment lookups; bracing/melee scans staggered;
standing formations only re-order soldiers whose slot moved.

================================================================================
SESSION 3 CHANGES (2026-10-12)
================================================================================

1. customParams keys are lowercased by the engine - all reads now use lowercase
   (bracing had never registered a soldier before this)
2. Lua unit script framework enabled (000_unit_script.lua); killed soldiers used to
   linger forever as invisible blocking corpses
3. Damage via AddUnitDamage so auto-engage actually triggers
4. Slot math fixed: ranks were in front of the formation for north/south facings,
   and the target started at the first soldier instead of the layout center
5. Contact line system: glue, repel, push vs brace, rear-rank gap
6. Formation-level contact lock, withdrawal with disengage penalty
7. Combat gated on formation engagement; damage/cycle/armor/abilities from stats
8. Closing ranks on casualties
9. /luarules reload rebuilds formation/cohesion/combat state without respawning
10. Drag-line orders (reshape in place, then march), group right-click moves,
    reliable attack targeting (through trees, between soldiers)
11. March at 75% of soldier speed, charge at 100% near the enemy
12. Morale & routing live (regiment manager registration fixed)
13. Archers: real arrows, regiment-level fire control, ranged approach;
    test army has an archer regiment behind each side's swords
14. Lob/direct fire modes, landing friendly fire, direct-fire armor penetration
15. A side whose army is entirely routing loses immediately
16. Projectile/ground-FX textures restored (removed empty gamedata/resources.lua)
17. One range circle per selected ranged regiment
18. Slower, tougher swordsmen (51 elmos/s, armor 3, 70 HP)
19. Archers hold fire on the move; Skirmish stance kites melee
20. Cavalry with charge bonus; test army has a cavalry regiment on each flank
21. Cavalry smash into weak lines (alignment, penetration, knockback)
22. Battle AI for AI-controlled teams
23. Point costs, 1200-point armies, spearmen, fatigue, slow march / jog

================================================================================
FUTURE CONSIDERATIONS
================================================================================

- Rear rank pushing creates natural pressure on front
- Flanking attacks hit formations from side (different facing)
- Cavalry charges: high momentum, can break through lines
- Shield wall formations: very high brace, low mobility
- Routing: formation dissolves, soldiers flee individually
- Rally: soldiers reform around a new center point
- Wedge formation: concentrated momentum at point
- Morale system integration with formation cohesion

================================================================================
END OF DESIGN DOCUMENT
================================================================================
