29 Commits
Author SHA1 Message Date
max bittkerandClaude Opus 5.5 76391328ec fix(engine): allow staff ::kick and ::ban regardless of NODE_PRODUCTION
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:30:11 -04:00
max bittker fb26497163 Harden Miscellania transfers and add coin dupe regression coverage
Follow up kadeski’s fix in PR #73 with current-balance and inventory-capacity checks. Keep the original report and authorship credited in the vendor patch checklist. Exercise both reported exploit inputs in compiled-script tests, and build engine content for those tests in CI.
2026-10-11 19:37:42 -04:00
max bittker 2012ab67f6 Reduce tick stalls and move leaderboard refreshes off the game thread 2026-10-11 02:21:47 -04:00
max bittker e504d6adb3 fix(mapview): preserve map proportions at minimum zoom 2026-10-11 01:17:02 -04:00
max bittkerandClaude Opus 5.5 68ff9ebf82 engine: add an optional live world feed for spectator viewers
A read-only /worldfeed websocket that streams the players and npcs in a
subscribed tile area every tick (positions, steps, anims, spotanims,
facing, hitmarks, chat, appearance), plus an optional online roster. It
drives the rs-world 3D viewer (github.com/MaxBittker/rs-world).

Off unless WORLD_FEED=true. WORLD_FEED_TOKEN requires ?token=, and
WORLD_FEED_MAX_CLIENTS caps connections (default 32). While no viewer is
subscribed the tick hook returns immediately. Hidden-visibility players
are never sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:10:56 -04:00
max bittkerandClaude Opus 5.5 33aa0a1424 fix(server): preserve socket ownership through reconnects
World.processLogins() swapped in the replacement socket without pointing it
back at the active player, and the WS/TCP close handlers cleared a player's
socket without checking that the closing socket still owned it. A delayed
close of the old socket could detach the replacement.

Same change as 87dc1bf18 (deployed 2026-10-06), applied to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:00:49 -04:00
max bittker 0c1d35abb1 feat(hiscores): rank bank wealth by item name substring 2026-10-08 12:28:19 -04:00
max bittkerandClaude Opus 5.5 2fdb019b3e fix(server): cap game sessions at 100 per IP keyed on Fly-Client-IP; skip repeat state frames
MAX_PLAYERS_PER_IP is now 100 on Fly (code default stays 200 for local load
tests). getIp only trusts Fly-Client-IP behind Fly and the socket peer
elsewhere; the old cf-connecting-ip / X-Forwarded-For lookup took the first,
client-supplied entry, so a swarm could claim a new IP per connection and walk
past the cap.

The gateway no longer fans out a state frame that repeats the last one sent
to subscribers apart from its publication revision (same tick, same content:
a post-action publish where the action changed nothing visible yet).
Subscribers get the next tick's frame instead; any difference still goes out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:07:37 -04:00
max bittker e5282efdd9 fix: preserve player assets across Grand Exchange recovery 2026-09-07 23:47:29 -04:00
max bittker 69d5b709c8 feat: ship Grand Exchange with durable Fly market storage 2026-09-07 23:28:51 -04:00
max bittkerandClaude Opus 5 28acd81cb0 feat(sdk): surface server op rejections; reachability-filtered find*
Two silent failure modes cost bots a 10s timeout each and were
indistinguishable from "the action ran and did nothing".

Op rejections. When the server refuses an op (stunned, mid-action, target
out of view, modal open underneath) every Op*Handler answers with
UNSET_MAP_FLAG and nothing else - no message, no error. Client.ts and the
lite client now both count those packets, StateCollector turns unsets
that arrive while the player is standing still into
state.opFeedback.opRejectedCount, and ActionHelpers exposes the
snapshot/compare pair. interactNpc and pickpocketNpc use it to bail with
reason 'rejected' instead of waiting out the timeout. opFeedback is
optional throughout - an older connected client just goes quiet, and the
helpers report "no rejection".

Pickpocket acking. "You attempt to pick..." is printed by ~pick_pocket
before its first p_delay, so it is the earliest evidence the op actually
ran - an attempt earlier than xp or a stun. Without it, a lost op looked
identical to a slow one. Attempts that neither ack nor reject within 3
idle ticks (measured from the last step, since the client walks before
writing OPNPC) now return reason 'not_started'. Also fixes "you fail to
pick" being read as a timeout rather than a stun, and checks xp before
messages so a successful pick is never misreported.

