Skip to content

Public matches allow troop duplication through batched fractional attacks #4948

Description

@thomastschinkel

Summary

Players can generate free troops in public matches by sending multiple fractional attack intents in the same server turn.

Steps to reproduce

  1. Join a public match and wait until spawn immunity ends.
  2. Choose a non-adjacent opponent and obtain their simulation Player.id().
  3. Send this WebSocket intent 10 times in a burst before the next server turn:

{
"type": "intent",
"intent": {
"type": "attack",
"targetID": "TARGET_PLAYER_ID",
"troops": 0.999
}
}

  1. Because the target is non-adjacent, the attack has no reachable border and automatically retreats.

What happens

Each 0.999 request deducts 0 troops because troop removal is floored. The attacks then combine into a single 9.99-troop attack. On
retreat, the game refunds floor(9.99) = 9 troops.

Observed result:

Before: 25000 troops
After attack creation: 25000 troops
Combined attack: 9.99 troops
After auto-retreat: 25009 troops
Net gain: +9 free troops

The server accepts and queues all 10 intents, and identical client simulations produce the same game hash, so this affects the
synchronized public-match state.

At the current rate limits, this can generate approximately 135 free troops per minute when repeated in bursts.

cause

AttackIntentSchema accepts fractional troop amounts. AttackExecution floors each troop deduction individually, combines attacks afterward,
and then floors the combined amount again during retreat.

The attack amount should be normalized to the amount actually deducted, or multiple attacks should be aggregated before resource
deduction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    approvedApproved for a PR, if you assigned to the issue.good first issueGood for new contributors.

    Type

    No type

    Projects

    Status
    Triage

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions