# FREEEXILE: SERVER ENGINE & COMBATACTOR INTEGRATION SURVEY REPORT

> **Document Type**: Architecture & Codebase Investigation Report  
> **Author**: `explorer_survey_2` (teamwork_preview_explorer / Engine Investigator)  
> **Target Project**: FreeExile (Grimdark Savage ARPG Server Backend)  
> **Target Request**: Character Stat Aggregator Core, Modifier System, Formula Persistence & Server Engine Integration (Request `2026-10-04T09:01:16Z`)  
> **Integrity Mode**: Read-Only Survey (Zero Source Code Mutation)  
> **Date**: October 4, 2026 (UTC)  

---

## 1. EXECUTIVE SUMMARY & MISSION SCOPE

This report provides the exhaustive technical survey of the Server Engine loop (`server/world/server_engine_loop.py`), `CombatActor` runtime structures (`server/world/combat_engine.py`), player registration mechanics, and combat/movement stat consumption across FreeExile.

### Core Discoveries at a Glance
1. **Hardcoded Stats Located**:
   - In `server/world/server_engine_loop.py:105-113`, `CombatActor` is instantiated with hardcoded `base_attack = 50.0`.
   - Default arguments in `register_player` (`max_hp = 1000.0`, `move_speed = 6.0`, `element = FiveElements.KIM`) override real character progression unless explicitly overridden.
   - Secondary combat stats (`crit_chance = 0.05`, `crit_multiplier = 1.5`, `resistances = {}`, `current_energy = 100.0`, `max_energy = 100.0`) are left as static dataclass defaults on `CombatActor`.
2. **Combat Pipeline Scaling & Damage Formulas**:
   - In `server/world/combat_engine.py:323`, skill raw damage scales directly off `attacker.base_attack` via `raw_dmg = eff_damage * (attacker.base_attack / 50.0)`. A character with 150 base attack deals $3.0\times$ skill damage.
   - Elemental resistance mitigations clamp effective resistance to $[-50\%, +75\%]$ in `_calculate_mitigated_damage` (`combat_engine.py:160`).
   - Movement speed on `PlayerCharacter.move_speed` in `movement_authority.py:76` dictates server anti-speedhack tolerance. If gear/meridian grants movement speed and `PlayerCharacter.move_speed` is not updated, legitimate players are falsely flagged for speedhacking and rubberbanded.
3. **Player Join / Zone Enter Flow & Data Access Gaps**:
   - `CharacterService` (`server/auth/character_service.py`) stores base account/character records.
   - `InventoryService` (`server/inventory/inventory_service.py`) manages equipment in `CharacterInventory.equipment: Dict[EquipmentSlot, InventoryItem]`, holding `affixes: List[Affix]`.
   - `MeridianService` (`server/world/meridian_service.py`) tracks passive trees and socketed jewels in `character_meridians` SQLite table, exposing `compute_total_stats()`.
   - **The Disconnect**: `server_engine_loop.register_player` receives only primitive floats (`entity_id`, `initial_x`, `initial_y`) and has zero reference to `InventoryService` or `MeridianService`. Although `MeridianService` had an ad-hoc `apply_to_combat_actor` helper, it operated in isolation, ignored equipment affixes completely, and lacked formula persistence.
4. **Clean Integration Blueprint**:
   - A non-breaking extension of `ServerEngineLoop.register_player` accepting optional `player_id`, `account_id`, and `aggregated_stats` allows `CharacterStatAggregator` to inject calculated stats seamlessly while retaining 100% backward compatibility for all 12 existing engine loop test cases.

---

## 2. DETAILED INVESTIGATION OF `server_engine_loop.py` & `register_player`

### 2.1 File Overview & Runtime Lifecycle
- **Path**: `server/world/server_engine_loop.py` (236 lines, complies with $\le 350$ lines Soft Cap).
- **Architecture**: Authoritative 30Hz fixed simulation loop (33.33ms per tick).
- **Core Subsystems**:
  - `self.spatial_grid = SpatialGrid(cell_size=cell_size)`
  - `self.movement_authority = MovementAuthorityEngine()`
  - `self.combat_engine = CombatEngine()`

