mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Phase 3b (sweep): nick-floor + supply-burn sweep, T5 supply unlock
Wired SurpriseNickFloorOverride and SupplyBurnRatePctOverride into the
harness day-loop via two new parameterized helpers (surpriseRoundNickF,
applyDailyBurnP). Live callers go through the existing constants;
sweep test sits on top of the Phase 3-A best cell (e=23, d=1).
TestExpeditionBalance_Phase3B_NickSupplySweep walks 3×3 (floor ∈ {0, 1,
tier=live}) × (burn% ∈ {50, 75, 100=live}) × 10 zones × 200 trials.
Strong partial T5 positive; nick-floor lever inert.
- Supply burn is the T5 unlock: dragons_lair 0% → ~55% at burn=50.
Fighter survives elites; burn=75 isn't enough margin.
- T4 peaks at burn=75 (~12% underdark/feywild); burn=50 dips T4
slightly (more elites survived into).
- Nick-floor inert across tiers (≤3pp swing); wounded-clamp already
eats the chip-damage budget. Recommend dropping from live-tuning
candidates.
- T2-T3 wall persists: forest_shadows, manor_blackspire,
abyss_portal stuck at ~0% across every combo — outliers, not
addressable by global levers.
Global levers wrung out. Plan-doc Phase 3-B section + memory pointers
updated; next is Phase 4 (per-zone outlier pass). -short shows the
same two pre-existing failures (TestAdv2Scenario_ZoneRunGoblinWarrens,
TestMageSpellbookLineInRender).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -253,6 +253,61 @@ density) or supply margin.
|
|||||||
on top of e=23/d=1 to push T1-T3 toward target band and lift T4-T5
|
on top of e=23/d=1 to push T1-T3 toward target band and lift T4-T5
|
||||||
off the floor.
|
off the floor.
|
||||||
|
|
||||||
|
#### Phase 3-B — nick-floor + supply-burn sweep (shipped)
|
||||||
|
|
||||||
|
Wired two more harness overrides (`SurpriseNickFloorOverride`,
|
||||||
|
`SupplyBurnRatePctOverride`) and swept a 3×3 grid (floor ∈ {0, 1, tier
|
||||||
|
(=live)} × burn% ∈ {50, 75, 100 (=live)}) on top of the Phase 3-A best
|
||||||
|
cell (e=23, d=1). Test: `TestExpeditionBalance_Phase3B_NickSupplySweep`.
|
||||||
|
|
||||||
|
**Strong partial positive for T5 supply margin; nick-floor lever inert.**
|
||||||
|
|
||||||
|
Tier-mean completion (Fighter, centerline level, 10 zones × 200 trials):
|
||||||
|
|
||||||
|
| (floor, burn%) | T1 mean | T2 | T3 | T4 | T5 |
|
||||||
|
|----------------|--------:|----:|----:|-----:|-----:|
|
||||||
|
| 0, 50 | 20.8% | 5.0%| 3.8%| 7.5% | 29.5%|
|
||||||
|
| 0, 75 | 26.5% | 6.0%| 2.8%| 9.0% | 0.0% |
|
||||||
|
| 0, 100 | 25.8% | 5.0%| 3.0%| 0.0% | 0.0% |
|
||||||
|
| 1, 50 | 25.5% | 6.5%| 1.8%| 8.5% | 27.8%|
|
||||||
|
| 1, 75 | 28.5% | 6.2%| 4.0%|11.5% | 0.0% |
|
||||||
|
| 1, 100 | 26.8% | 7.5%| 2.5%| 0.0% | 0.0% |
|
||||||
|
| tier, 50 | 26.8% | 6.2%| 2.5%| 7.8% | 27.5%|
|
||||||
|
| tier, 75 (live floor) | **29.5%** | **7.8%** | 3.0% | **12.8%** | 0.0% |
|
||||||
|
| tier,100 (live)| 28.5% | 6.8%| 3.0%| 0.0% | 0.0% |
|
||||||
|
|
||||||
|
Three readings:
|
||||||
|
|
||||||
|
1. **Supply burn is the T5 unlock.** dragons_lair completes ~55% at
|
||||||
|
burn=50 (versus 0% at burn=75/100) — the fighter survives elites
|
||||||
|
but starves en route. burn=75 isn't enough margin; burn=50
|
||||||
|
converts the starvation-out into a completion.
|
||||||
|
2. **Supply burn is the T4 modulator.** underdark/feywild step from
|
||||||
|
0% (burn=100) → 12% (burn=75) → 8% (burn=50). The burn=75 peak is
|
||||||
|
the sweet spot; halving further leaves the fighter alive *into*
|
||||||
|
more elite fights, where it dies in combat instead.
|
||||||
|
3. **Nick-floor lever is inert.** Across all burn levels, dropping
|
||||||
|
the surprise-round floor from `tier` → `1` → `0` moves T1-T3
|
||||||
|
means by at most ~3pp. The wounded-clamp (Phase 2b,
|
||||||
|
`clampSurpriseNick`) already eats the chip-damage budget on
|
||||||
|
repeat entries; the raw floor only matters on the first fresh-HP
|
||||||
|
swing, which the engine recovers from. **Recommendation: discard
|
||||||
|
this lever from the live-tuning candidate list.**
|
||||||
|
|
||||||
|
**T2-T3 wall persists.** Even at the best (floor=tier, burn=75) cell:
|
||||||
|
T2 reads 7.8% (target 62-82%) and T3 reads 3.0% (target 54-74%).
|
||||||
|
forest_shadows T2, manor_blackspire T3, and abyss_portal T5 sit at
|
||||||
|
~0% across every combo — these are zone-specific outliers, not
|
||||||
|
addressable by any global lever. **Phase 4 (per-zone outlier pass)
|
||||||
|
is the next move.**
|
||||||
|
|
||||||
|
**Next:** Phase 4 walks the matrix zone-by-zone. The shipped Phase 3
|
||||||
|
sweep already names the worst offenders (forest_shadows T2,
|
||||||
|
manor_blackspire T3, abyss_portal T5, crypt_valdris T1); each needs a
|
||||||
|
diagnostic trace (boss stat-block, roster mix, supply DC) to pick the
|
||||||
|
right per-zone tool from `Phase 4`'s list. Optional: revisit shipping
|
||||||
|
the burn=75 global if Phase 4 outlier fixes leave T4 still stuck.
|
||||||
|
|
||||||
### Phase 4 — per-zone outlier pass
|
### Phase 4 — per-zone outlier pass
|
||||||
|
|
||||||
After globals settle, the matrix will name outlier zones — tiers
|
After globals settle, the matrix will name outlier zones — tiers
|
||||||
|
|||||||
@@ -203,11 +203,24 @@ func (p *AdventurePlugin) runHarvestInterrupt(
|
|||||||
// surpriseRoundNick computes a small HP nick representing the enemy's
|
// surpriseRoundNick computes a small HP nick representing the enemy's
|
||||||
// free first swing. Roughly attack-bonus + 1d4, with a tier-based floor.
|
// free first swing. Roughly attack-bonus + 1d4, with a tier-based floor.
|
||||||
func surpriseRoundNick(m DnDMonsterTemplate, tier int) int {
|
func surpriseRoundNick(m DnDMonsterTemplate, tier int) int {
|
||||||
|
return surpriseRoundNickF(m, tier, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// surpriseRoundNickF is the floor-parameterized form used by the Phase
|
||||||
|
// 3-B sim harness lever sweep. floorOverride < 0 means "use live"
|
||||||
|
// (floor = tier); floorOverride >= 0 substitutes that absolute value
|
||||||
|
// as the floor (0 disables the floor entirely). Live callers always go
|
||||||
|
// through surpriseRoundNick. See gogobee_expedition_difficulty.md
|
||||||
|
// Phase 3-B.
|
||||||
|
func surpriseRoundNickF(m DnDMonsterTemplate, tier, floorOverride int) int {
|
||||||
if tier < 1 {
|
if tier < 1 {
|
||||||
tier = 1
|
tier = 1
|
||||||
}
|
}
|
||||||
dmg := 1 + rand.IntN(4) + m.AttackBonus/2
|
dmg := 1 + rand.IntN(4) + m.AttackBonus/2
|
||||||
floor := tier
|
floor := tier
|
||||||
|
if floorOverride >= 0 {
|
||||||
|
floor = floorOverride
|
||||||
|
}
|
||||||
if dmg < floor {
|
if dmg < floor {
|
||||||
dmg = floor
|
dmg = floor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,15 @@ func makeSupplies(tier ZoneTier, p SupplyPurchase) ExpeditionSupplies {
|
|||||||
// where HarshMod is 1×) — the dungeon is actively starving you out.
|
// where HarshMod is 1×) — the dungeon is actively starving you out.
|
||||||
// - otherwise, harshActive applies HarshMod (zone-tier scaled).
|
// - otherwise, harshActive applies HarshMod (zone-tier scaled).
|
||||||
func applyDailyBurn(s ExpeditionSupplies, harshActive, siege bool) (ExpeditionSupplies, float32) {
|
func applyDailyBurn(s ExpeditionSupplies, harshActive, siege bool) (ExpeditionSupplies, float32) {
|
||||||
|
return applyDailyBurnP(s, harshActive, siege, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyDailyBurnP is the rate-parameterized form used by the Phase 3-B
|
||||||
|
// sim harness lever sweep. burnRatePct == 0 means "use live" (100%);
|
||||||
|
// any positive value scales the final per-day burn by that percent
|
||||||
|
// (e.g. 50 = half burn). Live callers always go through applyDailyBurn.
|
||||||
|
// See gogobee_expedition_difficulty.md Phase 3-B.
|
||||||
|
func applyDailyBurnP(s ExpeditionSupplies, harshActive, siege bool, burnRatePct int) (ExpeditionSupplies, float32) {
|
||||||
burn := s.DailyBurn
|
burn := s.DailyBurn
|
||||||
switch {
|
switch {
|
||||||
case siege:
|
case siege:
|
||||||
@@ -179,6 +188,9 @@ func applyDailyBurn(s ExpeditionSupplies, harshActive, siege bool) (ExpeditionSu
|
|||||||
}
|
}
|
||||||
burn *= mult
|
burn *= mult
|
||||||
}
|
}
|
||||||
|
if burnRatePct > 0 {
|
||||||
|
burn = burn * float32(burnRatePct) / 100
|
||||||
|
}
|
||||||
s.Current -= burn
|
s.Current -= burn
|
||||||
if s.Current < 0 {
|
if s.Current < 0 {
|
||||||
s.Current = 0
|
s.Current = 0
|
||||||
|
|||||||
@@ -121,6 +121,26 @@ type expeditionBalanceProfile struct {
|
|||||||
// TestExpeditionBalance_Phase3_GlobalLeverSweep.
|
// TestExpeditionBalance_Phase3_GlobalLeverSweep.
|
||||||
EliteInterruptThresholdOverride int
|
EliteInterruptThresholdOverride int
|
||||||
ThreatDriftBaseOverride int
|
ThreatDriftBaseOverride int
|
||||||
|
|
||||||
|
// Phase 3-B global-lever overrides. Zero means "use live":
|
||||||
|
//
|
||||||
|
// SurpriseNickFloorOverride — absolute floor for the raw
|
||||||
|
// surprise-round nick. Live floor is the zone tier (1..5).
|
||||||
|
// Convention: 0 (the zero value) = "use live tier floor"; a
|
||||||
|
// positive value sets the floor directly; -1 disables the floor
|
||||||
|
// entirely (floor = 0). Lower floor = less per-fight chip damage
|
||||||
|
// on fresh entries.
|
||||||
|
//
|
||||||
|
// SupplyBurnRatePctOverride — percent multiplier on the per-day
|
||||||
|
// supply burn. Zero means "use live" (100%); 50 = half burn.
|
||||||
|
// Lower = more supply margin for T4/T5 where starvation is the
|
||||||
|
// dominant failure mode post-Phase-3-A.
|
||||||
|
//
|
||||||
|
// Both wired into the harness day loop only; live callers go
|
||||||
|
// through the shipped helpers. See
|
||||||
|
// TestExpeditionBalance_Phase3B_NickSupplySweep.
|
||||||
|
SurpriseNickFloorOverride int
|
||||||
|
SupplyBurnRatePctOverride int
|
||||||
}
|
}
|
||||||
|
|
||||||
// expeditionTrialResult is the outcome of one simulated expedition.
|
// expeditionTrialResult is the outcome of one simulated expedition.
|
||||||
@@ -248,9 +268,11 @@ func (h *expeditionHarness) advanceExpeditionOneDay() expeditionTrialResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Morning rollover — supply burn + day++.
|
// 1. Morning rollover — supply burn + day++. Phase 3-B sweep can
|
||||||
|
// scale the per-day burn via the harness profile; live callers go
|
||||||
|
// through applyDailyBurn at 100%.
|
||||||
harsh := h.exp.ThreatLevel > 60
|
harsh := h.exp.ThreatLevel > 60
|
||||||
newSupplies, _ := applyDailyBurn(h.exp.Supplies, harsh, h.exp.SiegeMode)
|
newSupplies, _ := applyDailyBurnP(h.exp.Supplies, harsh, h.exp.SiegeMode, h.supplyBurnRatePctOverride)
|
||||||
h.exp.Supplies = newSupplies
|
h.exp.Supplies = newSupplies
|
||||||
h.exp.CurrentDay++
|
h.exp.CurrentDay++
|
||||||
|
|
||||||
@@ -394,6 +416,22 @@ func (h *expeditionHarness) resolvedNickDivisor() int {
|
|||||||
return liveSurpriseNickDivisor
|
return liveSurpriseNickDivisor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolvedNickFloor translates the harness profile's surprise-nick
|
||||||
|
// floor override into the int contract surpriseRoundNickF expects
|
||||||
|
// (<0 = use live tier-floor, >=0 = absolute floor value). The profile
|
||||||
|
// uses 0 for "use live" so the struct's zero-value is the safe
|
||||||
|
// default; -1 in the profile means "disable floor" (floor = 0).
|
||||||
|
func (h *expeditionHarness) resolvedNickFloor() int {
|
||||||
|
switch {
|
||||||
|
case h.surpriseNickFloorOverride == 0:
|
||||||
|
return -1
|
||||||
|
case h.surpriseNickFloorOverride < 0:
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
return h.surpriseNickFloorOverride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// terminate stamps the final trial result with shared bookkeeping
|
// terminate stamps the final trial result with shared bookkeeping
|
||||||
// (days elapsed, threat at end, encounter count, HP%).
|
// (days elapsed, threat at end, encounter count, HP%).
|
||||||
func (h *expeditionHarness) terminate(reason string, completed, died, starved bool) expeditionTrialResult {
|
func (h *expeditionHarness) terminate(reason string, completed, died, starved bool) expeditionTrialResult {
|
||||||
@@ -438,7 +476,7 @@ func (h *expeditionHarness) runHarnessFight(zone ZoneDefinition, elite bool) Com
|
|||||||
// spiral. Mirror live exactly so the harness measures the same
|
// spiral. Mirror live exactly so the harness measures the same
|
||||||
// lever the live caller applies.
|
// lever the live caller applies.
|
||||||
nick := clampSurpriseNickD(
|
nick := clampSurpriseNickD(
|
||||||
surpriseRoundNick(monster, int(zone.Tier)),
|
surpriseRoundNickF(monster, int(zone.Tier), h.resolvedNickFloor()),
|
||||||
h.char.HPCurrent, h.char.HPMax,
|
h.char.HPCurrent, h.char.HPMax,
|
||||||
h.resolvedNickDivisor(),
|
h.resolvedNickDivisor(),
|
||||||
)
|
)
|
||||||
@@ -560,6 +598,10 @@ type expeditionHarness struct {
|
|||||||
// (eliteInterruptThreshold=19, threatDriftBase=3).
|
// (eliteInterruptThreshold=19, threatDriftBase=3).
|
||||||
eliteInterruptThresholdOverride int
|
eliteInterruptThresholdOverride int
|
||||||
threatDriftBaseOverride int
|
threatDriftBaseOverride int
|
||||||
|
// Phase 3-B levers. See expeditionBalanceProfile field doc; both
|
||||||
|
// zero-sentinel for "use live".
|
||||||
|
surpriseNickFloorOverride int
|
||||||
|
supplyBurnRatePctOverride int
|
||||||
// traceFight, if non-nil, is invoked once per fight inside
|
// traceFight, if non-nil, is invoked once per fight inside
|
||||||
// runHarnessFight with a human-readable summary. Used by the
|
// runHarnessFight with a human-readable summary. Used by the
|
||||||
// Phase 2 lethality probe to spot whether the nick, the picked
|
// Phase 2 lethality probe to spot whether the nick, the picked
|
||||||
@@ -612,6 +654,8 @@ func runExpeditionBalanceTrial(p expeditionBalanceProfile, seed uint64) expediti
|
|||||||
surpriseNickDivisorOverride: p.SurpriseNickDivisorOverride,
|
surpriseNickDivisorOverride: p.SurpriseNickDivisorOverride,
|
||||||
eliteInterruptThresholdOverride: p.EliteInterruptThresholdOverride,
|
eliteInterruptThresholdOverride: p.EliteInterruptThresholdOverride,
|
||||||
threatDriftBaseOverride: p.ThreatDriftBaseOverride,
|
threatDriftBaseOverride: p.ThreatDriftBaseOverride,
|
||||||
|
surpriseNickFloorOverride: p.SurpriseNickFloorOverride,
|
||||||
|
supplyBurnRatePctOverride: p.SupplyBurnRatePctOverride,
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
res := h.advanceExpeditionOneDay()
|
res := h.advanceExpeditionOneDay()
|
||||||
|
|||||||
@@ -720,6 +720,141 @@ func TestExpeditionBalance_Phase3_GlobalLeverSweep(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestExpeditionBalance_Phase3B_NickSupplySweep is the second
|
||||||
|
// global-tuning sweep from gogobee_expedition_difficulty.md. Phase 3-A
|
||||||
|
// surfaced the elite-bracket threshold as the dominant T1–T3 lever
|
||||||
|
// (e=23/d=1 lifted T1 from 3% → 24%) but exposed a tail-side
|
||||||
|
// fingerprint shift: T4/T5 dragons_lair death dropped 60% → 24% while
|
||||||
|
// starvation climbed to 75% — the fighter now survives elites long
|
||||||
|
// enough to run out of food. Phase 3-A picked the best cell but it
|
||||||
|
// still leaves every tier well below target band (T1 70-90%, T2
|
||||||
|
// 62-82%, …, T5 ~40%).
|
||||||
|
//
|
||||||
|
// Phase 3-B holds the Phase 3-A best cell (eliteInterruptThreshold=23,
|
||||||
|
// threatDriftBase=1) and walks two more global knobs:
|
||||||
|
//
|
||||||
|
// surpriseNickFloor (live=tier, i.e. 1..5 by zone tier) — the raw
|
||||||
|
// surprise-round nick on a fresh-HP entry. Lower floor = less
|
||||||
|
// per-fight chip on T4/T5 standard fights, where the live tier=4-5
|
||||||
|
// floor is the biggest single contributor to the wear-down curve
|
||||||
|
// that funnels survivors into starvation. Sweep {-1 (disable, =0),
|
||||||
|
// 1 (flat), 0-sentinel (live tier)}.
|
||||||
|
//
|
||||||
|
// supplyBurnRatePct (live=100, %) — daily supply burn scalar. Lower
|
||||||
|
// = more days of margin. T4 and T5 burns are 3×/4× the T1 baseline;
|
||||||
|
// halving them ought to convert the starvation outs we saw in
|
||||||
|
// Phase 3-A into actual completions if survivability is the only
|
||||||
|
// remaining blocker, or leave them dying in combat if it isn't.
|
||||||
|
// Sweep {100 (live), 75, 50}.
|
||||||
|
//
|
||||||
|
// 3×3 = 9 combos × 10 zones × 200 trials/cell, all on top of the
|
||||||
|
// Phase 3-A best cell. Diagnostic-only — no gates beyond Phase 1
|
||||||
|
// wiring sanity. -short skips.
|
||||||
|
func TestExpeditionBalance_Phase3B_NickSupplySweep(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("phase 3-B nick/supply sweep is heavy; -short skips it")
|
||||||
|
}
|
||||||
|
|
||||||
|
const trialsPerCell = 200
|
||||||
|
const baseSeed uint64 = 0xB1C5E2
|
||||||
|
// Phase 3-A best cell — held constant across this sweep.
|
||||||
|
const eliteThreshold = 23
|
||||||
|
const driftBase = 1
|
||||||
|
|
||||||
|
nickFloors := []int{-1, 1, 0} // -1 disables floor; 1 flat-1; 0 = use live tier
|
||||||
|
burnPcts := []int{50, 75, 100}
|
||||||
|
|
||||||
|
t.Logf("phase3-B nick/supply sweep — %d zones × %d nick-floors × %d burn-pcts × %d trials, Fighter @ tier centerline (rolls=4); base e=%d d=%d",
|
||||||
|
len(zoneOrder), len(nickFloors), len(burnPcts), trialsPerCell, eliteThreshold, driftBase)
|
||||||
|
|
||||||
|
type tierStat struct {
|
||||||
|
cells int
|
||||||
|
sumC float64
|
||||||
|
lo float64
|
||||||
|
hi float64
|
||||||
|
}
|
||||||
|
|
||||||
|
nickLabel := func(f int) string {
|
||||||
|
switch {
|
||||||
|
case f == 0:
|
||||||
|
return "tier"
|
||||||
|
case f < 0:
|
||||||
|
return "0"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, floor := range nickFloors {
|
||||||
|
for _, burn := range burnPcts {
|
||||||
|
tierStats := map[ZoneTier]*tierStat{}
|
||||||
|
t.Logf("─── surpriseNickFloor=%s supplyBurnPct=%d ───", nickLabel(floor), burn)
|
||||||
|
for i, id := range zoneOrder {
|
||||||
|
zone, ok := getZone(id)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("zoneOrder[%d]=%q not in registry", i, id)
|
||||||
|
}
|
||||||
|
level, ok := phase1TierCenterline[zone.Tier]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("zone %q has tier %d with no phase1 centerline mapping", id, zone.Tier)
|
||||||
|
}
|
||||||
|
profile := expeditionBalanceProfile{
|
||||||
|
ZoneID: id,
|
||||||
|
Class: ClassFighter,
|
||||||
|
Level: level,
|
||||||
|
Supplies: makeSupplies(zone.Tier, SupplyPurchase{StandardPacks: 3}),
|
||||||
|
CampType: CampTypeStandard,
|
||||||
|
EliteInterruptThresholdOverride: eliteThreshold,
|
||||||
|
ThreatDriftBaseOverride: driftBase,
|
||||||
|
SurpriseNickFloorOverride: floor,
|
||||||
|
SupplyBurnRatePctOverride: burn,
|
||||||
|
}
|
||||||
|
seed := baseSeed + uint64(i)*1_000_003 +
|
||||||
|
uint64(floor+2)*1009 + uint64(burn)*7919
|
||||||
|
r := runExpeditionBalanceCell(profile, trialsPerCell, seed)
|
||||||
|
|
||||||
|
c := r.CompletionRate() * 100
|
||||||
|
t.Logf("CELL f=%-4s b=%-3d %-18s T%d L%-2d comp=%5.1f%% death=%5.1f%% starve=%5.1f%% med_days=%2d med_threat=%3d encs=%4.1f hp_left=%5.1f%%",
|
||||||
|
nickLabel(floor), burn, zone.ID, zone.Tier, r.Profile.Level,
|
||||||
|
c,
|
||||||
|
r.DeathRate()*100,
|
||||||
|
float64(r.StarvedOuts)/float64(r.Trials)*100,
|
||||||
|
r.MedianDays, r.MedianThreatEnd,
|
||||||
|
r.AvgEncounters, r.AvgHPRemainingPct*100,
|
||||||
|
)
|
||||||
|
|
||||||
|
ts, ok := tierStats[zone.Tier]
|
||||||
|
if !ok {
|
||||||
|
ts = &tierStat{lo: math.Inf(1), hi: math.Inf(-1)}
|
||||||
|
tierStats[zone.Tier] = ts
|
||||||
|
}
|
||||||
|
ts.cells++
|
||||||
|
ts.sumC += c
|
||||||
|
if c < ts.lo {
|
||||||
|
ts.lo = c
|
||||||
|
}
|
||||||
|
if c > ts.hi {
|
||||||
|
ts.hi = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tiers := []ZoneTier{
|
||||||
|
ZoneTierBeginner, ZoneTierApprentice, ZoneTierJourneyman,
|
||||||
|
ZoneTierVeteran, ZoneTierLegendary,
|
||||||
|
}
|
||||||
|
for _, tier := range tiers {
|
||||||
|
ts := tierStats[tier]
|
||||||
|
if ts == nil || ts.cells == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mean := ts.sumC / float64(ts.cells)
|
||||||
|
t.Logf("TIER f=%-4s b=%-3d T%d n=%d mean_comp=%5.1f%% spread=%5.1f pp",
|
||||||
|
nickLabel(floor), burn, tier, ts.cells, mean, ts.hi-ts.lo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// joinZones is a tiny helper kept local to the test file so the
|
// joinZones is a tiny helper kept local to the test file so the
|
||||||
// per-tier log line reads in one logical chunk without pulling in
|
// per-tier log line reads in one logical chunk without pulling in
|
||||||
// strings.Join's import for production code.
|
// strings.Join's import for production code.
|
||||||
|
|||||||
Reference in New Issue
Block a user