Coverage Summary for Class: BalanceSettings (core)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| BalanceSettings |
0%
(0/1)
|
0%
(0/1)
|
|
0%
(0/19)
|
0%
(0/20)
|
// FILE: core/src/main/kotlin/core/BalanceSettings.kt
package core
/**
* Balance settings for outcome resolution and other gameplay parameters.
*
* ## Contract
* All values are immutable constants. Changing them affects game balance deterministically.
*
* ## Invariants
* - SUCCESS_CHANCE_MIN <= SUCCESS_CHANCE_MAX
* - SUCCESS_CHANCE_MIN >= 0, SUCCESS_CHANCE_MAX <= 100
* - FAIL_CHANCE_MIN >= 1 (FAIL must always be reachable)
* - pSuccess + pPartial + pFail = 100 for any valid input
*
* ## Determinism
* All calculations using these constants are deterministic.
*/
object BalanceSettings {
// ─────────────────────────────────────────────────────────────────────────
// Outcome Resolution Parameters
// ─────────────────────────────────────────────────────────────────────────
/** Minimum success chance percentage (floor). */
const val SUCCESS_CHANCE_MIN: Int = 5
/** Maximum success chance percentage (ceiling). */
const val SUCCESS_CHANCE_MAX: Int = 85
/** Offset added to (heroPower - difficulty) before multiplying. */
const val SUCCESS_FORMULA_OFFSET: Int = 5
/** Multiplier for (heroPower - difficulty + offset). */
const val SUCCESS_FORMULA_MULTIPLIER: Int = 20
/** Fixed partial outcome chance percentage. */
const val PARTIAL_CHANCE_FIXED: Int = 14
/** Minimum fail chance percentage (FAIL must always be reachable). */
const val FAIL_CHANCE_MIN: Int = 1
/**
* Upper bound (exclusive) for percentage-based RNG rolls.
*
* Most reducer rolls are modeled as `rng.nextInt(PERCENT_ROLL_MAX)` and compared against chance
* values expressed in percent.
*
* Typical value: `100` (meaning roll in range `[0, 100)`).
* Must be > 0.
*/
const val PERCENT_ROLL_MAX: Int = 100
/**
* Chance (percent) that a DEATH resolution is reported as MISSING instead, for narrative variety.
* PoC/MVP: small fixed chance; kept deterministic via RNG seed. Value in [0,100).
*/
const val MISSING_CHANCE_PERCENT: Int = 10
// ─────────────────────────────────────────────────────────────────────────
// Gameplay Timing & Economic Parameters
// ─────────────────────────────────────────────────────────────────────────
/** Default number of drafts generated per inbox cycle (rank F). */
const val DEFAULT_N_INBOX: Int = 2
/** Default number of new heroes arriving per day (rank F). */
const val DEFAULT_N_HEROES: Int = 2
/** Initial days remaining when a hero accepts a contract. */
const val DAYS_REMAINING_INIT: Int = 2
/** Interval in days between tax assessments. */
const val TAX_INTERVAL_DAYS: Int = 7
/** Base copper amount charged every tax interval. */
const val TAX_BASE_AMOUNT: Int = 10
/** Percentage penalty added to tax when a payment is missed. */
const val TAX_PENALTY_PERCENT: Int = 10
/** Maximum number of missed taxes before suffering a shutdown. */
const val TAX_MAX_MISSED: Int = 3
/** Number of days until auto-resolve runs for a stale draft. */
const val AUTO_RESOLVE_INTERVAL_DAYS: Int = 7
/** Threshold under which heroes will automatically decline risky quests. */
const val DECLINE_HARD_THRESHOLD: Int = -30
/** Stability penalty applied when a BAD auto-resolve occurs (per Miss). */
const val STABILITY_PENALTY_BAD_AUTO_RESOLVE: Int = 2
/** Minimum stability value (floor). */
const val STABILITY_MIN: Int = 0
/** Maximum stability value (ceiling). */
const val STABILITY_MAX: Int = 100
/** Base multiplier for inbox/heroes per rank level. */
const val RANK_MULTIPLIER_BASE: Int = 2
/** Default contract difficulty when board contract is missing. */
const val DEFAULT_CONTRACT_DIFFICULTY: Int = 1
// ─────────────────────────────────────────────────────────────────────────
// Fraud Investigation Settings (v0)
// ─────────────────────────────────────────────────────────────────────────
/**
* Duration of WARN status in days.
* A hero caught for fraud the first time receives a WARN for this duration.
*/
const val WARN_DURATION_DAYS: Int = 7
/**
* Duration of BAN status in days.
* A hero caught for fraud while already warned receives a BAN for this duration.
*/
const val BAN_DURATION_DAYS: Int = 90
/**
* Probability (percent) of catching fraud under STRICT policy.
* Higher than SOFT to reward stricter oversight.
*/
const val CATCH_CHANCE_STRICT_PERCENT: Int = 90
/**
* Probability (percent) of catching fraud under SOFT policy.
* Lower than STRICT, reflecting lenient oversight.
*/
const val CATCH_CHANCE_SOFT_PERCENT: Int = 50
/**
* Probability (percent) of a rumor spreading when fraud escapes under STRICT policy.
* Lower than SOFT because strict policy catches more fraud, fewer escapes to spread rumors.
*/
const val RUMOR_CHANCE_ON_ESCAPE_STRICT_PERCENT: Int = 10
/**
* Probability (percent) of a rumor spreading when fraud escapes under SOFT policy.
* Higher than STRICT because lenient policy lets more fraud escape, more rumors spread.
*/
const val RUMOR_CHANCE_ON_ESCAPE_SOFT_PERCENT: Int = 50
/**
* Reputation penalty applied when a rumor spreads about escaped fraud.
* Accumulated into pendingReputationDelta and applied on weekly boundary.
*/
const val REP_PENALTY_ON_RUMOR: Int = 1
// ─────────────────────────────────────────────────────────────────────────
// Contract Pricing (Client Deposit)
// ─────────────────────────────────────────────────────────────────────────
/** Chance (percent) that the client pays a deposit upfront. MVP: 50%. */
const val CLIENT_PAYS_CHANCE_PERCENT: Int = 50
/** Fraction of payout that becomes deposit when client pays. Basis points (5000 = 50%). */
const val CLIENT_PAYS_FRACTION_BP: Int = 5_000
/** Rank F payout range (gp). */
const val PAYOUT_F_MIN: Int = 0
const val PAYOUT_F_MAX: Int = 1
/** Rank E payout range (gp). */
const val PAYOUT_E_MIN: Int = 1
const val PAYOUT_E_MAX: Int = 6
/** Rank D payout range (gp). */
const val PAYOUT_D_MIN: Int = 6
const val PAYOUT_D_MAX: Int = 25
/** Rank C payout range (gp). */
const val PAYOUT_C_MIN: Int = 25
const val PAYOUT_C_MAX: Int = 150
/** Rank B payout range (gp). */
const val PAYOUT_B_MIN: Int = 150
const val PAYOUT_B_MAX: Int = 700
/** Rank A payout base range (gp). */
const val PAYOUT_A_MIN: Int = 700
const val PAYOUT_A_MAX: Int = 2500
/** Rank A tail probability (percent) for heavy-tail sampling. */
const val PAYOUT_A_TAIL_CHANCE_PERCENT: Int = 10
/** Rank A tail range (gp) for heavy-tail sampling. */
const val PAYOUT_A_TAIL_MIN: Int = 2500
const val PAYOUT_A_TAIL_MAX: Int = 8000
/** Rank S payout range (gp). */
const val PAYOUT_S_MIN: Int = 2000
const val PAYOUT_S_MAX: Int = 10_000
// ─────────────────────────────────────────────────────────────────────────
// Derived Constraints (compile-time assertions via init block)
// ─────────────────────────────────────────────────────────────────────────
init {
require(SUCCESS_CHANCE_MIN >= 0) { "SUCCESS_CHANCE_MIN must be >= 0" }
require(SUCCESS_CHANCE_MAX <= 100) { "SUCCESS_CHANCE_MAX must be <= 100" }
require(SUCCESS_CHANCE_MIN <= SUCCESS_CHANCE_MAX) { "SUCCESS_CHANCE_MIN must be <= SUCCESS_CHANCE_MAX" }
require(FAIL_CHANCE_MIN >= 1) { "FAIL_CHANCE_MIN must be >= 1 (FAIL must always be reachable)" }
require(PERCENT_ROLL_MAX > 0) { "PERCENT_ROLL_MAX must be > 0" }
// Ensure normalized probabilities: pSuccess_max + pPartial + pFail_min <= 100
val maxPossibleNonFail = SUCCESS_CHANCE_MAX + PARTIAL_CHANCE_FIXED
require(maxPossibleNonFail <= 100 - FAIL_CHANCE_MIN) {
"SUCCESS_CHANCE_MAX ($SUCCESS_CHANCE_MAX) + PARTIAL_CHANCE_FIXED ($PARTIAL_CHANCE_FIXED) " +
"must leave room for FAIL_CHANCE_MIN ($FAIL_CHANCE_MIN): max non-fail = $maxPossibleNonFail, " +
"but 100 - FAIL_CHANCE_MIN = ${100 - FAIL_CHANCE_MIN}"
}
// Contract pricing validations
require(CLIENT_PAYS_CHANCE_PERCENT in 0..100) { "CLIENT_PAYS_CHANCE_PERCENT must be in [0,100]" }
require(CLIENT_PAYS_FRACTION_BP in 0..10_000) { "CLIENT_PAYS_FRACTION_BP must be in [0,10000]" }
// Payout band validations (min <= max)
require(PAYOUT_F_MIN <= PAYOUT_F_MAX) { "PAYOUT_F_MIN must be <= PAYOUT_F_MAX" }
require(PAYOUT_E_MIN <= PAYOUT_E_MAX) { "PAYOUT_E_MIN must be <= PAYOUT_E_MAX" }
require(PAYOUT_D_MIN <= PAYOUT_D_MAX) { "PAYOUT_D_MIN must be <= PAYOUT_D_MAX" }
require(PAYOUT_C_MIN <= PAYOUT_C_MAX) { "PAYOUT_C_MIN must be <= PAYOUT_C_MAX" }
require(PAYOUT_B_MIN <= PAYOUT_B_MAX) { "PAYOUT_B_MIN must be <= PAYOUT_B_MAX" }
require(PAYOUT_A_MIN <= PAYOUT_A_MAX) { "PAYOUT_A_MIN must be <= PAYOUT_A_MAX" }
require(PAYOUT_A_TAIL_MIN <= PAYOUT_A_TAIL_MAX) { "PAYOUT_A_TAIL_MIN must be <= PAYOUT_A_TAIL_MAX" }
require(PAYOUT_S_MIN <= PAYOUT_S_MAX) { "PAYOUT_S_MIN must be <= PAYOUT_S_MAX" }
require(PAYOUT_A_TAIL_CHANCE_PERCENT in 0..100) { "PAYOUT_A_TAIL_CHANCE_PERCENT must be in [0,100]" }
}
}