### 2.2 Verbatim Inspection of `register_player`
Lines 86–114 in `server/world/server_engine_loop.py`:
```python
    def register_player(
        self,
        entity_id: int,
        initial_x: float,
        initial_y: float,
        initial_z: float = 0.0,
        element: FiveElements = FiveElements.KIM,
        move_speed: float = 6.0,
        max_hp: float = 1000.0
    ) -> None:
        """Registers a player into all authoritative subsystems."""
        grid_entity = Entity(entity_id=entity_id, x=initial_x, y=initial_y)
        self.spatial_grid.add_entity(grid_entity)
        self.active_entities[entity_id] = grid_entity
        self.entity_z_levels[entity_id] = initial_z

        player_char = PlayerCharacter(entity_id=entity_id, x=initial_x, y=initial_y, move_speed=move_speed)
        self.movement_authority.register_player(player_char)

        combat_actor = CombatActor(
            actor_id=entity_id,
            name=f"Player_{entity_id}",
            element=element,
            current_hp=max_hp,
            max_hp=max_hp,
            base_attack=50.0
        )
        self.combat_engine.register_actor(combat_actor)
```

### 2.3 Analysis of Hardcoded & Uninitialized Stats
| Parameter / Field | Source in Code | Current Value | Problem / Architecture Implication |
| :--- | :--- | :--- | :--- |
| `base_attack` | `server_engine_loop.py:111` | **`50.0` (Hardcoded literal)** | Completely ignores weapon base damage, weapon type (2H +50% More), weapon implicits, and item/meridian flat/percent damage affixes. |
| `max_hp` | `server_engine_loop.py:94` | `1000.0` (Default arg) | Ignores STR base scaling (+1 HP/pt), `pref_life` affixes, and Meridian node HP bonuses (+200 HP from Origin `m_c1`, +500 HP from Keystone `m_n_keystone`). |
| `move_speed` | `server_engine_loop.py:93` | `6.0` (Default arg) | Ignores boots move speed affix (`suff_move_spd`) and Meridian movement nodes (`m_e4` +6%, `m_e_keystone` +8%). |
| `crit_chance` | `CombatActor` default | `0.05` (5.0%) | Ignores DEX scaling (+0.1%/pt), `suff_crit_chance` affixes, and Meridian crit nodes (+12% from `jewel_gold_thunder`). |
| `crit_multiplier`| `CombatActor` default | `1.50` (150%) | Ignores `suff_crit_multi` affixes (+65%) and Meridian crit damage nodes (`m_e3` +18%, `jewel_gold_thunder` +35%). |
| `resistances` | `CombatActor` default | `{}` (Empty dict / 0%) | Ignores all elemental resistance affixes (`suff_fire_res`, `all_res`) and Meridian resistance bonuses (`m_c1` +15, `m_w3` +20). |
| `is_player` | `CombatActor` default | `False` | Fails to explicitly flag entity as player, potentially bypassing progression/death penalty hooks in `attach_progression_service` (`combat_engine.py:113-118`). |
| `player_id` | `CombatActor` default | `None` | Prevents downstream systems from resolving player account/progression states by string UUID. |

---

## 3. `CombatActor` DEFINITION & COMBAT PIPELINE STAT USAGE

### 3.1 `CombatActor` Dataclass Model
Defined in `server/world/combat_engine.py:20-43`:
```python
@dataclass
class CombatActor:
    actor_id: int
    name: str
    element: FiveElements
    current_hp: float = 1000.0
    max_hp: float = 1000.0
    base_attack: float = 50.0
    crit_chance: float = 0.05
    crit_multiplier: float = 1.5
    resistances: Dict[FiveElements, float] = field(default_factory=dict)
    last_evasion_timestamp_ms: int = -1000
    evasion_iframe_duration_ms: int = 250  # 0.25s i-frame window
    is_player: bool = False
    level: int = 1
    player_id: Optional[str] = None
    # Sprint 2: Weapon Sets, Energy, Animation Lock & Cooldowns
    weapon_set_1: Any = "SWORD"
    weapon_set_2: Any = "PROJECTILE_WEAPON"
    active_weapon_set: int = 1
    current_energy: float = 100.0
    max_energy: float = 100.0
    animation_locked_until_ms: int = 0
    cooldowns: Dict[int, int] = field(default_factory=dict)
```

