Stage 17: backend defect fixes (nudge code, TG name, robot names/timing, multi-device push, move-duration metric + admin analytics)
- #3 nudge-on-own-turn: distinct result code nudge_own_turn + i18n (was reused 'not_your_turn') - #2 sanitize connector registration name to the editable format; Player/Игрок-XXXXX fallback - #5 variant-aware robot name pools (composed full/colloquial first + surname forms; ru gets <=20% latin) - #4 move-number-aware robot move timing (early 1-5min -> late 10-90min, skew k=4) - #7 emit move event to the actor too (multi-device sync); opponent_moved stays in-app only - #1 live game_move_duration{variant,phase} histogram + admin console per-user min/avg/max columns and an inline-SVG move-time-by-move-number chart (offline from the journal) - ProvisionRobot bypasses editor name validation (system names like 'Peter J.')
This commit is contained in:
@@ -27,14 +27,14 @@ func TestPlayToWinDistribution(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMoveDelayBoundsAndDeterminism checks every sampled delay stays in
|
||||
// [2min, 90min) and is reproducible for a (seed, moveCount).
|
||||
// TestMoveDelayBoundsAndDeterminism checks every sampled delay stays in the hard
|
||||
// bounds [1min, 90min] and is reproducible for a (seed, moveCount).
|
||||
func TestMoveDelayBoundsAndDeterminism(t *testing.T) {
|
||||
for seed := int64(1); seed <= 200; seed++ {
|
||||
for mc := 0; mc < 50; mc++ {
|
||||
d := moveDelay(seed, mc)
|
||||
if d < 2*time.Minute || d >= 90*time.Minute {
|
||||
t.Fatalf("delay %s out of [2m,90m) for seed=%d mc=%d", d, seed, mc)
|
||||
if d < 1*time.Minute || d > 90*time.Minute {
|
||||
t.Fatalf("delay %s out of [1m,90m] for seed=%d mc=%d", d, seed, mc)
|
||||
}
|
||||
if moveDelay(seed, mc) != d {
|
||||
t.Fatalf("delay not deterministic for seed=%d mc=%d", seed, mc)
|
||||
@@ -43,22 +43,49 @@ func TestMoveDelayBoundsAndDeterminism(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMoveDelaySkew checks the distribution is right-skewed with the intended
|
||||
// ~10-minute median: most delays are short, the mean sits above the median.
|
||||
// TestMoveDelayGrowsWithMoveCount checks the delay band shifts up over a game: the
|
||||
// first move lives in the short [1,5]min band, a late move in the long [10,90]min
|
||||
// band, so the median think time rises with the move count.
|
||||
func TestMoveDelayGrowsWithMoveCount(t *testing.T) {
|
||||
median := func(mc int) float64 {
|
||||
const n = 4000
|
||||
xs := make([]float64, n)
|
||||
for s := 0; s < n; s++ {
|
||||
xs[s] = moveDelay(int64(s+1), mc).Minutes()
|
||||
}
|
||||
sort.Float64s(xs)
|
||||
return xs[n/2]
|
||||
}
|
||||
for s := int64(1); s <= 500; s++ {
|
||||
if d := moveDelay(s, 0).Minutes(); d < 1 || d > 5 {
|
||||
t.Fatalf("first-move delay %.2f out of [1,5] for seed %d", d, s)
|
||||
}
|
||||
if d := moveDelay(s, 40).Minutes(); d < 10 || d > 90 {
|
||||
t.Fatalf("late-move delay %.2f out of [10,90] for seed %d", d, s)
|
||||
}
|
||||
}
|
||||
if early, late := median(0), median(30); early >= late {
|
||||
t.Errorf("median should grow with move count: move0=%.1f move30=%.1f", early, late)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMoveDelaySkew checks the late-game distribution is right-skewed at a fixed move
|
||||
// count: short delays are frequent (median near the band floor) and the mean sits
|
||||
// above the median, with a tail toward the cap.
|
||||
func TestMoveDelaySkew(t *testing.T) {
|
||||
const n = 20000
|
||||
mins := make([]float64, 0, n)
|
||||
var sum float64
|
||||
for mc := 0; mc < n; mc++ {
|
||||
m := moveDelay(42, mc).Minutes()
|
||||
for s := 0; s < n; s++ {
|
||||
m := moveDelay(int64(s+1), 28).Minutes() // late band [10,90]
|
||||
mins = append(mins, m)
|
||||
sum += m
|
||||
}
|
||||
sort.Float64s(mins)
|
||||
median := mins[n/2]
|
||||
mean := sum / float64(n)
|
||||
if median < 7 || median > 13 {
|
||||
t.Errorf("median delay = %.1f min, want ~10 (7-13)", median)
|
||||
if median < 12 || median > 20 {
|
||||
t.Errorf("late median delay = %.1f min, want ~15 (12-20)", median)
|
||||
}
|
||||
if mean <= median {
|
||||
t.Errorf("mean %.1f should exceed median %.1f (right skew)", mean, median)
|
||||
|
||||
Reference in New Issue
Block a user