mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
N3/P3: initiative, and a turn engine that seats a party
The turn engine ran a fixed player -> enemy -> round_end phase machine over one player and one monster. It now runs a round as a sequence of seats. turnOrder derives that sequence per round. A solo roster short-circuits to the historical [player, enemy] and rolls nothing -- the duel has never had initiative, and handing the monster a coin flip on who swings first would be a live balance change. A party rolls it with the auto-resolve engine's own formula (speed + d10 + InitiativeBias). Every seat in a round shares a (round, phase) pair, so the acting seat is mixed into the RNG *seed* rather than the stream. Seat 0 and the enemy sentinel mix to nothing, which is what keeps a solo fight drawing exactly the pre-roster stream across a suspend/resume. The round cursor persists as Statuses.TurnIdx, omitempty so no solo row carries it. A fight that was in flight when the field landed decodes it as 0; turnIdxForPhase reconciles that against Phase, which is the older and load-bearing field. Without it, a suspended enemy_turn would resume, step, and land back on enemy_turn forever. The enemy now picks a target uniformly among the standing roster (solo draws nothing), a downed seat forfeits its turn silently, and the fight is lost only when anyAlive() goes false -- not when the acting seat drops. That last one fixes a latent solo bug on the way past: resolvePlayerSwings returns false when a retaliate aura kills the swinger between extra attacks, and the old code walked that corpse into the enemy's turn. commit() reads seat 0 explicitly instead of the cursor, which the enemy turn parks on its target and round_end walks across the roster. TestCombatCharacterization is byte-identical: solo balance did not move.
This commit is contained in:
@@ -181,7 +181,7 @@ func turnSession(phase string, playerHP, enemyHP int) *CombatSession {
|
||||
|
||||
func stepEngine(sess *CombatSession, player, enemy *Combatant, action PlayerAction) ([]CombatEvent, error) {
|
||||
rng := rand.New(rand.NewPCG(42, phaseOrdinal(sess.Phase)))
|
||||
te := resumeTurnEngine(sess, player, enemy, rng)
|
||||
te := resumeTurnEngine(sess, []*Combatant{player}, enemy, rng)
|
||||
events, err := te.step(action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -375,7 +375,7 @@ func TestTurnEngine_StepRejectsTerminalSession(t *testing.T) {
|
||||
player, enemy := basePlayer(), baseEnemy()
|
||||
|
||||
rng := rand.New(rand.NewPCG(1, 1))
|
||||
te := resumeTurnEngine(sess, &player, &enemy, rng)
|
||||
te := resumeTurnEngine(sess, []*Combatant{&player}, &enemy, rng)
|
||||
if _, err := te.step(PlayerAction{Kind: ActionAttack}); err != errCombatSessionOver {
|
||||
t.Errorf("err = %v, want errCombatSessionOver", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user