### 3.2 Where and How Stats Are Used in Runtime Combat

#### 1. Damage Calculation Pipeline (`combat_engine.py:149-231`)
- **Elemental Overcoming (Ngũ Hành Tương Khắc)**:
  $$\text{elem\_mult} = 1.25 \quad \text{if attacker overcomes defender, else } 1.0$$
- **Resistance Mitigation**:
  Lines 159–161:
  $$\text{effective\_res} = \min(0.75, \max(-0.50, \text{defender.resistances.get}(\text{element}, 0.0)))$$
  $$\text{mitigated} = \text{raw\_damage} \times \text{elem\_mult} \times (1.0 - \text{effective\_res})$$
- **Critical Strike**:
  Lines 163–165: If critical strike triggers, `mitigated *= attacker.crit_multiplier`.
- **Health Depletion & Lethality**:
  Lines 213–215:
  $$\text{new\_hp} = \max(0.0, \text{defender.current\_hp} - \text{final\_damage})$$
  $$\text{is\_fatal} = (\text{was\_alive}) \land (\text{new\_hp} \le 0.0)$$

#### 2. Skill Cast Execution Pipeline (`combat_engine.py:271-343`)
- **Attack Power Ratio**:
  Line 323 is the pivotal scaling formula:
  $$\text{raw\_dmg} = \text{eff\_damage} \times \left(\frac{\text{attacker.base\_attack}}{50.0}\right)$$
  *Evidence*: A base attack of 50.0 results in $1.0\times$ multiplier; a base attack of 150.0 results in $3.0\times$ multiplier. Leaving `base_attack = 50.0` completely neutralizes all gear scaling!
- **Resource Depletion**:
  Line 316: `attacker.current_energy = max(0.0, attacker.current_energy - eff_cost)`.
- **Animation Lock & Cooldowns**:
  Lines 317–318: Enforces lock duration and per-skill cooldown timestamps.

#### 3. Evasion & Invulnerability (Huyễn Ảnh Bộ)
- `combat_engine.py:182-187`: If $0 \le (\text{now\_ms} - \text{defender.last\_evasion\_timestamp\_ms}) \le 250\text{ms}$, incoming damage is 100% evaded (`is_evaded = True`, damage $= 0.0$).

#### 4. Movement Displacement & Anti-Cheat Validation (`movement_authority.py:54-88`)
- **Displacement**: $\Delta x = \text{norm\_x} \times \text{player.move\_speed} \times dt$.
- **Speedhack Threshold**:
  $$\text{max\_allowed\_dist} = (\text{player.move\_speed} \times dt) \times \text{tolerance\_multiplier}$$
  *Evidence*: If player moves faster than `player.move_speed * 1.15`, the engine flags suspicion and rubberbands the player back to their last known authoritative coordinate!

---

## 4. PLAYER JOIN & ZONE ENTER FLOW: DATA ACCESS & GAP ANALYSIS

### 4.1 End-to-End Player Join Sequence
```
Client (Cocos / WebApp)
   │
   ├─► 1. Connect ws://127.0.0.1:8080 (ws_gateway_bridge.py)
   ├─► 2. Authenticate Session (authoritative_gateway_service.py)
   │      - Resolves account_id, character_id, entity_id
   │
   ├─► 3. Zone Transition / Map Enter (zone_engine.py)
   │      - Spawns player location: spawn_player(player_id, zone_id)
   │      - Enforces Sanctuary safe havens / Waypoint safe radius 8.0m
   │
   ▼
ServerEngineLoop.register_player(entity_id, initial_x, initial_y, ...)
   │
   ├── [SpatialGrid] ────────► Adds Entity(entity_id, x, y)
   ├── [MovementAuthority] ──► Registers PlayerCharacter(move_speed = 6.0)
   └── [CombatEngine] ───────► Registers CombatActor(base_attack = 50.0) [GAP!]
```

### 4.2 How Data Is Currently Structured Across Services

#### 1. Character Identity (`server/auth/character_service.py:26-44`)
- `Character`: `character_id: str`, `account_id: str`, `name: str`, `class_type: CharacterClass`, `level: int`, `season_id: str`, `current_zone_id: str`.

