123 lines
5.8 KiB
Bash
123 lines
5.8 KiB
Bash
#!/usr/bin/env bash
|
|
# #162 8-NODE STRESS -- maximum-entropy K/D chaos before the Friday field test.
|
|
# Eight nodes, one drop zone, EVERYONE autofires missile salvos at everyone
|
|
# (staggered periods -> continuous overlapping splash), plus GOTO=enemy so the
|
|
# pack stays in knife-fight range. Simultaneous kills, mutual kills, splash
|
|
# suicides, respawn pileups -- all of it.
|
|
# Invariants are RECEIPT-DERIVED (scenario-agnostic):
|
|
# I1 PLAYER_DEAD == real deaths I2 deathcost APPLYING == deaths
|
|
# I3 SCORE type=2 == deaths I4 SWALLOWED == 0 (the synchronous
|
|
# latch means a duplicate can no longer even be GENERATED)
|
|
# I6 every player's kills/deaths mirror converges to authority on some node
|
|
# I7 no mirror anywhere exceeds authority (no phantom tallies)
|
|
# I8 zero DMG inst=R deliveries
|
|
set -x
|
|
. /c/git/bt411/scratchpad/night6/bench_common.sh
|
|
cd /c/git/bt411/content || exit 1
|
|
taskkill //F //IM btl4.exe >/dev/null 2>&1; sleep 3
|
|
rm -f kd8_*.log
|
|
mkdir -p /c/git/bt411/scratchpad/night15/mlbak && mv matchlog_*.txt /c/git/bt411/scratchpad/night15/mlbak/ 2>/dev/null
|
|
bt_expert_egg MP.EGG KD8.EGG
|
|
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=madcat/" KD8.EGG
|
|
python - <<'EOF'
|
|
import io, re, sys
|
|
txt = io.open('KD8.EGG', encoding='latin-1').read()
|
|
m = re.search(r"\[127\.0\.0\.1:1602\](?:\n[^\[\n][^\n]*)*", txt)
|
|
page = m.group(0)
|
|
adds = []
|
|
for i in range(6):
|
|
addr = "127.0.0.1:%d" % (1702 + i * 100)
|
|
p = page.replace("[127.0.0.1:1602]", "[%s]" % addr)
|
|
p = re.sub(r"name=\S+", "name=Strs%d" % (i + 3), p)
|
|
p = re.sub(r"dropzone=\S+", "dropzone=one", p)
|
|
txt += "\n" + p + "\n"
|
|
adds.append("pilot=%s" % addr)
|
|
txt = txt.replace("pilot=127.0.0.1:1602",
|
|
"pilot=127.0.0.1:1602\n" + "\n".join(adds))
|
|
io.open('KD8.EGG', 'w', encoding='latin-1').write(txt)
|
|
pages = txt.count("advancedDamage=1")
|
|
print("pilot pages with advancedDamage=1:", pages)
|
|
sys.exit(0 if pages >= 8 else 1)
|
|
EOF
|
|
[ $? -eq 0 ] || { echo "FAIL: egg extension broke advancedDamage stamping"; exit 1; }
|
|
|
|
# Eight combatants, one core each, staggered salvo periods.
|
|
PORTS=(1501 1601 1701 1801 1901 2001 2101 2201)
|
|
AFFS=(0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80)
|
|
PERIODS=(3 4 5 7 3 4 5 7)
|
|
for i in 0 1 2 3 4 5 6 7; do
|
|
( export BT_GOTO=enemy BT_GOTO_STOP=90
|
|
export BT_AUTOFIRE=1 BT_AF_MISSILE=1 BT_AF_PERIOD=${PERIODS[$i]}
|
|
export BT_SCORE_LOG=1 BT_DEATH_LOG=1 BT_MATCHLOG=1
|
|
bt_launch kd8_$i.log KD8.EGG ${AFFS[$i]} -net ${PORTS[$i]} )
|
|
sleep 2
|
|
done
|
|
sleep 5
|
|
python ../tools/btconsole.py KD8.EGG \
|
|
127.0.0.1:1501 127.0.0.1:1601 127.0.0.1:1701 127.0.0.1:1801 \
|
|
127.0.0.1:1901 127.0.0.1:2001 127.0.0.1:2101 127.0.0.1:2201 > kd8_r.log 2>&1 &
|
|
R=$!; sleep 480; kill $R 2>/dev/null; sleep 2
|
|
bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe >/dev/null 2>&1
|
|
|
|
python - <<'PY'
|
|
import glob, io, re, sys
|
|
|
|
deaths = pdead = type2 = swall = applying = dmg_r = 0
|
|
mirrors = {}
|
|
for path in glob.glob("matchlog_*.txt"):
|
|
for line in io.open(path, encoding="latin-1", errors="replace"):
|
|
if line.startswith("DEATH ") and "inst=M" in line:
|
|
deaths += 1
|
|
elif line.startswith("PLAYER_DEAD"):
|
|
pdead += 1
|
|
elif line.startswith("SCORE") and "type=2" in line:
|
|
type2 += 1
|
|
elif line.startswith("DMG") and "inst=R" in line:
|
|
dmg_r += 1
|
|
elif line.startswith("SBMIRROR"):
|
|
m = re.search(r"player=(\d+:\d+) kills=(\d+) deaths=(\d+)", line)
|
|
if m:
|
|
key = (path, m.group(1))
|
|
k, d = int(m.group(2)), int(m.group(3))
|
|
pk, pd = mirrors.get(key, (0, 0))
|
|
mirrors[key] = (max(pk, k), max(pd, d))
|
|
for path in glob.glob("kd8_*.log"):
|
|
txt = io.open(path, encoding="latin-1", errors="replace").read()
|
|
swall += txt.count("SWALLOWED")
|
|
applying += txt.count("-> APPLYING")
|
|
|
|
print("=== #162 8-NODE STRESS INVARIANTS ===")
|
|
print("deaths / PLAYER_DEAD / type2 / APPLYING:", deaths, pdead, type2, applying)
|
|
print("SWALLOWED:", swall, " DMG inst=R:", dmg_r)
|
|
auth_d = {}; auth_k = {}
|
|
for path in glob.glob("matchlog_*.txt"):
|
|
for line in io.open(path, encoding="latin-1", errors="replace"):
|
|
m = re.search(r"PLAYER_DEAD .*player=(\d+:\d+) .*tally=(\d+)", line)
|
|
if m: auth_d[m.group(1)] = max(auth_d.get(m.group(1), 0), int(m.group(2)))
|
|
m = re.search(r"SCORE .*player=(\d+:\d+) type=2 .*kills=(\d+)", line)
|
|
if m: auth_k[m.group(1)] = max(auth_k.get(m.group(1), 0), int(m.group(2)))
|
|
print("authoritative kills:", auth_k)
|
|
print("authoritative deaths:", auth_d)
|
|
|
|
fails = []
|
|
if deaths < 10: fails.append("LOW STRESS: only %d deaths -- lengthen/retune" % deaths)
|
|
if pdead != deaths: fails.append("I1 FAIL: PLAYER_DEAD %d != deaths %d" % (pdead, deaths))
|
|
if applying != deaths: fails.append("I2 FAIL: APPLYING %d != deaths %d" % (applying, deaths))
|
|
if type2 != deaths: fails.append("I3 FAIL: type2 %d != deaths %d" % (type2, deaths))
|
|
if swall: fails.append("I4 FAIL: %d SWALLOWED (duplicate GENERATED despite the synchronous latch)" % swall)
|
|
for pl, want in auth_k.items():
|
|
got = max((k for (p, q), (k, d) in mirrors.items() if q == pl), default=0)
|
|
if want and got != want:
|
|
fails.append("I6 FAIL: %s kills mirrored %d != %d" % (pl, got, want))
|
|
for pl, want in auth_d.items():
|
|
got = max((d for (p, q), (k, d) in mirrors.items() if q == pl), default=0)
|
|
if want and got != want:
|
|
fails.append("I6 FAIL: %s deaths mirrored %d != %d" % (pl, got, want))
|
|
for (p, pl), (k, d) in mirrors.items():
|
|
if k > auth_k.get(pl, 0): fails.append("I7 FAIL: %s kills mirror %d > auth" % (pl, k))
|
|
if d > auth_d.get(pl, 0): fails.append("I7 FAIL: %s deaths mirror %d > auth" % (pl, d))
|
|
if dmg_r: fails.append("I8 FLAG: %d DMG inst=R" % dmg_r)
|
|
print("VERDICT:", "PASS -- all 8-node stress invariants hold" if not fails else "; ".join(sorted(set(fails))))
|
|
sys.exit(0 if not fails else 1)
|
|
PY
|