Skip to main content
GZCTF uses a dynamic scoring system where challenge values decrease as more teams solve them, rewarding teams who solve challenges earlier.

Dynamic Score Formula

The score for a challenge is calculated using an exponential decay formula: f(S,r,d,x)=S×[r+(1r)×exp(1xd)]f(S, r, d, x) = \left \lfloor S \times \left[r + ( 1- r) \times \exp\left( \dfrac{1 - x}{d} \right) \right] \right \rfloor

Parameters

S
number
required
Original Score: The base point value of the challenge (configured by admin)
r
number
default:"0.0 to 1.0"
required
Minimum Score Ratio: The lowest percentage of original score the challenge can reachFor example, r = 0.2 means the challenge will never drop below 20% of its original value.
d
number
required
Difficulty Coefficient: Controls how quickly the score decreases
  • Lower values (e.g., 5): Score drops quickly
  • Higher values (e.g., 20): Score drops slowly
x
number
required
Submission Count: Number of teams that have solved the challenge

Example Calculation

Given a challenge with:
  • Original score: S = 1000
  • Minimum ratio: r = 0.3 (30% minimum)
  • Difficulty: d = 10
  • Solves: x = 5 teams
f(1000,0.3,10,5)=floor(1000×[0.3+0.7×exp((15)/10)])=floor(1000×[0.3+0.7×exp(0.4)])=floor(1000×[0.3+0.7×0.6703])=floor(1000×0.7692)=769pointsf(1000, 0.3, 10, 5) = floor(1000 × [0.3 + 0.7 × exp((1-5)/10)]) = floor(1000 × [0.3 + 0.7 × exp(-0.4)]) = floor(1000 × [0.3 + 0.7 × 0.6703]) = floor(1000 × 0.7692) = 769 points
The formula satisfies most dynamic scoring requirements by allowing customization of the three key parameters: original score, minimum ratio, and difficulty coefficient.

Score Decay Behavior

Here’s how the score changes as more teams solve a challenge:
# Solves | Score | % of Original
#    1   |  1000 |    100%
#    2   |  949  |     95%
#    5   |  769  |     77%
#   10   |  564  |     56%
#   20   |  398  |     40%
#   50   |  310  |     31%
#  100+  |  300  |     30% (minimum)

Blood Bonuses

The first three teams to solve a challenge receive bonus multipliers on top of the current score:

First Blood

+50% bonus (1.5x multiplier)First team to solve receives 150% of current score

Second Blood

+30% bonus (1.3x multiplier)Second team receives 130% of current score

Third Blood

+10% bonus (1.1x multiplier)Third team receives 110% of current score
Blood bonuses are awarded based on the order of accepted submissions, not submission time. Only correct flags count.

Blood Bonus Configuration

Blood bonuses are configurable per game:
public struct BloodBonus
{
    public long FirstBlood { get; }   // Parts per thousand (e.g., 500 = 50%)
    public long SecondBlood { get; }
    public long ThirdBlood { get; }
    
    // Default: 50%, 30%, 10%
    public const long DefaultValue = (50 << 20) + (30 << 10) + 10;
}
The bonus percentages are stored as parts per thousand for precision:
  • 500 = 50.0%
  • 300 = 30.0%
  • 100 = 10.0%
  • 50 = 5.0%

Example with Blood Bonus

A challenge worth 800 points at the time of solving:
PositionBase ScoreBonusMultiplierFinal Score
1st solve800+50%1.5x1200
2nd solve789+30%1.3x1026
3rd solve778+10%1.1x856
4th solve768-1.0x768
The score decreases between solves, so second and third blood receive bonuses on a slightly lower base score than first blood.

Permissions and Scoring

Not all teams affect the dynamic score calculation:

AffectDynamicScore Permission

Teams must have the AffectDynamicScore permission for their solves to increment x in the formula:
public enum GamePermission
{
    AffectDynamicScore = 1 << 12  // Bit flag
}
Official Teams
  • Solves increment the submission count x
  • Affect score for all teams
  • Typical for main competition divisions

GetScore Permission

Separately, teams need GetScore permission to actually receive points:
GetScore = 1 << 10
A team can have GetScore without AffectDynamicScore - they earn points but don’t affect others’ challenge values.

Score Calculation Flow

When a team submits a correct flag:
1

Count submissions

Calculate x = number of teams with AffectDynamicScore permission who have solved the challenge
2

Apply formula

Calculate current score using the dynamic formula:
score = floor(S × [r + (1-r) × exp((1-x)/d)])
3

Apply blood bonus

If this is the 1st, 2nd, or 3rd solve:
score = score × bloodMultiplier
4

Award points

If team has GetScore permission, add the score to their total
5

Update rankings

Recalculate scoreboard positions based on total scores and solve times

Scoreboard Tie-Breaking

When teams have the same total score, tie-breaking uses:
  1. Total Score (primary)
  2. Time of Last Accepted Submission (earlier is better)
  3. Team ID (lower is better, rare edge case)
This ensures teams who reach the same score first rank higher.

Division-Specific Scoring

Divisions can have separate scoreboards:
Includes all teams with RankOverall permission
  • Typically for official participants
  • Determines final rankings and prizes
Shows rankings within a specific division
  • All teams in the division (regardless of RankOverall)
  • Useful for regional or skill-based rankings

Challenges

Configure challenge base scores and types

Games

Set up blood bonuses and game configuration

Teams & Divisions

Manage team permissions and divisions

Participating

Understand flag submission and validation

Build docs developers (and LLMs) love