#### 2. Inventory & Equipment (`server/inventory/inventory_service.py:61-150`)
- `InventoryService.get_character_inventory(account_id, character_id) -> CharacterInventory`.
- `CharacterInventory.equipment`: Mapping `EquipmentSlot -> InventoryItem`.
- `InventoryItem`:
  - `item_uuid: str`, `item_id: str`, `rarity: ItemRarity`, `item_level: int`.
  - `affixes: List[Affix]` where `Affix.stat_key` includes `phys_dmg`, `fire_dmg`, `max_hp`, `all_res`, `atk_speed`, `crit_rate`.
  - Supports canonical 15-tier affixes (`item_affix_catalog.py`) with prefix/suffix tiers.

#### 3. Meridian Constellation (`server/world/meridian_service.py:90-340`)
- `MeridianService.get_or_create_player(player_id) -> PlayerMeridianState`.
- `MeridianService.compute_total_stats(player_id) -> MeridianStatBonus`.
- Aggregates 29 potential Acupoints and 4 Jewel Sockets into `MeridianStatBonus`:
  `hp`, `mp`, `dps`, `dps_mult`, `crit_rate`, `crit_dmg`, `armor`, `evasion`, `attack_speed`, `resist`, `dmg_reduction`.

### 4.3 The Identified Architectural Gap
In the current server implementation:
1. `ServerEngineLoop.register_player()` has **zero access** to `InventoryService` or `MeridianService`.
2. All player characters enter combat simulation with generic, identical base stats: 50.0 attack damage, 1000 HP, 6.0 m/s speed, 5% crit chance, and 0% resistances.
3. Although `MeridianService` provided an ad-hoc `apply_to_combat_actor(player_id, actor)` method (`meridian_service.py:340-355`), it was:
   - Disconnected from the engine loop registration phase.
   - Completely ignorant of weapon damage, gear affixes, and inventory equipment.
   - Incapable of distinguishing additive *increased* from multiplicative *more* percentages.
   - Incapable of tag-based filtering (e.g. fire vs physical) or conditional evaluations (e.g. low life).
   - Devoid of formula persistence or auditing.

---

## 5. INTEGRATION BLUEPRINT: WIRING `CharacterStatAggregator`

### 5.1 Proposed Interface Contract for `CharacterStatAggregator`
To ensure clean modularity and comply with the $\le 350$ lines Soft Cap, `CharacterStatAggregator` should expose the following public methods:

```python
class CharacterStatAggregator:
    def __init__(
        self,
        inventory_service: Optional[InventoryService] = None,
        meridian_service: Optional[MeridianService] = None,
        formula_repository: Optional[FormulaPersistenceRepository] = None,
    ) -> None: ...

    def aggregate_player_stats(
        self,
        player_id: str,
        account_id: Optional[str] = None,
        character_id: Optional[str] = None,
        active_tags: Optional[Set[str]] = None,
        conditions: Optional[Dict[str, bool]] = None,
        trigger_reason: str = "MAP_JOIN",
    ) -> AggregatedCharacterStats:
        """
        Combines Base Attributes + Equipment Affixes + Meridian Passives.
        Evaluates Flat -> Increased/Reduced -> More/Less math with Tag & Condition filters.
        Persists serialized AST JSON to SQLite.
        Returns immutable AggregatedCharacterStats.
        """
        ...
```

### 5.2 Aggregated Stats Data Model
```python
@dataclass(slots=True, frozen=True)
class AggregatedCharacterStats:
    player_id: str
    max_hp: float
    current_hp: float
    base_attack: float
    crit_chance: float
    crit_multiplier: float
    movement_speed: float
    max_energy: float
    current_energy: float
    armour: float
    evasion: float
    attack_speed_multiplier: float
    resistances: Dict[FiveElements, float]
    formula_ast: Dict[str, Any]
    calculation_id: str
```

### 5.3 Non-Breaking Wiring in `ServerEngineLoop`
In `server/world/server_engine_loop.py`:

