ClockworkRS Docs

UPGRADE-239 — Retarget rsmod v2 from rev 233 to rev 239 (Sailing-era)

changes are committed and pushed.

Status: PLANNED (recon complete, 2026-07-02). Work starts after the current Hunter changes are committed and pushed.

Goal: move the base game from OSRS rev 233 (cache 2293, Sept 2025) to rev 239 (cache 2615 = 239.2, June 2026) — the current live revision. This buys 1:1 parity with live, the Sailing-era cache (world entities, new areas, Interface Uplift), and puts us on the newest rsprot revision family so future bumps are incremental.

Implementing the Sailing skill itself (ships as live world entities) is explicitly out of scope — that is a separate project (see §8). This document covers getting the server to boot, pack, and run all existing content on the 239 cache/protocol.

Companion file: UPGRADE-239-rsprot-whatsnew.md — vendored snapshot of rsprot's official per-revision breaking-change log (WHATSNEW.md). That file is the authoritative packet-level checklist; this document is the plan.


1. Verified facts (all checked 2026-07-02)

ItemFinding
Sailing releaserev 235.3, 2025-11-19
Current liverev 239.2, 2026-06-30 ("The Blood Moon Rises")
Target cacheOpenRS2 id 2615https://archive.openrs2.org/caches/runescape/2615/disk.zip (180 MB, 25/25 indexes valid, HTTP 200 verified). Weekly sub-rev archives exist if we want a different 239.x.
XTEARemoved from OSRS at rev 237.1 (2026-03-25). Cache 2615 has zero encrypted groups; keys.json downloads fine but is an empty array. Our downloadCache pipeline survives unchanged; protocol-side, Rebuild*V2 packets no longer carry keys.
rsprotnet.rsprot:osrs-239-{api,shared} exist on Maven Central at 1.0.0-ALPHA-20260630. Critically, every rev family 221–239 is republished at that same version (including osrs-233). No osrs-240 yet.
Symbols.data/symbols/*.sym are externally supplied from Joshua-F/osrs-dumps. Our workspace fork (../osrs-dumps) is already on 2026-06-25-rev239. DESIGN.md §3 already uses the rev-239 dump as the interface reference.
Upstream rsmodFrozen at 233 since 2025-10-01 (no bump branch). We carry this retarget ourselves.
RSProxSupports every rev ≥ 223; actively maintained. Client-launch path unaffected in principle (smoke test in Phase U0).
Client artifactsLive 239 jav_config.ws + gamepack_9612560.jar (signed, param=25=239) archived to dist/rev239/ on 2026-07-02, while live is still 239.

2. Why a two-step migration

rsprot rebuilds all revision modules from one repo at every release. Our current pin is osrs-233-*:1.0.0-ALPHA-20250909; the newest is 1.0.0-ALPHA-20260630. Nine months of cross-rev library churn (renames, login-block field changes, OpcodeMapper reshape) landed in the 233 artifacts too. So we split:

  • Step 1 (U1): bump the version, keep the revision. osrs-233-* 20250909 → 20260630 on main. Compile errors = pure library churn. The server still runs against the current 233 cache and client, so this step is fully testable in isolation and merges to main before the risky part starts. Note: 233@20260630 keeps the OLD per-worldId info-protocol API (the InfoProtocols facade only exists at 235+), so this step does NOT touch the RspCycle rework.
  • Step 2 (U2): swap the revision at fixed version. osrs-233-*osrs-239-*, both at 20260630. Compile errors = pure revision drift, enumerated per-rev in the vendored WHATSNEW. Done on branch retarget-239 together with the cache/client bump.

Per-rev stepping (234→235→…→239) is possible (all artifacts published) but buys little: the rev-235 info refactor is the bulk either way, and WHATSNEW gives exact deltas. Reserve stepping as a fallback if U2 turns out gnarlier than expected.

3. Everything pinned to 233 (inventory, 2026-07-02)

Real pins that must change:

  1. gradle/libs.versions.tomlrsprot = "1.0.0-ALPHA-20250909", rsprot-api = net.rsprot:osrs-233-api, rsprot-shared = net.rsprot:osrs-233-shared.
  2. api/core/src/main/kotlin/org/rsmod/api/core/Build.ktMAJOR = 233, CACHE_URL/XTEA_URL → cache id 2293 → change to 2615. (Build.MAJOR is asserted against RSProtConstants.REVISION at startup — NetworkScript.kt:34.)
  3. dist/jav_config.ws — pins the 233 gamepack (param=25=233, initial_jar=gamepack_2506588.jar). Repoint to the archived dist/rev239/ artifacts.
  4. .data/symbols/*.sym — refresh from the rev-239 osrs-dumps.
  5. Cosmetic/docs only (non-blocking): scripts/client-up.sh, scripts/README.md, two code comments (WorldMapReferences.kt, RunecraftRefs.kt).

Blast radius: 71 files import net.rsprot (208 imports). 45 are in api/net. Only ~5 files carry real semantic coupling:

FileRoleExpected work
api/net/.../rsprot/RspCycle.ktper-tick Player/Npc info driverThe big port — see §4.1
api/net/.../provider/MessageConsumerProvider.kt25 incoming-message registrationsOpLoc/OpNpc/OpObj → V2 (typealiases may make this zero-touch)
api/registry/.../zone/ZoneUpdateTransformer.ktzone-prot payloadsverify V2 ctor shapes; standalone OBJ_* prots are enclosed-only since 234 (we already send enclosed)
api/player/.../ui/PlayerInterfaceExtensions.ktIfSetEventsV2 etc.verify shapes
api/net/.../NetworkFactory.ktNetworkService bootstrapnear-unchanged; optionally add getBinaryHeaderProvider() (§7)

The other ~50 files (all handlers/, most player-output builders) import exactly one rsprot type each — mechanical renames at worst.

Data that survives untouched (all name-keyed): cache-enricher TOMLs (api/cache-enricher), all spawn TOMLs (npc = 'giantrat' + absolute coords), all 82 *References() files. The ~694 find("name", hash) pins go stale but the packer runs with verifyIdentityHashes=false so they don't block; run once with =true to harvest Invalid hash: <old> | Cache hash: <new> lines and repin mechanically (see U2 step 6).

4. rsprot 233→239 API drift (confirmed by source diff)

Baseline 5a8c43e ("Revision 233", = ALPHA-20250909) diffed against master. Everything below is CONFIRMED unless marked otherwise. Details per revision: see the vendored WHATSNEW.

4.1 Info protocols — the InfoProtocols facade (rev 235 refactor)

  • NetworkService.playerInfoProtocol / .npcInfoProtocol / .worldEntityInfoProtocol are gonenetworkService.infoProtocols: InfoProtocols.
  • Login: val infos = infoProtocols.alloc(playerIndex, clientType) (one call replaces three). Logout: infoProtocols.dealloc(infos).
  • Per tick: infos.updateRootCoord(level, x, z); on login/rebuild infos.updateRootBuildAreaCenteredOnPlayer(x, z); one infoProtocols.update() for everyone; then infos.getPackets()InfoPackets:
    • rootWorldInfoPackets: activeWorld (SetActiveWorldV2), npcUpdateOrigin, playerInfo/npcInfo/worldEntityInfo as PacketResult<T> (onSuccess/onFailure/getOrThrow). NpcInfo must be safeReleaseOrThrow()d when empty or it leaks (OSRS omits empty npc-info packets).
    • WorldInfoPackets per active world entity (dormant for us — no live avatars).
    • Send order documented in rsprot README quick guide, including re-sending root activeWorld at the end (client-side pathfinding needs it).
  • All manual per-worldId management our RspCycle-era code used (allocateWorld/destroyWorld/updateRenderCoord/updateBuildArea(worldId,…)/ toPacket()) is removed — handled inside the library.
  • Root world id changed from -1 to 0 at rev 237. RspCycle hardcodes private val worldId: Int get() = -1 — must become 0 / library constant (PlayerInfo.ROOT_WORLD).

4.2 Extended info (hits RspCycle + RspNpcInfo call sites)

  • Hitmarks (rev 237): addSoakedHitMark/removeHitMark removed; addHitMark(sourceIndex, selfType, sourceType, otherType, value, delay = 0, limit) — no soaking concept.
  • Facing (rev 238): setFacePathingEntity(index) removed → explicit setFaceNpc/setFacePlayer/setFaceWorldEntity/setFaceCoordGrid/setFaceLoc/ setFaceAngle/resetFacing.
  • New (239, optional): setTransparency, setFreeze, resetPlayer, NPC body-customisation V3.

4.3 Messages

  • Incoming: OpLoc/OpNpc/OpObj*V2 (subop support; deprecated typealiases exist so this may compile as-is). ResumePCountDialogLong added (237).
  • Outgoing: RebuildLogin/Normal/Region*V2 (no XTEA; typealiases exist); camera packets all V2/V3 (V1s of MoveTo/LookAt/MoveToCycles/MoveToArc removed at 237); UpdateRebootTimer/SetMapFlag → V2; IfSetModel → V2; new group-storage packets (GroupFull, GroupVar*, 239). Our V2 zone family (LocAddChangeV2/MapProjAnimV2/IfSetEventsV2/SetActiveWorldV2) carries forward — we move WITH the V2 grain (unlike the abandoned 227 downgrade, PORTING.md).
  • Renames applied to ALL rev families in 20260624: PlayerAnimSpecific→AnimSpecific, CamLookAtEasedCoord→CamRotateToCoordinate, IfCrmViewClick→IfCrmViewOp, OculusLeave→ExitFreecam, plus IfRunScript→IfScriptTrigger. These land in U1.

4.4 Login / bootstrap

  • AbstractNetworkServiceFactory surface: unchanged except new optional getBinaryHeaderProvider() (§7).
  • LoginBlock.hasExternalAuthenticator: BooleanexternalAuthenticatorType: Int; validationClientTypedeprecatedClientType (client-type cross-check disabled since 237). Our ConnectionHandler CRC validation flow is unchanged.
  • WorldEntityAvatarFactory.alloc fully reshaped (ownerIndex-based priority, index range 1..4095) — irrelevant until we build Sailing (no live avatars).

5. Phased plan

U0 — Prep (done / near-done)

  • Archive live-239 client artifacts → dist/rev239/jav_config_live_239.ws + gamepack_9612560.jar (2026-07-02, param=25=239 confirmed).
  • Vendor rsprot WHATSNEW → UPGRADE-239-rsprot-whatsnew.md.
  • Take a few RSProx oracle captures against live 239 while it's still live (login/rebuild sequence, a hit-splat fight, a camera cutscene) — reference material for U2 semantics questions.
  • Smoke-test RSProx client launch at 239 (should Just Work).
  • Commit + push current Hunter work; main clean.

U1 — Library bump on main (small, independently shippable)

  1. libs.versions.toml: rsprot = "1.0.0-ALPHA-20260630" (artifacts stay osrs-233-*).
  2. Fix compile errors: 20260624 renames (§4.3), externalAuthenticatorType, anything else the compiler finds. NO info-protocol changes at this step.
  3. Full test suite + normal in-client session on the existing 233 cache.
  4. Commit to main. This isolates library churn from revision churn permanently.

U2 — Revision swap on branch retarget-239

  1. libs.versions.toml: osrs-233-*osrs-239-*.
  2. Build.kt: MAJOR = 239; cache URLs → id 2615.
  3. ./gradlew cleanInstall (or downloadCache + packCache) — pulls the 239 cache; empty keys.json expected.
  4. Refresh .data/symbols/*.sym from the rev-239 osrs-dumps (open item: locate the dump→sym transform used originally — see §9).
  5. Port compile errors, in this order: a. RspCycle → InfoProtocols facade (§4.1), root world 0, PacketResult flow with safeReleaseOrThrow on empty NpcInfo, README-documented send order. b. NetworkScript/RspClient alloc/dealloc → single Infos handle. c. Extended-info call sites (§4.2): hitmark + facing APIs in RspCycle, RspNpcInfo, and whatever engine paths feed them (NpcPostTickProcess.setFaceAngle etc.). d. Sweep the 1-import files (mostly typealias-covered).
  6. Repack with verifyIdentityHashes=true once; script the harvest of Invalid hash: X | Cache hash: Y | Reference: Z lines into repins of the ~694 find("name", hash) sites (38 files).
  7. dist/: new jav_config from dist/rev239/ template (faithful-copy rule from the RSProx jav_config gotcha still applies), self-host gamepack_9612560.jar, param=25=239.

U3 — Validation

  • GameTestState suites (expect near-green: content is name-keyed; watch the known gotchas — SAME_THREAD inv-transaction tests, daemon hygiene).
  • Diff old vs new .sym name sets: names that vanished = content that needs attention before it fails at runtime.
  • In-client sweep (user), priority order:
    1. Login, rebuild, walking across region boundaries (rebuild V2 / no-XTEA path).
    2. Combat: hit splats + head bars (reshaped hitmark API), facing (reshaped facing API), all Hunter trap flows (heavy facing/anim users).
    3. Interface Uplift risk sweep: settings, bank, leagues tier/relic UI, skill guides, world map — the interfaces Jagex reworked between 233.3 and 235.
    4. One pass over each shipped skill loop (WC/Mining/Fishing/RC/Hunter).
  • RSProx compare: capture our server's session and diff message flow against the U0 live-239 oracle captures for login/rebuild/combat.

U4 — Free win: native .bin capture

Implement getBinaryHeaderProvider() in NetworkFactory — rsprot 239 natively exports RSProx-compatible .bin captures of OUR server's sessions (Session.getBinaryBlobOrNull(), net.rsprot.protocol.api.binary.*). This makes the "compare our emissions vs live" oracle workflow first-class. Small, do it while the bootstrap file is warm.

U5 — Out of scope here

  • Sailing gameplay (world-entity ships): WorldEntityAvatarFactory, per-world InfoPackets, RebuildWorldEntityV4 — a Tier-C-sized project with no upstream reference implementation.
  • Leagues if3 interface forward-port if any Leagues UI is missing from the 239 cache (DESIGN.md §7 — verify during U3 sweep).
  • Ongoing cadence: when live moves to 240+, the same U1/U2 recipe applies but scoped to one revision of WHATSNEW deltas.

6. Working-copy strategy

Second full clone, not a worktree (established project rule: worktrees miss the gitignored .data vanilla cache and invite gradle daemon contention). For the upgrade this is doubly right: the fresh clone's gradlew install downloads the NEW 239 cache into its own .data, leaving the main checkout's 233 cache untouched — both servers stay runnable side by side for A/B comparison.

Rules for the upgrade clone:

  • Branch retarget-239 pushed to origin; clone tracks it.
  • Never run gradle in both checkouts simultaneously, and keep Cursor closed during CLI builds (daemon/build-cache corruption — known failure mode).
  • U1 happens on main in the MAIN checkout before the clone branches, so the clone starts from the post-U1 state.

7. RSProx's role (answering "would live dumps help?")

  • Not needed for cache/keys/content — the cache IS the content and OpenRS2 covers it completely (and XTEA no longer exists).
  • Essential as the semantics oracle: U0 captures of live 239 answer any question the API diff leaves open (exact rebuild sequencing, hitmark field conventions, camera V3 behavior) — same workflow that cracked the Hunter net-trap overlay.
  • After U2/U4: capture-vs-capture diffing between our server and live becomes nearly symmetrical thanks to rsprot's native .bin export.

8. Effort estimate

  • U1: hours (rename-level churn, compiler-driven).
  • U2: the real work — RspCycle/InfoProtocols port is days of careful work; the rest is compiler-driven + scripted repinning. 1–2 focused weeks total including U3 validation, consistent with the original estimate.
  • Biggest schedule risk: Interface Uplift fallout in U3 (unknown until we look), and any .sym-name drift that breaks existing References.

9. Open research items

  1. .sym production: no in-repo generator writes .data/symbols/*.sym. Locate or rebuild the transform from the osrs-dumps repo layout to .sym format (idname; components as iface:child). One-time scriptable.
  2. RSProx launch at 239: smoke test (U0).
  3. Leagues UI presence in cache 2615: check during U3; determines whether DESIGN.md §7's if3 forward-port is needed at all.
  4. MapProjAnimV2/IfSetEventsV2 ctor shapes at 239: expected unchanged (PORTING.md table was about the 227 direction); confirm at compile time.
  5. Gamepack self-hosting check RESOLVED (2026-07-02): the gamepack never needs hosting — RSProx's native/RuneLite clients don't fetch initial_jar (vanilla-launcher-only field; the 233 flow runs today with gamepack_2506588.jar hosted nowhere). dist/rev239/gamepack_9612560.jar (320 KB, JAGEXLTD-signed) is archival insurance only.

10. References

On this page