mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Combat engine §2(a): the enemy charges for what a seat brings
Both scaling levers counted seats. partyEnemyHPScale gave +15% boss HP for any roster >= 2, and partyActionExpectation lifted the enemy from 1 to 2.4 attacks a round. A seat COUNT charges the same for an under-levelled friend, a hired NPC, and a true peer — so a below-median body cost a full seat's worth of boss and did not give a full seat's worth back. Measured, once the companion's free full-heal was taken away and he became honest: hiring him was WORSE than going alone (66.1% against solo's 69.0%). That is this bug, and it has been live for every under-levelled friend anyone has ever invited. Seats now carry a SeatWeight, and both levers scale on the summed weight of the LIVING seats rather than on a head count. The weight is level-based, priced against the leader, times a discount for a hireling (no subclass, no magic items, gear that is never Masterwork — the layers a player accrues and a hireling never will). Level, and deliberately not a power score: an HP-x-damage proxy would rank a cleric below a fighter and quietly make every mixed HUMAN party easier, which is a difficulty regression smuggled in under a bug fix. The safety argument is one property: **a peer weighs exactly 1.0**. So the curves interpolate between the integer knots the P8 sweep tuned — (1, 1.0), (2, 2.4), 2n-1 from 3 up — and every integer input returns exactly what it always returned. Solo is byte-identical, a party of same-level humans is byte-identical, both goldens hold unmoved, and only an UNEQUAL roster lands between the knots. That is the entire point of the change. It also finishes §2(b): a seat that is down now buys the enemy nothing. §2(b) fixed the head-count half; a corpse still carried its full weight until this. Measured, 640 runs/arm, same grid: solo 69.0% (unchanged — corpus intact) + Pete 76.8% (+7.8pp) + a human cleric peer 77.6% (+8.6pp) band solo +Pete lift trailing (<40%) 10.0% 31.0% +21.0pp middle 58.9% 76.8% +17.9pp leading (>=70%) 93.5% 99.2% +5.7pp Help, never a carry: he rescues the players who were drowning and barely moves the ones who were already fine — and he stays below a real human of the leader's level, which is the invariant a hireling must never break. Claude-Session: https://claude.ai/code/session_01J5SQZWoLmL3M3mw2XmHHdy
This commit is contained in:
@@ -142,11 +142,22 @@ func TestP8PartyScaling_SoloExemptPartyScaled(t *testing.T) {
|
||||
if got := partyActionExpectation(2); got != 2.4 {
|
||||
t.Fatalf("duo action expectation = %v, want 2.4", got)
|
||||
}
|
||||
// The curve now takes a fractional weight rather than a head count, so that a
|
||||
// below-median seat costs the enemy less than a peer does. Every INTEGER input
|
||||
// must still return exactly what it always returned — that is what keeps solo
|
||||
// and a party of peers byte-identical, and the balance corpus with them.
|
||||
for n := 3; n <= 5; n++ {
|
||||
if got, want := partyActionExpectation(n), float64(2*n-1); got != want {
|
||||
if got, want := partyActionExpectation(float64(n)), float64(2*n-1); got != want {
|
||||
t.Fatalf("party of %d: action expectation = %v, want %v", n, got, want)
|
||||
}
|
||||
}
|
||||
// A duo carrying a half-strength body sits between soloing and a true duo.
|
||||
if got := partyActionExpectation(1.5); got <= 1 || got >= 2.4 {
|
||||
t.Fatalf("weight 1.5 action expectation = %v, want strictly between 1 and 2.4", got)
|
||||
}
|
||||
if got := partyEnemyHPScale(1.5); got <= 1.0 || got >= 1.15 {
|
||||
t.Fatalf("weight 1.5 HP scale = %v, want strictly between 1.0 and 1.15", got)
|
||||
}
|
||||
if got := partyEnemyHPScale(3); got != 1.15 {
|
||||
t.Fatalf("party HP scale = %v, want 1.15", got)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user