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:
Ilia Denisov
2026-06-06 09:59:12 +02:00
parent 6886efb6c0
commit 635f2fd9fc
30 changed files with 1068 additions and 120 deletions
@@ -28,6 +28,12 @@
{{else}}<p class="note">no statistics</p>{{end}}
</section>
</div>
{{if .MoveChart}}
<section class="panel"><h2>Move timing</h2>
<p class="note">Think time per move number across all games — <span class="lg lg-min">min</span> · <span class="lg lg-avg">mean</span> · <span class="lg lg-max">max</span>.</p>
{{.MoveChart}}
</section>
{{end}}
<section class="panel"><h2>Identities</h2>
<table class="list">
<thead><tr><th>Kind</th><th>External ID</th><th>Confirmed</th><th>Created</th></tr></thead>
@@ -2,7 +2,7 @@
<h1>Users</h1>
{{with .Data}}
<table class="list">
<thead><tr><th>Account</th><th>Display name</th><th>Kind</th><th>Lang</th><th>Created</th></tr></thead>
<thead><tr><th>Account</th><th>Display name</th><th>Kind</th><th>Lang</th><th>Created</th><th title="per-move think time across all games">Move min</th><th>avg</th><th>max</th></tr></thead>
<tbody>
{{range .Items}}
<tr>
@@ -11,9 +11,10 @@
<td>{{.Kind}}</td>
<td>{{.Language}}</td>
<td>{{.CreatedAt}}</td>
{{if .HasMoveStats}}<td>{{.MoveMin}}</td><td>{{.MoveAvg}}</td><td>{{.MoveMax}}</td>{{else}}<td colspan="3"><span class="note">—</span></td>{{end}}
</tr>
{{else}}
<tr><td colspan="5"><span class="note">no users</span></td></tr>
<tr><td colspan="8"><span class="note">no users</span></td></tr>
{{end}}
</tbody>
</table>