v0.7.1: durable OPS baseline, Model Leaderboard, P2P audit sync, profiles & themes - #1
v0.7.1: durable OPS baseline, Model Leaderboard, P2P audit sync, profiles & themes#1DrGekoz wants to merge 28 commits into
Conversation
… chat - Fix office layout overflow (flex min-height:0 + overflow:hidden) so the scene fits the viewport and all panels scroll correctly. - Leaderboard: GitHub usernames only (agents/subagents/local rows never shown); rank + tier number + tier icon per row, clickable profile cards. - Tier ladder: all 500 IdleViber tier icons (#N + icon), exponential Ops curve reaching 5,000,000 at tier 500. - Office chat: WebRTC P2P mesh, cached history, per-sender tier badge, sound toggle. - GitHub connect: login sets your username + avatar on leaderboard and chat. - Header stats strip (Ops, calls, sessions, time ran). - Hover-reveal themed scrollbars on leaderboard / tiers / chat panels.
…odal Server: - Persist per-user profiles (bio, links, chosen tier icon, profile/interface themes) in profiles.json; /profile GET + POST endpoints. - Fold profile fields into leaderboard rows so the frontend + P2P see them. Frontend: - Profile popup rewrite: GitHub avatar, chosen tier icon + number, bio, brand links (YouTube/Spotify/SoundCloud/GitHub/Discord/Steam, multiples allowed) with platform icons, profile theme accent color, and "Multiplayer Fork Developer" for DrGekoz (DEV chip kept). Edit button on your own profile. - Profile editor (#edit): bio, per-platform links add/remove, 500-icon tier picker, profile theme color, and an interface theme with per-element color pickers (bg, panel, buttons, borders, highlight, font, sub-fonts, status, scrollbar, font size/family) with live preview + save. - Interface theming: whole UI refactored to CSS custom properties; theme persists locally (localStorage) across reloads and rides the P2P score packets so peers see each other's profile themes. - Connect modal: removed the "enter a github username" box; added a working Edit Profile button. - Chat: tier icon now sits left of the GitHub profile picture. - Leaderboard widens to fit its longest row adaptively. - About/Credits modal: story about the original plugin (Teknium) + the DrGekoz / Hermes IdleViber enhancement, with links.
…rs, cleanup - On-hover tier tooltip (top-right): large 128px icon + tier name/#, Ops requirement, and Ops-derived calls + sessions estimates. - Large tier icons: copied the 500 IdleViber 256px icons (resized to 128px) into web/icons/large/ and use them in tooltips, profile popups, and the tier-unlock modal; server /icons/ now safely serves the subdirectory. - Save fix: profile edits (bio, links, tier icon, themes) save locally and apply immediately + ride the P2P score packets, so clicking a username in the leaderboard or chat populates their full profile - no more "Save failed". - Tier unlock: locked tiers render as silhouettes; reaching a new tier plays a fanfare (if sound on) and opens a side-by-side modal to keep/use the new tier icon. - Profile AGENTS list: shows live status, hides idle/gone agents, and is sent through P2P so peers see real-time rosters. - Tier picker (edit modal): tier number under each icon. - Connect modal: removed the Edit-Profile button; clicking your name in the top bar now opens your own profile (or the connect modal when logged out); the login button reads "Logged in with GitHub" when connected. - Profile editor: red (non-themeable) Log out button under Save. - pollNow hoisted to module scope (fixes refresh calls from handlers).
…large-icon tooltips - tiers.js: each tier now carries explicit calls (=ceil(ops/2)) and sessions (divisor shrinks 150->25 per tier so session reqs increase per tier while staying reachable at ~23 calls/session) threshold fields. - server _tier_for_user(ops,calls,sessions): highest tier where ALL three thresholds are met; leaderboard folds real tools/sessions into gating. - edit modal tier picker: tiers beyond the user's current tier are greyed out and blocked (tooltip says 'Locked - reach Tier #N'); current icon stays usable. - tier tooltips: show real calls/sessions requirements (not estimates) and large 128px icons with a small-icon fallback so they never render blank.
…ersion - backend: report Hermes' real session count from state.db (cached 20s) instead of only events observed while the plugin was running - frontend: invert black simpleicons GitHub logos to white on dark profile backgrounds and in the About popup so they stay visible - leaderboard/profile/edit UI polish across the interactive office
- Add per-model correction tracking: jekyll-hyde records every activation against the audited model (hyde_core + __init__). - Pixel Office: Model Leaderboard panel + /modelboard endpoint, ranking models fewest-corrections-first (most-corrected sinks to the bottom). - Add jnorthrup as a DEV; credit jekyll-hyde in the About panel + README.
- pixel-office now SUPPORTS the jekyll-hyde audit engine (reads its data, never bundles it). Model board shows corrections + sandbagged/genuine/ uncertain verdicts + escalations + last-corrected, sorted fewest-first. - If jekyll-hyde isn't installed, panel shows a 'download Jekyll-Hyde' CTA. - modelboard rides the P2P score packets so peers' audit data merges into one shared board. - jekyll-hyde: activation records now carry model + timestamp; correction tally + /hyde corrections.
…o ops_baseline.json so scores never drop on events.jsonl trim
|
Hi Teknium 👋 — Joe (DrGekoz) here. First, quick thanks for building the original Pixel Office. This PR is my fork of it, and I wanted to give you the full picture of what I've done with it, why, and everything that's changed between your v0.2.0 and my current v0.7.1. Who's behind this forkI'm the guy who built Hermes IdleViber — the ambient gateway idle game. It's a cozy pixel-art idle/incremental game that runs in the browser: 6 themed rooms, 90 upgrades, a 500-rung tier ladder with chiptune music and particle effects, P2P mesh multiplayer with a real-time leaderboard and chat, and the gimmick where your local Hermes gateway latency becomes a VPS multiplier. It's built on Node + Firebase (Auth/Firestore) with a pure-JS canvas, and it's where the entire 500-tier icon set that this office uses comes from. I'm not a traditional dev — I describe myself as a "vibe-coder on steroids." I know HTML/CSS, I've picked up Supabase and Node and Firebase as I go, and I lean on AI-assisted development as my primary workflow. I ship fast and I refine later. When I saw your Pixel Office, I saw a natural extension of the same universe I was already building, so I forked it and wired it into my Hermes setup. I've been running it as my live office for a while now, and this PR is the accumulated result. The short version of what this fork addsYour v0.2.0 was a clean, visual office scene. Mine is a live, interactive office that ranks real people, tracks real score across sessions without ever losing it, spans a 500-tier ladder, ships real-time P2P chat, and even plugs into the Jekyll-Hyde audit engine so I can watch my models get corrected live. Everything that changed, version by versionv0.3.0 — The Interactive Office. The scene became a live office. Added a GitHub-usernames-only leaderboard (agents and subagents never pollute it — it ranks people), ranked by cumulative Ops earned per tool call, with full profile cards on click. Added the full 500-rung tier ladder using the IdleViber icon set (exponential Ops curve from tier 1 at 0 up to tier 500 at 5,000,000 Ops), your current rung highlighted and auto-scrolled into view. Added real-time P2P office chat over WebRTC data channels using the IdleViber mesh pattern, with per-sender tier badges and history that survives nobody being online. Plus a header stats strip (live Ops, calls, sessions, uptime) and themed scrollbars. v0.4.0 — Profiles, Themes & Tier Gating. Editable user profiles with bios and social/link rows, custom accent + interface themes across every surface (leaderboard, chat, canvas, agents, profile), a DEV badge and rainbow-border theme for DrGekoz, teknium1 and jnorthrup. Tier requirements now gate on calls + sessions, not just Ops — locked icon picker with an unlock modal and robust large-icon tooltips. Backend now reports Hermes' real session count from the state DB (cached 20s) instead of only what the plugin observed while running. Fixed simpleicons GitHub logos inverting to invisible on dark backgrounds. v0.5.0 — Model Leaderboard + Jekyll-Hyde. Added a Models panel that ranks models by how many times the Jekyll-Hyde completion auditor corrected them (fewest-corrections-first, so the worst model sinks to the bottom), plus a v0.6.0 — Support-based Model Leaderboard + P2P audit sync. Reworked the modelboard so pixel-office supports Jekyll-Hyde rather than bundling it — it reads Hyde's data and never fabricates anything. Richer per-model rows (corrections, sandbagged/genuine/uncertain verdicts, escalations, last-corrected time), a "download Jekyll-Hyde" CTA when it's not installed, and the modelboard now rides the P2P score packets so peers' audit data merges into one shared board across machines. v0.7.1 — Durable OPS baseline. This one's the fix you'll appreciate most from a data-integrity angle. Ops (and tools/sessions) were being re-folded live from ScreenshotsHere's the office as it looks today, straight from the Leaderboard — real GitHub usernames, tier icons, DEV badge, full profile cards: 500-tier ladder — every rung from the IdleViber icon set, exponential Ops curve, current tier highlighted: Edit profile — bio, links, accent + theme, DEV rainbow border: Profiles panel: Office chat — real-time P2P chat with per-sender tier badges: About / credits: Why fork instead of contribute upstreamHonestly? I build fast and I build a lot — a full interactive office on top of the scene, a 500-tier game ladder, P2P chat, and now audit-engine integration came together quickly and diverged heavily from the original. A PR keeps the momentum without blocking on review cycles, and you can cherry-pick whatever's useful back. The Model Leaderboard, in particular, was built by jnorthrup for his Jekyll-Hyde engine and I've kept his attribution front-and-centre — nothing here is claiming his work. If any of this is worth pulling into the main repo, say the word and I'll help merge it cleanly. Happy to keep iterating with you. — Joe (DrGekoz) · Hermes IdleViber · hermes-pixel-office-enhanced |
…lare avoidance, input validation, reconnect backoff)
- Console logs mirroring Hermes IdleViber ('📡 P2P: ...') at every P2P event:
identity set, start/stop, offering/answering, connected/closed/teardown,
score recv, chat recv/send, ice state, backoff, and a rebroadcast summary.
- Harden the mesh:
* Glare avoidance - only the lexicographically-smaller peer id initiates;
the larger side answers, so two peers coming online together can't create
duplicate connection pairs.
* Message validation - drop empty/oversized (>64k) data-channel payloads,
require a non-empty short score id and numeric counter, ignore our own
echoed entry, reject malformed JSON (try/catch everywhere).
* Reconnect backoff - exponential (3s..60s) per peer on failed connects so a
dead peer can't be hammered every 4s poll.
* Cap concurrent connections (MAX_PEERS=12) and guard bad args in
attachChannel/initiate/answer.
- Add test_p2p_smoke.js: drives the REAL module code path (poll -> initiate ->
offer -> answer -> channel open) for two peers with stubbed WebRTC/signaling
and asserts connect, score+chat flow, and the hardening (oversized/malformed/
own-echo/long-id payloads rejected). PASS verified.
- Leaderboard + Model Leaderboard rows now lay out on ONE line (rank, tier, icon, name/DEV, and stats all in a row); on narrow windows the stats group wraps to a clean second line instead of clipping or overflowing. - Fix tier-icon flicker: renderLeaderboard/renderModelBoard/renderTiers now skip the DOM rebuild when nothing changed. They were re-creating every icon element on each 1.5s poll tick and every P2P score packet, re-triggering image load/decode.
- Recreated the signaling gist as PUBLIC (08842d6294ebb262df0886f460f802d5) so any player can read presence/SDP/scores without auth. - _ensure_gist() now discovers an existing signaling gist (lists account gists, else falls back to the _WELL_KNOWN_GIST constant) instead of silently creating a private one per machine -- this was why two players each saw 0 peers. - New gists are created public. - WebRTC data channels remain the FRONT transport for realtime scores/chat. - Added gist FALLBACK for score delivery: server POST /signaling/score stores a trimmed score in the shared doc; client posts its score every 10s when it has 0 WebRTC-connected peers and merges peers' gist scores for unreachable peers. - _mutate_signaling() retries with jitter to shrink the concurrent-PATCH lost-update race on the shared gist.
…, self-healing)
Ports the battle-tested IdleViber P2P hardening into web/p2p.js and the
gist-signaling server so connections and packet delivery are reliable.
Client (web/p2p.js):
- ECDSA P-256 signed packets: every outbound message is {d,s} (payload +
SHA-256 signature); inbound messages are verified against the peer's
published public key before acceptance. Forged/corrupt packets dropped.
- Peers publish their ECDSA public key (JWK) + key id on register; peer keys
are imported BEFORE any handshake so the first packet on a channel verifies.
- ICE trickle through the gist: onicecandidate relays each candidate via a
new /signaling/ice endpoint; peers feed candidates into the connection, so
peers gathering candidates late still connect behind NAT.
- Unreliable (ordered:false, maxRetransmits:0) data channel for realtime
scores (no head-of-line blocking).
- Staleness filter: peers whose presence is older than 60s are skipped.
- Deterministic multi-phase poll (import keys -> initiate -> answer -> apply
answers -> ICE -> gist score fallback) so handshakes complete reliably.
- Kept: glare avoidance, per-peer exponential backoff, MAX_PEERS cap,
oversized/malformed/own-echo/bad-id rejection, gist score fallback.
Server (__init__.py):
- signaling_register stores the peer's ECDSA public key + key id.
- signaling_state relays a per-peer ICE candidate mailbox (ice consumed via
/signaling/clear).
- New POST /signaling/ice for candidate trickle.
Test: test_p2p_smoke.js now exercises the real signing path (webcrypto in the
sandbox, fake server relays keys) and passes 5/5 (connect + signed score/chat
flow + all hardening checks).
…d, TURN support - Peer id is now a per-browser random UUID (IdleViber pattern) instead of the GitHub username, so two players on the same account get distinct ids and can actually see + handshake each other (was the multiplayer blocker). - Server signaling_* endpoints accept a client 'peer' param (fallback to office github identity); /signaling/state reads ?peer=, POSTs read body.peer. - Durable leaderboard: server folds every peer's gist score snapshot into the /state board (_merge_gist_scores) so players appear with zero WebRTC (CGNAT). - Configurable ICE/TURN: ice_servers() reads plugin config or PIXEL_OFFICE_ICE_ SERVERS env, served at GET /signaling/ice-config; client loads it at start. - Client posts its FULL score entry to the shared gist every 10 min regardless of WebRTC connectivity (durable fallback), not the trimmed entry. - Real two-peer WebRTC sim (web/sim_p2p.js, puppeteer-core + 2 headless tabs): true data-channel RTT median ~2ms / p95 ~3ms. - test_p2p_smoke.js gains same-account distinct-peer assertion.
…rsion in About modal - Gist score persistence is now adaptive: no peer connected over WebRTC -> post the full score entry to the shared gist every 10 seconds; at least one peer connected -> post every 10 minutes (P2P channels carry realtime scores, the 10-min snapshot is the durable CGNAT/late-joiner fallback without burning the owner-write-only gist quota). Verified all 4 transitions. - About modal now shows the current version; single OFFICE_VERSION const is the source of truth, injected into #about-version on open. Set to v1.2.1.
- Gist score persistence now logs every POST to the console: 'gist post score' with ops, connected peer count, cadence (10s/600s), and a summary of the entry; plus 'gist post ok: accepted|rejected/ignored' on the response. - Bump OFFICE_VERSION + About modal to v1.2.2 to match the tag.
…yer backbone - New relay/ : Cloudflare Worker + Durable Object store (leaderboard, chat, profiles, modelboard) that every player can write to over plain HTTPS, so multiplayer works under any NAT/CGNAT and is free + non-stop forever. - Keys the board by GitHub username -> kills duplicate rows for the same account (the '3 of me / 2 of Rygel' bug). 10-min host-appointed gist backup via a Durable Object alarm (one call), optional GITHUB_TOKEN secrets. - web/p2p.js : relay client (post score, poll board+chat, merge), dedupe + stale-ghost pruning in getLeaderboard, chat merge, shared well-known relay default (zero-config install for all players). WebRTC mesh still runs for low-latency realtime; the relay is the reliable fallback. - __init__.py : /office-config endpoint + relay_url() (config/env/shared default). - web/index.html : fetch relay config and pass it into P2P. - Deployed live at https://pixel-office-relay.ads-doctor-melbourne.workers.dev - relay/test_relay.js local smoke test: 9/9 passing.
… single-source bump to v1.3.1
…n Model Leaderboard
… links externally + click-outside closes modals
…ocked tiers + chat chime resume
…e 0-0-0 model rows, README lazy-server note
Resolve whichever model the agent actually uses against the authoritative Models.dev catalog (models.dev/models.json, cached locally for a day with offline fallback) so the Model Leaderboard always shows the correct model name and never a bare 'unknown'. - Add idempotent resolve_model_name(): bare ids, provider-prefixed routing paths (deepseek/deepseek-v4-flash, openrouter/...), and canonical names all resolve to the same Models.dev display name (e.g. 'DeepSeek V4 Flash'). - _canonical_model_name() now routes non-ChatGPT models through Models.dev; ChatGPT ids keep mapping to the known roster. - _model_leaderboard() re-resolves legacy stripped-id keys to their canonical name and drops 'unknown'/'none'/'null' buckets entirely (Models.dev names every real model, so an unresolvable bucket is stale by definition). - Frontend: modelDisplayName() and the modelboard fold skip unknown/empty rows instead of rendering 'unknown'.
Latest: v1.4.3 (head
DrGekoz:main)Releases: v1.4.0 → v1.4.3 (DrGekoz/hermes-pixel-office-enhanced)