Files
my-world/tools/test/race_game_check.py
T
aibrashandClaude Sonnet 5.5 9130202a51 Карта Race: гонка на ногах по киберпанк-антиутопии, 8 игроков
- maps/race: трасса 633 м из Blender (три развилки: срез по контейнерам, каньон со стенами
  против нижней дороги, труба с лазерами против коридора), запечённый свет, небо, дождь, гул;
  фоновая песня (Ogg Vorbis, ~0,6 МБ) включается на «СТАРТ!»
- Правила (race_rules.gd): R — готов; когда готовы все — отсчёт 6 с, заморозка в слотах,
  ворота строго по порядку, сплиты, место, итоги, личный рекорд (user://race_best.cfg)
- Сеть: Net.submit_race / recv_race / race_event (RACE_READY, START, CP, FINISH, STATE).
  ВНИМАНИЕ: набор RPC изменён — клиент и сервер выкладываются вместе, старый клиент
  с новым сервером не работает (rpc node checksum failed)
- player.gd: input_frozen и place() для режимов карт; project.godot: действие ready (R)
- Портал RACE в хабе, пресет экспорта Race (include_filter для ресурсов, грузимых строкой)
- tools/test: двухклиентный тест гонки на локальном сервере, бот, облёт, проверка пакета
- README: правила гонки, события, проверки, заметки

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WxZo3ekR5m1af437Qzqwc1
2026-10-28 17:28:05 +05:00

85 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Сверка логов race_game_test.sh: A, B (два круга), C (зашёл в разгар круга 1, участвует в круге 2 и выходит)."""
import re, sys, os
W = sys.argv[1]
logs = {n: open(os.path.join(W, n + ".log"), errors="replace").read().splitlines() for n in "ABC"}
ok = True
def check(cond, text):
global ok
print((" ДА " if cond else " НЕТ ") + text)
if not cond:
ok = False
def rounds_of(lines):
"""Таблицы итогов по кругам: список [ [(место, id, время, ворот)...], ... ]"""
res, cur = [], []
for l in lines:
m = re.search(r"итог: место (\d+) игрок (-?\d+) время (-?[\d.]+) ворот (\d+)", l)
if m:
if cur and int(m.group(1)) == 1:
res.append(cur); cur = []
cur.append((int(m.group(1)), int(m.group(2)), float(m.group(3)), int(m.group(4))))
elif cur and "фаза -> WAIT" in l:
res.append(cur); cur = []
if cur: res.append(cur)
return res
ra, rb = rounds_of(logs["A"]), rounds_of(logs["B"])
print("Итоговые таблицы A:", ra)
print("Итоговые таблицы B:", rb)
check(len(ra) == 2 and len(rb) == 2, "оба клиента показали итоги двух кругов (%d и %d)" % (len(ra), len(rb)))
for i in range(min(len(ra), len(rb))):
check(ra[i] == rb[i], "круг %d: таблица мест одинакова на A и B" % (i + 1))
if ra[i]:
times = [r[2] for r in ra[i]]
check(len(ra[i]) == 2 and all(t > 0 for t in times) and len(set(times)) == len(times),
"круг %d: два финиша с разным временем %s" % (i + 1, times))
check([r[0] for r in ra[i]] == [1, 2] and ra[i][0][2] < ra[i][1][2], "круг %d: места 1 и 2 по возрастанию времени" % (i + 1))
if len(ra) == 2:
check(len(ra[1]) == 2, "круг 2: C вышел в разгар и убран из итогов (в таблице %d строк)" % len(ra[1]))
# один START на круг
starts = sum(1 for n in "ABC" for l in logs[n] if "даю старт" in l)
check(starts == 2, "команду START дал один клиент на круг (всего %d за 2 круга)" % starts)
for n in "AB":
senders = re.findall(r"старт от (\d+): слот (\d+) из (\d+)", "\n".join(logs[n]))
print(" старт на %s: %s" % (n, senders))
sa = re.findall(r"старт от (\d+): слот (\d+) из (\d+)", "\n".join(logs["A"]))
sb = re.findall(r"старт от (\d+): слот (\d+) из (\d+)", "\n".join(logs["B"]))
sc = re.findall(r"старт от (\d+): слот (\d+) из (\d+)", "\n".join(logs["C"]))
check(len(sa) == 2 and len(sb) == 2 and [s[0] for s in sa] == [s[0] for s in sb], "A и B приняли одних и тех же отправителей START в обоих кругах")
if len(sa) == 2 and len(sb) == 2:
check(sa[0][2] == "2" and sa[1][2] == "3", "в круге 1 участвовали 2, в круге 2 — 3 игрока (A: %s)" % [s[2] for s in sa])
check(sorted([sa[1][1], sb[1][1], sc[0][1] if sc else "?"]) == ["0", "1", "2"] and sa[0][1] != sb[0][1], "слоты в кругах различны и подряд")
# синхронность старта по unix-времени (фаза RACE)
def race_unix(lines):
return [float(m.group(1)) for l in lines for m in [re.search(r"ФАЗА RACE, unix=([\d.]+)", l)] if m]
ua, ub = race_unix(logs["A"]), race_unix(logs["B"])
print(" unix начала гонки A:", ua, " B:", ub)
check(len(ua) == 2 and len(ub) == 2 and all(abs(x - y) < 0.35 for x, y in zip(ua, ub)), "начало гонки на A и B совпадает в пределах 0,35 с: %s" % [round(x - y, 3) for x, y in zip(ua, ub)])
# заморозка
fr = [float(m.group(1)) for n in "ABC" for l in logs[n] for m in [re.search(r"ЗАМОРОЗКА: сдвиг за отсчёт ([\d.]+)", l)] if m]
check(len(fr) >= 5 and max(fr) < 0.01, "заморозка на отсчёте: смещения %s" % fr)
# музыка
mus_after = [l for n in "AB" for l in logs[n] if "МУЗЫКА после старта" in l]
mus_cd = [l for n in "AB" for l in logs[n] if "МУЗЫКА на отсчёте" in l]
check(len(mus_after) >= 4 and all("играет=true" in l for l in mus_after), "музыка играет после старта (%d проверок)" % len(mus_after))
check(len(mus_cd) >= 4 and all("false" in l for l in mus_cd), "на отсчёте музыки нет (%d проверок)" % len(mus_cd))
# возрождение
resp = [l for n in "AB" for l in logs[n] if "ВОЗРОЖДЕНИЕ" in l]
for l in resp: print(" ", l)
check(len(resp) >= 4 and all("-> ВЕРНО" in l for l in resp), "возрождение у ворот лицом по трассе (%d проверок, ошибок нет)" % len(resp))
# C: зритель, потом участник
c = "\n".join(logs["C"])
check("ГОНКА_ИДЁТ" in c and "участник=false" in c, "C зашёл в разгар круга 1: «гонка идёт», без зачёта")
check("УЧАСТНИК=true" in c, "C участвует в круге 2")
check("ВЫХОЖУ из игры" in c, "C вышел в разгар круга 2")
for n in "ABC":
errs = [l for l in logs[n] if "SCRIPT ERROR" in l or (l.startswith("ERROR") and "still in use at exit" not in l) or "Parse Error" in l]
check(not errs, "в логе %s нет ошибок скриптов %s" % (n, errs[:2]))
check("ГОТОВО" in "\n".join(logs["A"]) and "ГОТОВО" in "\n".join(logs["B"]), "A и B дошли до конца теста")
print("\nПРОВЕРКА ПРОЙДЕНА" if ok else "\nПРОВЕРКА ПРОВАЛЕНА")
sys.exit(0 if ok else 1)