```python
class ServerEngineLoop:
    def __init__(
        self,
        cell_size: float = 64.0,
        stat_aggregator: Optional[Any] = None,
    ):
        # Existing subsystems...
        self.spatial_grid: SpatialGrid = SpatialGrid(cell_size=cell_size)
        self.movement_authority: MovementAuthorityEngine = MovementAuthorityEngine()
        self.combat_engine: CombatEngine = CombatEngine()
        # Injected Aggregator (optional for zero-breakage test compatibility)
        self.stat_aggregator = stat_aggregator
```

And in `register_player`:
```python
    def register_player(
        self,
        entity_id: int,
        initial_x: float,
        initial_y: float,
        initial_z: float = 0.0,
        element: FiveElements = FiveElements.KIM,
        move_speed: float = 6.0,
        max_hp: float = 1000.0,
        player_id: Optional[str] = None,
        account_id: Optional[str] = None,
        character_id: Optional[str] = None,
        aggregated_stats: Optional[Any] = None,
        context_tags: Optional[Set[str]] = None,
        conditions: Optional[Dict[str, bool]] = None,
    ) -> CombatActor:
        """Registers a player into all authoritative subsystems with aggregated stats."""
        # 1. Resolve final stats (Aggregator -> Direct stats -> Default fallback)
        resolved_hp = max_hp
        resolved_attack = 50.0
        resolved_move_speed = move_speed
        resolved_crit_chance = 0.05
        resolved_crit_mult = 1.50
        resolved_res: Dict[FiveElements, float] = {}

        if aggregated_stats is not None:
            resolved_hp = getattr(aggregated_stats, "max_hp", max_hp)
            resolved_attack = getattr(aggregated_stats, "base_attack", 50.0)
            resolved_move_speed = getattr(aggregated_stats, "movement_speed", move_speed)
            resolved_crit_chance = getattr(aggregated_stats, "crit_chance", 0.05)
            resolved_crit_mult = getattr(aggregated_stats, "crit_multiplier", 1.50)
            resolved_res = getattr(aggregated_stats, "resistances", {})
        elif self.stat_aggregator is not None and player_id is not None:
            stats = self.stat_aggregator.aggregate_player_stats(
                player_id=player_id,
                account_id=account_id or player_id,
                character_id=character_id or player_id,
                active_tags=context_tags,
                conditions=conditions,
                trigger_reason="MAP_JOIN",
            )
            resolved_hp = stats.max_hp
            resolved_attack = stats.base_attack
            resolved_move_speed = stats.movement_speed
            resolved_crit_chance = stats.crit_chance
            resolved_crit_mult = stats.crit_multiplier
            resolved_res = stats.resistances

        # 2. Register spatial grid
        grid_entity = Entity(entity_id=entity_id, x=initial_x, y=initial_y)
        self.spatial_grid.add_entity(grid_entity)
        self.active_entities[entity_id] = grid_entity
        self.entity_z_levels[entity_id] = initial_z

        # 3. Register movement authority with synced move_speed (anti-speedhack safe)
        player_char = PlayerCharacter(
            entity_id=entity_id, x=initial_x, y=initial_y, move_speed=resolved_move_speed
        )
        self.movement_authority.register_player(player_char)

        # 4. Register combat actor with aggregated attributes
        combat_actor = CombatActor(
            actor_id=entity_id,
            name=f"Player_{entity_id}",
            element=element,
            current_hp=resolved_hp,
            max_hp=resolved_hp,
            base_attack=resolved_attack,
            crit_chance=resolved_crit_chance,
            crit_multiplier=resolved_crit_mult,
            resistances=resolved_res,
            is_player=True,
            player_id=player_id or f"player_{entity_id}",
        )
        self.combat_engine.register_actor(combat_actor)
        return combat_actor
```

### 5.4 Why This Blueprint Guarantees Zero Regressions
1. **Positional Parameter Stability**: The first 7 parameters (`entity_id`, `initial_x`, `initial_y`, `initial_z`, `element`, `move_speed`, `max_hp`) remain completely unchanged in type, order, and defaults.
2. **Existing Test Immunity**: Existing callers (e.g. `test_isometric_engine_loop.py:50, 84`, `test_agent_decision_core.py:30`, `test_agent_orb_service.py:31`) pass only the original arguments. In the absence of `aggregated_stats` or `stat_aggregator + player_id`, the method executes its legacy branch with `base_attack = 50.0` and `max_hp = 1000.0`, resulting in 100% test parity.
3. **Return Value Addition**: Returning `combat_actor` is strictly non-breaking (previous callers ignored the `None` return) and eliminates awkward lookups via `engine.combat_engine.actors[entity_id]`.
4. **Anti-Speedhack Synchronization**: Synchronizing `player_char.move_speed = resolved_move_speed` ensures `MovementAuthorityEngine.validate_and_reconcile_position` allows higher travel distances when boots or passives provide movement speed bonuses.

