Skip to content

ENOWARS 2024 Scoring Formula

Scoring formula for Enowars 8 organized by ENOFLAG.

Summary

The total score of each team is calculated from offense, defense and sla components of every team for each of their services and rounds played.

The checker returns one of three results for each service: up, recovering and down. The result is up if all SLA checks pass, and down if some SLA checks do not pass. A service is considered recovering if flags for one round in the retention period could not be recovered, but the latest round passed SLA checks.

The following python pseudo-code captures the team score calculation:1

SLA = 100.0
ATTACK = 1000.0
DEF = -50.0

type CheckerResult = Literal["up"] | Literal["recovering"] | Literal["down"]

@dataclass
class RoundStateFlagstore:
    lost: str | None # flag of the current round if stolen by any team
    captures: list[str] # flags of this flagstore captured from other teams

@dataclass
class RoundStateService:
    flagstores: list[RoundStateFlagstore]
    checker_result: CheckerResult
    team_results: list[CheckerResult]

@dataclass
class RoundState:
    services: list[RoundStateService]

def score(rounds: list[RoundState], owner: dict[str, str],
          captures: dict[str, int]):
    attack = defense = sla = 0
    for rnd in range(len(rounds)):
        for service in rnd.services:
            for flagstore in service.flagstores:
                for flag in flagstore.captures:
                    attack += ATTACK / captures[flag] \
                        / len(service.flagstores) / len(rnd.services)

                if (flag := flagstore.lost) is not None:
                    defense -= DEF / len(service.flagstores) \
                        / len(rnd.services)

            if service.checker_result == "up":
                sla += SLA
            elif service.checker_result == "recovering":
                sla += 0.5 * SLA
    return (attack, defense, sla)

Review

  • Only small differences to FaustCTF 2024 and SaarCTF 2024, inherits most of the same strengths (simple, easy to implement) and weaknesses (score recalculation).
  • Flag value is scaled by the number of flag stores of each service, not the total number of flagstores, thereby violating Tenet 7

Tenets

  1. Total score MUST increase with more flags captured

    Attack points scale linearly with the amount of flags captured.

  2. Total score MUST decrease with more flags lost

    Defense points scale non-linearly with the amount of flags lost. Beyond the first capture of a flag, the points lost due to defense do not increase.

  3. Flag value MUST diminish with more successful attacks

    Flag values scales inversely with the amount of captures.

  4. Perfect SLA MUST be worth more than any attacker's relative gain

    Based on the default constants for ATTACK, SLA and DEF, teams receive more SLA points than they lose through defense, but attackers may gain significantly more points than awarded through SLA.

  5. The cost of downtime MUST NOT outweigh the benefits of patching

    The cost of downtime is similar to the cost of defense per round. Patches prevent loss of points over multiple rounds and are thus favorable to not patching.

  6. SLA SHOULD decrease fairly with every missing flag in the retention period

    Independent of the amount of flags missing from the retention period, the service is awarded the same amount of SLA.

  7. Flag value SHOULD be calculated independent of its flagstore

    Flag value is scaled to the amount of flagstore per service, not to the total amount of flagstores.


  1. We've removed the per-service weights applied to attack, defense and SLA points from the formula, since historically they we're rarely changed and typically set to 1.