find* reachability. All four find* lookups take {reachable: true}, which
returns null rather than a target the client cannot route to - the case
where the client silently writes no packet at all. Default behaviour is
unchanged: an unreachable match still wins if it is the only one.

PATCHES.md gains entries for the lite client, the ClientStream.isClosed
getter, the LoopCycle split, and the mapFlagUnsetCount counter, which is
now duplicated in both clients and would go quiet without a loud failure
if a sync reverted it. bun run test covers src/lite too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 12:55:45 -04:00
max bittkerandClaude Opus 5 5e9a3775e7 fix(client): keep the main loop running in a background tab
A bot left in an unfocused tab went stale: ticks stopped advancing, state
aged into the minutes, and scripts reported "Stuck at (x, z)" while the
character stood still. Reconnecting was the only cure.

GameShell.run() is a sleep() loop and sleep() was setTimeout, which Chrome
clamps to one wake per second in a hidden page — then to roughly one per
minute once intensive throttling kicks in after about five minutes. Packets
still arrive over the WebSocket, but they are only drained inside mainloop(),
so the client falls arbitrarily far behind.

Timers inside a dedicated worker are exempt, so hand the clock to one: the
worker holds each delay and posts back when it elapses. Measured in Chrome
146 with the tab hidden, a page-timer loop managed 1.0 wake/s where the
worker-driven one held 66.4, against a foreground rate of 66.0.

Falls back to setTimeout when a worker cannot be created — no Worker or Blob
constructor, a CSP forbidding blob: workers, or the worker erroring at
runtime — so the worst case is today's behaviour rather than a hang.

Note this does not survive Chrome freezing or discarding a long-idle tab;
long-running scripts should still check sdk.getStateAge().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:16:23 -04:00
max bittkerandClaude Opus 5 5d8b3e30fc test(sdk): migrate logic tests to bun:test and drop stale manual scripts
Converts the hand-rolled test runners in chat-history and
click-dialog-by-text to bun:test describe/test, and points `check` at
`bun test sdk/test server/webclient/src/bot` so the previously-orphaned
webclient bot tests and sdk logic tests all run in CI (71 tests).

Removes manual integration scripts that required a live bot and were
superseded by the logic tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 19:00:13 -04:00
max bittkerandClaude Opus 5 9958f2b074 fix(sdk): correct interface-option clicks and make quantity actions honest
Audit of codex/porcelain-action-reliability, landing the changes that fix
real defects and leaving the speculative parts behind.

Interface options are published with a 1-based `index` label, but
`sendClickInterfaceOption` takes a 0-based array position. `fletchLogs`
and `craftLeather` matched an option by text and then fed its `index`
straight into that method, so both crafted the product *after* the one
requested — and matching the first option skipped it entirely. Adds
`sdk.clickInterfaceOption(selector)`, which resolves by object or visible
text and dispatches the option's `componentId`, and uses it at both call
sites. `sendClickInterfaceOption` keeps its behaviour with a renamed
parameter and a doc comment naming the trap.

`buyFromShop`/`sellToShop` decomposed the requested amount into an array
of 10/5/1 steps before sending anything, so `buyFromShop(item, 1e9)`
allocated a hundred-million-element array. Quantities are now validated
up front and the packets are streamed under a packet cap and deadline;
`sellAllToShop`'s unbounded `while (true)` is bounded the same way.

`sellToShop` reused the shop slot captured before the loop. Selling a
batch of non-stackables removes that slot, so later packets sold whatever
had slid into it. The slot is re-resolved by item id before each packet.

Shop and bank quantity actions returned `success: true` for short fills,
so a caller that asked for 28 proceeded believing it had 28. They now
report `requestedAmount`, the amount that actually moved, and
`partial: true`, and reserve `success` for an exact fill.

`chopTree` returned success when the tree merely despawned — which
happens when another player fells it — and then reported logs already in
the inventory as the ones just chopped. Success now requires the log
count to rise; the despawn still ends the wait, as a failure.

`withdrawItem` identified the withdrawn item by diffing inventory slots,
which names whatever landed in a changed slot rather than what was
withdrawn. It now tracks the bank item's id.

