Stage 17 round 5 (L2): robot play-to-win intent + next-move ETA in the admin game card
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 29s
CI / gate (pull_request) Successful in 1s
CI / deploy (pull_request) Successful in 1m14s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 29s
CI / gate (pull_request) Successful in 1s
CI / deploy (pull_request) Successful in 1m14s
The admin game detail now shows, per robot seat, the game's deterministic play-to-win decision (from the bag seed) and — while it is that robot's turn — its scheduled next-move ETA (sampled think-time delay, deferred past the sleep window), plus a caption with the ~40% global target. Wiring: robot.PlayToWin/NextMoveAt/PlayToWinTargetPercent exports, account.IsRobot, game RobotSchedule (seed + turn-start). Tests: NextMoveAt invariants (never early, never in the sleep window), PlayToWin export, and an admin render integration test asserting the intent + ETA + target appear.
This commit is contained in:
@@ -207,6 +207,37 @@ func TestMixDeterministic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestNextMoveAt checks the exported schedule used by the admin ETA: the instant is never
|
||||
// earlier than the sampled think-time delay, and it never lands while the robot is asleep
|
||||
// (a delay that would fall in the sleep window is deferred to the wake time).
|
||||
func TestNextMoveAt(t *testing.T) {
|
||||
base := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
for seed := int64(1); seed <= 500; seed++ {
|
||||
for _, h := range []int{0, 2, 6, 9, 14, 23} { // turn starts across the day
|
||||
start := base.Add(time.Duration(h) * time.Hour)
|
||||
at := NextMoveAt(seed, 3, start, "UTC")
|
||||
if at.Before(start.Add(moveDelay(seed, 3))) {
|
||||
t.Fatalf("seed %d h %d: ETA %s earlier than the scheduled delay", seed, h, at)
|
||||
}
|
||||
if asleep("UTC", sleepDrift(seed), at) {
|
||||
t.Fatalf("seed %d h %d: ETA %s lands in the sleep window", seed, h, at)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPlayToWinExport checks the exported decision matches the internal one and the target.
|
||||
func TestPlayToWinExport(t *testing.T) {
|
||||
for seed := int64(1); seed <= 200; seed++ {
|
||||
if PlayToWin(seed) != playToWin(seed) {
|
||||
t.Fatalf("PlayToWin(%d) != playToWin", seed)
|
||||
}
|
||||
}
|
||||
if PlayToWinTargetPercent != playToWinPercent {
|
||||
t.Errorf("PlayToWinTargetPercent = %d, want %d", PlayToWinTargetPercent, playToWinPercent)
|
||||
}
|
||||
}
|
||||
|
||||
// plays builds candidate plays carrying only the given scores (ranked as passed).
|
||||
func plays(scores ...int) []engine.MoveRecord {
|
||||
out := make([]engine.MoveRecord, len(scores))
|
||||
|
||||
Reference in New Issue
Block a user