Files
wizwar6e/deploy/wizwar-visitors.sh
T
Eric WagonerandClaude Fable 5.1 6e49fc9020 Credibility pass: comments say what the code can't, and the seams are gone
Engine: pit-rim exits live in one helper (pitRimExits) shared by the
resolver and the automaton; chargeReach names the six-stride cap; dust
sight is inDust + dustAtEnd; PushPending is exported once; the force-field
counter builds its events in one list; drawUnderSlowDeath says what it
draws under. Test groups are named for the rule they pin, not the
revision that introduced it.

Client: one smoothstep; one edgeScar per battle-scarred edge; one
board-zone rule (which also seats the caption strip on phones); the
compass names live in SIDE_NAMES; net.flash() is the one toast; MediaQuery
replaces two hand-rolled matchMedia listeners; sprites carry their sizes
as plain numbers and their CSS in one rule each; canvas helpers are
formatted like the rest of the tree. Comments that told how a cel or a
fallback used to look now say what it draws. The actions-over notice no
longer blames a pickup when slime ended the turn.

Server and ops: dataRoot() is the one data directory; headlineOf builds
its deeds without a cast; the rollup and skills read the same way the
code behaves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
2026-09-16 00:21:34 -04:00

93 lines
4.6 KiB
Bash
Executable File

#!/bin/bash
# The visitors digest: who is here right now and who came today — the
# rooms opened, how far each got, what humans said, what they filed —
# with today's traffic so far. Read by the wizwar-visitors skill.
# wizwar-visitors.sh [YYYY-MM-DD] (default: today, UTC)
set -u
DAY="${1:-$(date -u +%F)}"
/usr/local/bin/wizwar-rollup.sh "$DAY" >/dev/null 2>&1
python3 - "$DAY" <<'PY'
import json, sys, glob, datetime, subprocess
day = sys.argv[1]
now = datetime.datetime.now(datetime.timezone.utc)
print("as of %s UTC, day %s" % (now.strftime("%H:%M"), day))
def sh(cmd):
try: return subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=20).stdout.strip()
except Exception: return "?"
print("live sockets: %s | rooms touched in the last hour: %s" % (
sh("ss -tn state established '( sport = :8787 )' | tail -n +2 | wc -l"),
sh("find /var/lib/wizwar/rooms -name '*.jsonl' -mmin -60 | wc -l")))
# --- traffic so far, from the rollup ---
try:
rows = [json.loads(l) for l in open("/var/lib/wizwar/rollup.jsonl")]
r = [x for x in rows if x["day"] == day][-1]
p = r["paths"]
print("traffic: %d addresses, %d requests from people (%d bot); front %d, gallery %d, replays %d; campaigns %s; referrers %s" % (
r["humanAddresses"], r["human"], r["bot"], p.get("/", 0), p.get("/clips", 0), p.get("/watch", 0),
json.dumps(r["campaigns"]), json.dumps(r["referrers"])))
except Exception as e:
print("traffic: (no rollup row: %s)" % e)
# --- every room, for first sightings; today's rooms in detail ---
first_seen = {}
rooms = []
for f in glob.glob("/var/lib/wizwar/rooms/*.jsonl"):
try:
lines = open(f).read().splitlines()
meta = json.loads(lines[0])
except Exception: continue
ca = meta.get("createdAt", "")
if not ca: continue
humans = [meta["hostId"]]; bots = []; started = False; cmds = 0; chat = []; last = ca; steps = []
for l in lines[1:]:
try: x = json.loads(l)
except Exception: continue
k = x.get("kind")
if k == "join":
(bots if x.get("bot") else humans).append(x["name"])
elif k == "start": started = True
elif k == "command":
cmds += 1; last = x.get("at", last)
steps.append((x.get("playerId"), x["command"].get("type"), x.get("at", "")))
elif k == "chat":
last = x.get("at", last)
if x["player"] in humans: chat.append((x.get("at", "")[11:16], x["player"], x["text"]))
for h in humans:
if h not in first_seen or ca < first_seen[h]: first_seen[h] = ca
rooms.append(dict(id=meta["id"], created=ca, humans=humans, bots=bots, started=started, cmds=cmds, last=last, chat=chat, steps=steps))
today = sorted([r for r in rooms if r["created"].startswith(day)], key=lambda r: r["created"])
names = sorted({h for r in today for h in r["humans"]})
new = [n for n in names if first_seen[n].startswith(day)]
print("players today: %s | NEW today: %s" % (", ".join(names) or "none", ", ".join(new) or "none"))
print("rooms today: %d" % len(today))
for r in today:
span = ""
if r["cmds"]:
t0 = datetime.datetime.fromisoformat(r["created"].replace("Z", "+00:00")); t1 = datetime.datetime.fromisoformat(r["last"].replace("Z", "+00:00"))
span = "%d min" % round((t1 - t0).total_seconds() / 60)
human_cmds = [s for s in r["steps"] if s[0] in r["humans"]]
last_human = ("last human move: %s %s at %s" % (human_cmds[-1][0], human_cmds[-1][1], human_cmds[-1][2][11:16])) if human_cmds else "no human move"
state = "lobby only" if not r["started"] else ("started, no moves" if not r["cmds"] else "in play")
print(" %s %s %s%s %s %d moves (%d human) %s %s" % (
r["created"][11:16], r["id"], "+".join(r["humans"]), (" vs %d bot%s" % (len(r["bots"]), "" if len(r["bots"]) == 1 else "s")) if r["bots"] else "",
state, r["cmds"], len(human_cmds), span, last_human))
for at, who, text in r["chat"]: print(" %s %s: %s" % (at, who, text))
# --- the desk ---
reports = 0; unanswered = 0; answered = set()
try:
rows = [json.loads(l) for l in open("/var/lib/wizwar/feedback.jsonl") if l.strip()]
answered = {x["reportId"] for x in rows if "reportId" in x}
for x in rows:
if "happened" not in x: continue
if (x.get("id") or x["at"]) not in answered: unanswered += 1
if x["at"].startswith(day):
reports += 1
print(" REPORT %s %s (%s): %s / expected: %s" % (x["at"][11:16], x["player"], x["roomId"], x["happened"], x["expected"]))
except OSError: pass
print("feedback: %d filed today, %d unanswered overall" % (reports, unanswered))
PY