---

## 6. BLAST RADIUS & DEPENDENT VERIFICATION MATRIX

### 6.1 Blast Radius Impact Analysis
Running `tools/analysis/blast_radius.py` on the affected engine targets:
- **`server/world/server_engine_loop.py`**: **CRITICAL RISK** (7 direct dependents):
  1. `server/agent/agent_decision_core.py`
  2. `server/world/agent_orb_service.py`
  3. `tests/security_fuzzing/test_agent_security_fuzzing.py`
  4. `tests/unit/test_agent_decision_core.py`
  5. `tests/unit/test_agent_orb_hmac.py`
  6. `tests/unit/test_agent_orb_service.py`
  7. `tests/unit/test_isometric_engine_loop.py`
- **`server/world/combat_engine.py`**: **HIGH RISK** (12 direct dependents across unit, e2e, and security suites).

> **Pre-Modification Hook Warning**:
> Antigravity Native Lifecycle Hook (`~/.gemini/config/hooks.json`) blocks modifications to `server_engine_loop.py`. The implementing engineer MUST execute:
> ```bash
> python tools/analysis/blast_radius.py --target server/world/server_engine_loop.py --ack
> ```
> prior to applying the proposed changes.

### 6.2 Existing Baseline Test Verification
All existing test suites touching `server_engine_loop.py`, `combat_engine.py`, `inventory_service.py`, and `meridian_service.py` have been executed and verified passing:
- `tests/unit/test_isometric_engine_loop.py`: **5 / 5 passed** (0.17s)
- `tests/unit/test_agent_decision_core.py`, `test_agent_orb_service.py`, `test_agent_orb_hmac.py`: **12 / 12 passed** (0.64s)
- `tests/unit/test_combat_engine.py`: **3 / 3 passed** (0.14s)
- `tests/unit/test_inventory_service.py`: **6 / 6 passed** (0.28s)
- `tests/unit/test_meridian_server_service.py`: **7 / 7 passed** (0.39s)
- `tests/unit/test_sprint_2_martial_combat_system.py`: **7 / 7 passed** (0.20s)

---

## 7. RECOMMENDATIONS FOR IMPLEMENTING AGENTS

1. **Module Decomposition**:
   - `server/stats/stat_types.py` ($\le 120$ lines): Enums (`ModifierType`), immutable `@dataclass(slots=True, frozen=True)` models for `StatModifier`, `CalculationContext`, and `AggregatedCharacterStats`.
   - `server/stats/formula_persistence.py` ($\le 180$ lines): SQLite repository managing table `character_stat_calculations` in `data/character_stat_formulas.db` (or `:memory:` in tests).
   - `server/stats/stat_aggregator.py` ($\le 280$ lines): `CharacterStatAggregator` service extracting data from `InventoryService` and `MeridianService`, executing the mathematical pipeline, building AST structures, and persisting formula steps.
2. **Server Engine Loop Wiring**:
   - Unlock with `python tools/analysis/blast_radius.py --target server/world/server_engine_loop.py --ack`.
   - Update `ServerEngineLoop.__init__` and `register_player` following Section 5.3.
3. **Dedicated Unit Test Suite**:
   - Create `tests/unit/test_character_stat_aggregator.py` verifying:
     - Mathematical accuracy of Flat, Increased, Reduced, More, and Less combinations.
     - Tag-based filtering and conditional activations (`ON_LOW_HEALTH`, etc.).
     - SQLite formula persistence and valid JSON AST generation.
     - Integration test with `ServerEngineLoop.register_player`: assert `combat_actor.base_attack != 50.0` when registered with aggregated stats.
4. **Hygiene Audit**:
   - Run `python tools/lint/check_code_and_doc_hygiene.py --strict` to verify all new files adhere to the $\le 350$ lines Soft Cap.
