Grey-Flag PvP Status¶
Status: 🚧 Partial — The service, the combat hooks for player-on-player aggression, the
/grey-statusquery, 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_portreturns 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_innocentreputation 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:
- Earning a flag. Only attacking a good-standing victim sets the attacker
player_attackgrey. 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. - Spending the exemption. For a
player_attackgrey, only a good-standing attacker collects the penalty-free retaliation. (For astation_attackgrey, 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_attackgrey for 1 hour. This is the same branch that applies theattack_innocentreputation penalty and routes the policeattack_innocentengagement — the three consequences fire together. - Station aggression. When an attacker assaults a station, the attacker is marked
station_attackgrey 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:
- 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). - 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_attackgrey → any attacker is penalty-free.player_attackgrey → 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 reportsisGrey: falsewithremainingSeconds: 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¶
- A flag is live iff
grey_untilis non-NULL and strictly in the future; an expiredgrey_untilis equivalent to not-grey for every read. grey_until = MAX(existing, new)on every set — a later lesser offense never shortens a live flag.- A live flag's
grey_kindis preserved whenever the existing expiry wins the MAX, so the penalty-free rule always reads the more-severe standing. - Attacking a non-good-standing player never mints a
player_attackflag. - 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 |
Related¶
- bounty-and-reputation.md — personal-alignment tier table (the good-standing cutoff), the
attack_innocentreputation delta the grey flag accompanies, and system bounties. - FEATURES/gameplay/police-forces.md — NPC enforcement and the
attack_innocentpolice 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.