Also regenerates sdk/API.md, which was already stale on main (`bun run
check` fails there at docs:api:check), and adds unit coverage for option
resolution and quantity handling to test:focused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 18:16:14 -04:00
Max BittkerandClaude Opus 5 e5fb1e9d07 Make the agent API self-describing and add regression CI (#43)
* Improve agent API docs and validation

* Drop the wiki search tool

Removes wiki/search.ts and its test. The index only covered names, aliases,
summaries and Sources lines, so it had worse recall than grep on body text
(e.g. "cow hide" missed skills/crafting.md), and normalize() stripped
underscores so map-square queries matched every neighbouring square
(m50_50 returned m50_53 NPCs). The computed mapSquareBounds were only
indexed as text, so coordinate lookups — the one thing grep can't do —
never worked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Regenerate the wiki from server/content

Adds the coordinate-samples column to NPC location tables and picks up
content drift the checked-in pages had missed (807 NPC, 117 shop, 1573 item
pages; 335 of them new). Examples: Aubury now stocks 1000 chaos/death runes
rather than 500, and shop rows point at "Vial of water"/"Bolts" instead of
the stale "Vial"/"Bolt" names.

Also stops all three generators from crashing on their final step. Each one
read wiki/README.md unconditionally, but that file has never existed, so the
index update threw ENOENT on every run. Skip it when absent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 17:52:51 -04:00
max bittkerandClaude 3bdb46329b Track sdk/collision-data.json again; add refresh script
716a9375f removed collision-data.json from tracking and gitignored it as
"regenerable", but sdk/pathfinding.ts imports it statically -- so a fresh
clone hard-failed on import with no way to know why. Nothing fetched it.

Re-track it. The anti-bloat rationale was already sunk cost: that commit
never rewrote history, so all 12 prior collision-data blobs remain reachable
and every clone already pays for them. One more version costs ~3.4MB packed
(23MB raw, but it's highly repetitive), against a 259MB .git.

Add sdk/fetch-collision-data.ts to regenerate it from a running server,
replacing the bare curl in DEVELOPERS.md -- it buffers before writing so an
interrupted download can't leave a truncated file that breaks the import,
and needs --force to clobber. The committed copy is a fresh export: 123,648
zones (= 483 mapsquares, full coverage), 1,068,791 tiles, 2,603 doors.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 09:27:47 -04:00
max bittkerandClaude Fable 5 faebf16f70 MCP: fold sub-package into root and serve live API docs
- @modelcontextprotocol/sdk moves to the root package.json; mcp/ no longer
  carries its own package.json/bun.lock/node_modules (23MB install layer
  and second lockfile gone). Smoke-tested: initialize handshake +
  resources/read both work off root node_modules.
- The two hand-maintained MCP resources (mcp/api/{bot,sdk}.ts) had
  drifted stale (missing the Jul 6 chat APIs) — deleted, and the resource
  now points at the auto-generated sdk/API.md.
- Fixed connect_bot references (tool doesn't exist; bots auto-connect),
  the pre-rename server path in the manual-setup snippet, and install
  instructions in mcp/README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 19:12:15 -04:00
max bittkerandClaude Fable 5 377652f2aa Remove dead files and dependencies; move README art out of vendored content
Dead code/files (zero references, verified):
- server/engine/RuneScriptCompiler.jar (12.7MB) — replaced by
  @lostcityrs/runescript TS package in the 274 upgrade
- server/engine/package-lock.json — bun-only repo; also gitignored so
  vendor syncs don't reintroduce it
- server/engine/.github/ — vendored CI, can never run from a nested path
- server/engine/public/maped/ + view/maped.ejs — unreachable map editor
- server/gateway/{rsbot,run-recorder.ts,bunfig.toml} + bin/cli/login
  package.json entries — all point at files/dirs that no longer exist
- server/webclient/.gitmodules + empty emsdk/tinymidipcm gitlinks —
  submodules never initialized; build uses committed prebuilt wasm
- sdk/package.json — publish manifest for a never-published package
- sdk/test/zanaris-pathfinding.ts — duplicate of zanaris-navigation.ts;
  its unique waypoint folded into the survivor

Dead dependencies (lockfiles regenerated):
- gateway: puppeteer + @anthropic-ai/sdk (~54MB + Chromium download per
  Docker build, zero gateway imports); puppeteer now a root devDependency
  since sdk/test/utils/browser.ts is its real consumer
- engine: @2004scape/rsmod-pathfinder (replaced by in-engine routefinder;
  server/vendor copy is the SDK's), tsx, redundant @jimp direct pins

Also: README art (promo.gif 31MB + svgs) moved from server/content/title/
to docs/media/ so it stops shipping in every Docker image; stale
gateway/agent-state gitignore entry dropped; DEVELOPERS.md stale rows fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 19:10:41 -04:00
max bittkerandClaude Fable 5 716a9375f9 Repo cleanup: remove ~3.3GB of cruft, dead code, and stale docs
Disk/data:
- Delete scripts/ entirely: 3.2GB of gitignored Jan-Feb run telemetry
  plus the legacy per-skill script/lab_log layout superseded by bots/
  (tracked files remain recoverable from git history)
- Prune bots/ to the 10 most-used bots (claude46, Codex7, max, codexpl8,
  runepl8, maxbittker, codex53, aaravA, codex_53, multibot) + _template;
  all removed dirs archived to ~/rs-sdk-cleanup-archive-2026-07-13.tgz
  along with stream/ (held live Twitch/bot/Anthropic credentials)
- Remove orphaned sdk/node_modules + sdk/bun.lock (pathfinder is
  vendored in server/vendor; sdk has no deps of its own)

Git hygiene:
- Untrack sdk/collision-data.json (22MB blob per regen; kept on disk,
  regen curl documented in DEVELOPERS.md and .gitignore)
- gitignore backups/, .obsidian/, and engine public/client/*.js (the
  gap that let stale client.js shadow fresh webclient builds return)
- Delete the untracked stale public/client/client.js shadow copy

Dead code/config:
- Drop unused deps: tmi.js, @types/tmi.js, ts-morph
- Drop dead tsconfig globs (stream/, bot_arcs/)
- Remove unused nix toolchain (flake.nix, flake.lock, .envrc)

Docs:
- GEMINI.md/agents.md were byte-identical 4-month-stale copies of
  claude.md; replace with symlinks so all tools read one file
- DEVELOPERS.md: remove abolished copy-client.js-to-engine step and
  nonexistent agent//javaclient sections; document collision regen
- sdk/README.md: fix reference to nonexistent scripts/example-remote.ts
- Remove stale sdk/SDK_DESIGN.md and misleading sdk/test/test_status.md

Also commit the forgotten sdk/test/click-dialog-by-text.test.ts (tests
a shipped API, was never git-added).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 11:48:15 -04:00
max bittkerandClaude Opus 4.6 952f516e44 Add in-process TypeScript type checking to daemon exec
Uses ts-morph to type-check exec code against the real BotActions/BotSDK
types before execution. Type errors are surfaced as warnings in the output
but don't block execution. The ts-morph Project is initialized once at
daemon startup and reused across exec calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:12:11 -05:00
max bittker 95d722d16a idk 2026-03-05 15:33:35 -05:00
max bittkerandClaude Opus 4.6 ae7ad86640 use open package for cross-platform browser launching
Replaces hardcoded xdg-open/open/start with the `open` npm package,
which handles macOS, Windows, Linux, and WSL automatically. Falls back
to printing the URL if no browser can be opened.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:18:09 -05:00
max bittkerandClaude Sonnet 4.6 d2243665ee add twitch stream system and benchmark updates
Adds stream/ - 24/7 Twitch stream orchestrator with planner/executor
Claude agents, ffmpeg capture, Chromium game client, and exe.dev
deployment scripts (setup-exe.sh, start-exe.sh).

Also adds combat/gold benchmark tooling and fresh_run utility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 17:24:00 -05:00
max bittker 6b13c13e46 improve 2026-01-29 23:37:01 -05:00
max bittkerandClaude Opus 4.5 638793a506 remove puppeteer/playwright deps, rsmod-pathfinder now vendored
SDK no longer needs external deps beyond @inquirer/prompts.
Zero-install experience via bun auto-install.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:47:43 -05:00
max bittker 9c16e927ed wasm 2026-01-28 19:27:26 -05:00
max bittker 877a48905e bundle pathfinding into sdk 2026-01-28 15:44:47 -05:00
max bittker 31e7bc65fe fixes 2026-01-21 14:22:12 -05:00
Pazaz 590c168606 feat: Platform-agnostic start scripts 2025-06-05 14:20:38 -04:00