Fighter Cost System Reference
This document provides a comprehensive reference for the fighter cost calculation system in Gyrinx, including how costs are calculated, overridden, and displayed throughout the application.
See also: Fighter Cost System Design Guide for the design philosophy and architectural decisions behind this system.
Overview
The fighter cost system calculates the total cost of a fighter by combining:
Base fighter cost
Equipment costs
Weapon profile costs
Weapon accessory costs
Equipment upgrade costs
Campaign advancement costs
Cost Calculation Flow
1. List Total Cost
The total cost of a list is calculated by:
Total List Cost = Sum of all fighter costs + Current creditsImplementation: List.cost_int() in gyrinx/core/models/list.py:137
2. Fighter Cost
Each fighter's cost is calculated as:
Implementation: ListFighter.cost_int() in gyrinx/core/models/list.py:543
3. Base Fighter Cost
The base cost follows this priority hierarchy:
User override:
ListFighter.cost_override(if set)Child fighter: 0 (if fighter is child of another)
House override:
ContentFighterHouseOverride.cost(if exists)Content fighter:
ContentFighter.base_cost
Implementation: ListFighter._base_cost_int property in gyrinx/core/models/list.py:567
4. Equipment Assignment Cost
Each equipment assignment's cost is:
Or if total cost override is set:
Implementation: ListFighterEquipmentAssignment.cost_int() in gyrinx/core/models/list.py:1333
5. Equipment Base Cost
Equipment base cost priority:
Assignment override:
ListFighterEquipmentAssignment.cost_overrideLinked equipment: 0 (if equipment is linked/child)
Fighter equipment list:
ContentFighterEquipmentListItem.costBase equipment:
ContentEquipment.cost
Implementation: ListFighterEquipmentAssignment._equipment_cost_with_override() in gyrinx/core/models/list.py:1354
6. Weapon Profile Cost
Profile costs follow this priority:
Default assignment: 0 (if profile is part of default assignment)
Fighter equipment list:
ContentFighterEquipmentListItem.cost(with weapon_profile)Base profile:
ContentWeaponProfile.cost
Implementation: ListFighterEquipmentAssignment._profile_cost_with_override_for_profile() in gyrinx/core/models/list.py:1404
7. Weapon Accessory Cost
Accessory costs follow this priority:
Default assignment: 0 (if accessory is part of default assignment)
Fighter equipment list:
ContentFighterEquipmentListWeaponAccessory.costBase accessory:
ContentWeaponAccessory.cost
Implementation: ListFighterEquipmentAssignment._accessory_cost_with_override() in gyrinx/core/models/list.py:1469
8. Equipment Upgrade Cost
Upgrade costs depend on the equipment's upgrade mode:
Multi mode: Individual upgrade cost
Single mode: Cumulative cost (sum of all upgrades up to selected position)
Implementation: ContentEquipmentUpgrade.cost_int() in gyrinx/content/models.py:1528
9. Campaign Advancement Cost
In campaign mode, advancements increase fighter cost:
Cost Override Models
ContentFighterHouseOverride
Allows specific fighters to have different costs when added to specific houses.
Fields:
fighter: The ContentFighterhouse: The ContentHousecost: The override cost (nullable)
ContentFighterEquipmentListItem
Defines fighter-specific costs for equipment and weapon profiles.
Fields:
fighter: The ContentFighterequipment: The ContentEquipmentweapon_profile: Optional specific profilecost: The override cost
ContentFighterEquipmentListWeaponAccessory
Defines fighter-specific costs for weapon accessories.
Fields:
fighter: The ContentFighterweapon_accessory: The ContentWeaponAccessorycost: The override cost
Legacy Fighter System
The legacy fighter system supports the Venators' Gang Legacy rule, allowing them to use equipment from another house's fighter:
Legacy fighter: Set via
ListFighter.legacy_content_fighterEquipment list fighter: Property that returns legacy fighter if set, otherwise regular content fighter
Cost overrides: Check equipment_list_fighter for proper legacy support
Implementation: ListFighter.equipment_list_fighter property
Special Cost Rules
Zero Cost Items
Stash fighters: Must have
base_cost = 0Default assignments: Always cost 0
Linked equipment: Child equipment in linked relationships cost 0
Child fighters: Fighters created by equipment profiles cost 0
Cost Display
All costs are displayed with the ¢ symbol using format_cost_display():
Regular display: "50¢"
With sign: "+50¢" or "-50¢"
Zero: "0¢"
Virtual Equipment Assignment
The VirtualListFighterEquipmentAssignment class provides a unified interface for both:
Direct equipment assignments (
ListFighterEquipmentAssignment)Default equipment assignments (
ContentFighterDefaultAssignment)
This allows consistent cost calculation regardless of assignment type.
Facts System API
The facts system provides fast O(1) reads of cached cost values. Each cost-bearing model has database fields (rating_current, dirty) and methods to access them.
Facts Dataclasses
Cached values are returned as immutable dataclasses (defined in gyrinx/core/models/facts.py):
Cache Fields
Each level in the hierarchy has cached fields:
List
rating_current, stash_current, credits_current, dirty
ListFighter
rating_current, dirty
ListFighterEquipmentAssignment
rating_current, dirty
Facts Methods
Every cost-bearing model provides three methods:
facts() - Fast Cached Read
Returns cached values as a facts dataclass, or None if cache is stale:
facts_from_db() - Full Recalculation
Recalculates from database and optionally updates cache:
facts_with_fallback() - Hybrid Read (List only)
Returns cached facts if clean, otherwise calculates without updating cache:
When to Use Each Method
Display in views
facts() then facts_with_fallback()
Fast read, fallback if stale
Object creation
create_with_facts()
Atomic creation with cache
Handler operations
Don't call - use propagation
Handlers use incremental updates
Manual refresh
facts_from_db(update=True)
Full recalculation
The create_with_facts() Pattern
For atomic object creation with correct initial cache state:
Display Methods
Display methods use the facts system internally:
Dirty Flag Management
The dirty flag indicates cached values may be stale:
Cost Propagation
For write operations, the propagation system incrementally updates cached values rather than recalculating:
See also: Cost Handler Development Guide for detailed handler patterns.
Database Queries Optimization
The system uses several optimizations:
select_related()for foreign keysprefetch_related()for many-to-many relationshipsCached properties to avoid repeated calculations
Annotation with cost overrides in querysets
Prefetching for Facts System
To enable the facts system's can_use_facts property, views must use the appropriate prefetch:
The with_latest_actions() method prefetches the most recent ListAction, which is required for the guard condition that enables the facts system.
Common Usage Patterns
Getting a List's Total Wealth
Getting a Fighter's Total Cost
Getting Equipment Cost with Override
Checking for Cost Overrides
Error Handling
The system includes validation for:
Negative costs (prevented in model
clean()methods)Invalid cost strings (non-integer values)
Circular equipment links
Multiple fighter profiles for same equipment
Cost Mixins
The cost system provides reusable mixins to standardize cost calculation behavior across models:
CostMixin
gyrinx.models.CostMixin provides common cost methods for models with cost fields:
Key features:
Handles both integer and string cost fields
Converts string costs to integers when possible
Returns 0 for empty or non-numeric values
Provides formatted display with ¢ symbol
Supports custom field names via
cost_field_nameattribute
Usage example:
FighterCostMixin
gyrinx.models.FighterCostMixin extends CostMixin for models with fighter-specific cost overrides:
Key features:
Inherits all functionality from
CostMixinAdds
cost_for_fighter_int()methodExpects models to be annotated with
cost_for_fighterattributeRaises
AttributeErrorif annotation is missing
Usage with querysets:
Models Using Cost Mixins
The following models use these mixins:
ContentEquipment(FighterCostMixin)ContentWeaponProfile(FighterCostMixin) - with customcost_display()logicContentWeaponAccessory(FighterCostMixin)ContentEquipmentUpgrade(CostMixin) - with customcost_int()logicContentFighterEquipmentListItem(CostMixin)ContentFighterEquipmentListWeaponAccessory(CostMixin)ContentFighterEquipmentListUpgrade(CostMixin)ContentFighterDefaultAssignment(CostMixin)
Special Behaviors
Some models override the mixin methods for custom behavior:
ContentWeaponProfile:
cost_display()returns empty for standard profiles (no name)Shows "+" prefix for named profiles with positive costs
ContentEquipmentUpgrade:
cost_int()implements cumulative costs in SINGLE modeSums all upgrades up to current position
Testing
Key test files for the cost system:
gyrinx/core/tests/test_cost_display.py- Cost formatting testsgyrinx/core/tests/test_models_core.py- Core cost calculation testsgyrinx/core/tests/test_assignments.py- Equipment assignment cost testsgyrinx/content/tests/test_cost_methods.py- Comprehensive tests for all cost mixins
Last updated