Skip to content

Grey-Flag PvP Status

Status: 🚧 Partial — The service, the combat hooks for player-on-player aggression, the /grey-status query, and the clear-by-fine endpoint are wired into the live combat pipeline; the station-attack rail is in place but unreachable because port assault is not yet route-wired (attack_port returns 501).

Purpose

The grey flag is the combat-aggression consequence layer: a temporary, self-expiring "open season" mark a player earns by attacking a lawful target. While a player is grey, qualifying other players may retaliate against them with no reputation penalty — the game treats bringing an aggressor to justice as a lawful act rather than an attack on an innocent.

Grey is distinct from, and sits alongside, the two reputation-driven enforcement layers:

  • Wanted Status — a persistent state keyed on a deeply negative personal_reputation (< −500), which summons NPC police forces and gates station access per station protection. Wanted Status is about who you are (your standing); grey is about what you just did (a single act of aggression) and clears on a timer.
  • The attack_innocent reputation trigger — the personal-reputation penalty for attacking a non-Wanted, non-bountied player, graded by the tier table in bounty-and-reputation.md. The grey flag is the companion to this trigger on the same combat resolution: the same kill that costs the aggressor reputation also marks them grey.

Grey does not replace either system; it is an additional, time-boxed PvP-retaliation permission that the reputation systems do not provide.

The two grey kinds

A grey flag carries a kind, cached on the player so the penalty-free predicate never has to reconstruct the offense. The kind determines both the duration and who may retaliate penalty-free.

Kind Earned by Duration Who may retaliate penalty-free
player_attack Attacking and destroying a good-standing player 1 hour (3600 s) Only good-standing attackers (personal_reputation ≥ 0)
station_attack Attacking a station 1 day (86400 s) Any player (good or evil)

The asymmetry is deliberate. Attacking a fellow pilot makes you fair game to the lawful — a fellow aggressor gunning you down is not acting as justice and still pays the normal penalty. Assaulting infrastructure makes you everyone's fair game: any player, regardless of their own standing, may engage a station-attacker penalty-free.

Good standing

A player is in good standing iff personal_reputation ≥ 0 — the Neutral tier and above on the personal-alignment scale in bounty-and-reputation.md#reputation-tier-table. Good standing matters at two points:

  1. Earning a flag. Only attacking a good-standing victim sets the attacker player_attack grey. Destroying an already-grey, Wanted, or otherwise negative-standing pilot does not mint a fresh open-season mark — that target was not a lawful innocent to begin with.
  2. Spending the exemption. For a player_attack grey, only a good-standing attacker collects the penalty-free retaliation. (For a station_attack grey, standing is irrelevant — anyone qualifies.)

How a flag is set

Grey is applied during combat resolution, evaluated against the target's pre-resolution standing so that destruction side-effects on the victim's reputation cannot retroactively change the verdict.

  • Player aggression. When an attacker destroys a player who carried no bounty (a genuine innocent) and that victim was good-standing at the moment of attack, the attacker is marked player_attack grey for 1 hour. This is the same branch that applies the attack_innocent reputation penalty and routes the police attack_innocent engagement — the three consequences fire together.
  • Station aggression. When an attacker assaults a station, the attacker is marked station_attack grey for 1 day, charged for the act of assault regardless of capture or combat outcome.

The MAX-not-shorten rule

When a player who is already grey commits a further offense, the flag is set to the later of the existing expiry and the newly computed expiry — grey_until = MAX(existing, new). A longer remaining grey is never shortened by a lesser later offense. A pilot one day deep into a station_attack grey who then ganks a good-standing player does not have their flag reset down to a 1-hour player_attack window.

When the new offense extends the flag beyond the existing expiry, the cached kind updates to the new (longer-lived) offense's kind. When the existing flag still runs longer, both its expiry and its kind are preserved — downgrading the kind would weaken the penalty-free rule that applies against the more severe standing.

How a flag clears

A grey flag clears in exactly two ways:

  1. Auto-expiry. The flag lapses the moment wall-clock time passes grey_until. An expired flag reports as not-grey immediately; the cached columns are NULLed opportunistically (a lapsed flag never lingers as a live status, whether or not its row has been swept).
  2. Clear-by-fine. The flagged player pays a credit fine to clear the flag early. Both kinds are fine-clearable.
Kind Early-clear fine
player_attack 10,000 cr
station_attack 50,000 cr

The fine is charged under a row lock so the credit debit and the flag clear are serialized against concurrent spends. The clear fails — leaving credits and the flag untouched — if the player is not currently grey, the flag has already expired (a player is never charged to clear a flag that has already lapsed), or the player cannot afford the fine.

Interaction with the combat penalty

The penalty-free exemption is evaluated at the reputation-penalty point of combat resolution. When an attacker destroys a grey target and qualifies for that grey kind's exemption, both the attack_innocent reputation penalty and the police attack_innocent engagement routing are skipped — bringing a flagged aggressor to justice is lawful, so it neither costs the retaliator reputation nor summons the police on them.

The exemption qualification is:

  • station_attack grey → any attacker is penalty-free.
  • player_attack grey → only a good-standing attacker is penalty-free; a fellow aggressor still eats the full penalty.

A retaliator who is not good-standing killing a player_attack-grey target is treated as an ordinary attack on an innocent — full penalty, full police response.

Query and clear API

Two player-facing endpoints expose grey status, both scoped to the authenticated player:

  • GET /grey-status — reports the authenticated player's current grey status: whether grey, the kind, the ISO-8601 expiry, the remaining seconds, and the fine that would clear it early. An expired flag reports isGrey: false with remainingSeconds: 0.
  • POST /grey-status/clear-fine — pays the early-clear fine for the authenticated player. Returns success with the fine paid and credits remaining, or failure with a reason (not grey, already expired, or insufficient credits) leaving credits and the flag untouched.

Outputs / state changes

  • Player.grey_until — tz-aware UTC expiry timestamp, or NULL when not grey.
  • Player.grey_kind — the cached offense kind (player_attack / station_attack), or NULL when not grey.
  • Player.credits — debited by the early-clear fine when a fine clears the flag.

The combat-path mutators flush rather than commit, folding into the single locked combat transaction so the grey mutation, the reputation adjustment, and the combat result commit atomically together. The clear-by-fine path owns its own transaction and commits at the end.

Invariants

  1. A flag is live iff grey_until is non-NULL and strictly in the future; an expired grey_until is equivalent to not-grey for every read.
  2. grey_until = MAX(existing, new) on every set — a later lesser offense never shortens a live flag.
  3. A live flag's grey_kind is preserved whenever the existing expiry wins the MAX, so the penalty-free rule always reads the more-severe standing.
  4. Attacking a non-good-standing player never mints a player_attack flag.
  5. The penalty-free exemption is evaluated against the target's pre-resolution standing.

Source map

Concern Location
Grey-flag service (set / query / clear / fine) services/gameserver/src/services/grey_flag_service.py
Good-standing + penalty-free predicates same file (is_good_standing, attack_is_penalty_free)
Player-aggression hook services/gameserver/src/services/combat_service.py (PvP resolution)
Station-aggression hook services/gameserver/src/services/combat_service.py (port assault)
/grey-status + clear-fine endpoints services/gameserver/src/api/routes/player_combat.py
Player fields services/gameserver/src/models/player.py (grey_until, grey_kind)

Canon numbers

Number Value
Good-standing threshold personal_reputation ≥ 0
player_attack grey duration 3600 s (1 hour)
station_attack grey duration 86400 s (1 day)
player_attack early-clear fine 10,000 cr
station_attack early-clear fine 50,000 cr
  • bounty-and-reputation.md — personal-alignment tier table (the good-standing cutoff), the attack_innocent reputation delta the grey flag accompanies, and system bounties.
  • FEATURES/gameplay/police-forces.md — NPC enforcement and the attack_innocent police engagement skipped by the penalty-free exemption; Wanted Status as the persistent counterpart to grey.
  • FEATURES/economy/station-protection.md — station-driven defense and the docked-ship attack protection that bounds where station aggression is even possible.
  • combat-resolver.md — the single-ship combat pipeline in which the grey hooks fire.