scoring

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package scoring computes challenge point values. It is pure: numbers in, numbers out — no I/O, no clock, no imports beyond stdlib (docs/v0.1/07-scoring.md).

Index

Constants

View Source
const (
	ModeStatic  = "static"
	ModeDynamic = "dynamic"
)

ModeStatic and ModeDynamic are the built-in scoring mode ids. Their value is a pure function of the solve count, recomputed from the formula on every read. Any other mode is plugin-provided and scored locked-at-solve from a recorded value (0007) — never by calling the plugin on the read path.

Variables

This section is empty.

Functions

func Deregister

func Deregister(name string)

Deregister removes a plugin-registered mode from the default registry (revert-before-death). A name that overrode a built-in restores the built-in; a name that only ever was a built-in is a no-op. Mirrors Register as the plugin loader's revert entry.

func Engines

func Engines() map[string]ScoringEngine

Engines returns a snapshot of the default registry's engines (replaces the former Registry() function).

func IsBuiltinMode

func IsBuiltinMode(mode string) bool

IsBuiltinMode reports whether mode is a platform built-in (static/dynamic). It is a CONSTANT check — no registry lookup, no plugin access, no I/O — so a scoreboard recompute can classify a solve and stay deterministic regardless of plugin or registry state (a plugin deregistered mid-read must not change how a past solve is scored). Everything else is a plugin mode, whose value the recompute reads from the per-solve record, not from any engine.

func IsRegisteredMode

func IsRegisteredMode(mode string) bool

IsRegisteredMode reports whether a scoring mode id resolves in the default registry (built-in static/dynamic plus any loaded scoring plugins). It is the write-time guard for challenges.scoring — replacing the dropped DB CHECK, so an unknown mode is still rejected.

func Register

func Register(name string, e ScoringEngine, override bool) error

Register adds/replaces an engine in the default registry — the plugin loader's entry.

func Value

func Value(mode string, params ChallengeScoring, solves int) int

Value selects the engine by mode and computes the value against the default registry. Unknown modes fall back to static. Signature unchanged from v0.2.2.

Types

type ChallengeScoring

type ChallengeScoring struct {
	Initial int // points_initial
	Min     int // points_min  (dynamic only)
	Decay   int // decay       (dynamic only; solve count at which value reaches Min)
}

ChallengeScoring holds the scoring parameters of a challenge.

type DynamicEngine

type DynamicEngine struct{}

DynamicEngine decays the value with solve count (CTFd-compatible shape):

Value = max(Min, round( Initial - (Initial-Min) * solves² / Decay² ))

func (DynamicEngine) Name

func (DynamicEngine) Name() string

Name implements ScoringEngine.

func (DynamicEngine) Value

func (DynamicEngine) Value(params ChallengeScoring, solves int) int

Value implements ScoringEngine.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry resolves a scoring mode name to its engine. v0.3 makes the previously-static map mutable + override-protected so the plugin loader can add scoring modes; with only the built-ins registered it is behaviourally identical to v0.2.2.

Concurrency (v0.3 decision): an atomic pointer to an IMMUTABLE map. Readers (Get/Value, the scoreboard hot path) are lock-free; Register copies-on-write and swaps the pointer under a writer mutex, so a swap is atomic from a reader's view — a lookup in flight resolves to the old map or the new one, never a torn or missing engine. Pinned by TestScoringRegistryReaderAtomicContention.

func NewRegistry

func NewRegistry(builtins ...ScoringEngine) *Registry

NewRegistry builds a registry with the given engines registered as protected built-ins.

func (*Registry) Deregister

func (r *Registry) Deregister(name string)

Deregister removes a plugin-registered mode (revert-before-death when its plugin stops). If the name overrode a built-in, the built-in is RESTORED (the key keeps working via the built-in); otherwise the entry is removed and the mode falls back to static on use (Value's unknown-mode path). Deregistering a name that only ever was a built-in is a no-op (a built-in is not a plugin and cannot "stop"). Swapped atomically, so a reader never sees a torn map.

func (*Registry) Engines

func (r *Registry) Engines() map[string]ScoringEngine

Engines returns a snapshot copy of the current mode→engine map (for listing).

func (*Registry) Get

func (r *Registry) Get(mode string) (ScoringEngine, bool)

Get resolves an engine by mode name. Lock-free.

func (*Registry) Register

func (r *Registry) Register(name string, e ScoringEngine, override bool) error

Register adds or replaces an engine. A protected built-in is refused unless override is true; plugin-registered engines are not protected. The map is swapped atomically.

func (*Registry) Value

func (r *Registry) Value(mode string, params ChallengeScoring, solves int) int

Value selects the engine by mode and computes the value; an unknown mode falls back to static (v0.2.2 behaviour).

type ScoringEngine

type ScoringEngine interface {
	Name() string // "static" | "dynamic"
	Value(params ChallengeScoring, solves int) int
}

ScoringEngine computes a challenge's current point value given its valid solve count.

type StaticEngine

type StaticEngine struct{}

StaticEngine always returns the initial value.

func (StaticEngine) Name

func (StaticEngine) Name() string

Name implements ScoringEngine.

func (StaticEngine) Value

func (StaticEngine) Value(params ChallengeScoring, _ int) int

Value implements ScoringEngine: the value never changes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL