Battle Resolution Fix Report
The reported issue affected attacks involving more than 2,000 troops. The supplied error log identifies an invalid defender tribe value as the immediate cause of the crash, but does not establish why it occurred with that troop count.
The invalid tribe produced unit names from u-9 to u0, causing undefined array key warnings followed by an invalid SQL query in modifyUnit().
The updated AutomationBattleResolution.php includes:
Tribe validation before combat: Invalid attacker, defender, or reinforcement tribe data leaves the attack pending and logs the issue before evasion or combat changes occur.
Earlier deleted-village handling: The existing troop return logic runs before resolving the deleted village’s owner, tribe, walls, or units.
Defensive casualty checks: Invalid tribes cannot generate negative unit column names. Missing defender unit rows return a zero-loss structure without submitting an empty update.
Reinforcement validation: Invalid player tribe data is no longer automatically treated as Nature. Only reinforcement rows explicitly originating from 0 use tribe 4.
Consistent hero reporting: Reinforcement hero losses use the validated tribe.
Code comments: Changes are documented in the file.
Validation: The complete file passed a PHP 8.3 grammar check using a parser. All 33 original methods were retained; three methods were modified and three validation helpers were added.
When rams change the wall level, applyRamDamage() replays the whole battle
through calculateBattle(). That helper WRITES to the hero table, so the replay
applied the hero damage a second time: applyHeroBattleDamage() only looks up
heroes with dead = 0, so the hero who had just died on the first pass was no
longer found, the call returned null and casualties_attacker[11] was never set.
Consequences, all visible in the screenshots of the issue: the report showed the
hero alive and "gained XP" instead of "died", modifyAttack3() never decremented
t11, and returnunitsComplete() added the hero back to units.hero - a hero dead in
the hero table but still displayed in dorf1 and in the rally point. Defender
heroes were also charged their health damage twice.
calculateBattle() now takes an optional $previousHeroOutcome: when the caller
hands over the first pass result, the hero damage is not applied again and the
first verdict (attacker / defender / reinforcement heroes) is carried over.
Also:
- drop the temporary [TravianZ][EROU] error_log() debug traces;
- 37_revive.tpl: only render a revive row for a hero that is actually dead
(the revive action itself was already gated on dead = 1).
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Catalin Novgorodschi <cata7007@gmail.com>
### Core Architecture Refactor
- Split the `Database.php` class into domain-specific traits under `GameEngine/Database/`.
- Split the `Automation` class into domain-specific traits under `GameEngine/Automation/`.
- Grouped methods by functional domain for improved maintainability and navigation.
- Preserved 100% backward compatibility.
- No logic or behavioral changes.
- Pure structural refactor.