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)
| Item | Finding |
|---|---|
| Sailing release | rev 235.3, 2025-11-19 |
| Current live | rev 239.2, 2026-06-30 ("The Blood Moon Rises") |
| Target cache | OpenRS2 id 2615 → https://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. |
| XTEA | Removed 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. |
| rsprot | net.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 rsmod | Frozen at 233 since 2025-10-01 (no bump branch). We carry this retarget ourselves. |
| RSProx | Supports every rev ≥ 223; actively maintained. Client-launch path unaffected in principle (smoke test in Phase U0). |
| Client artifacts | Live 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 onmain. 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 tomainbefore the risky part starts. Note: 233@20260630 keeps the OLD per-worldId info-protocol API (theInfoProtocolsfacade 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 branchretarget-239together 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:
gradle/libs.versions.toml—rsprot = "1.0.0-ALPHA-20250909",rsprot-api = net.rsprot:osrs-233-api,rsprot-shared = net.rsprot:osrs-233-shared.api/core/src/main/kotlin/org/rsmod/api/core/Build.kt—MAJOR = 233,CACHE_URL/XTEA_URL→ cache id2293→ change to2615. (Build.MAJORis asserted againstRSProtConstants.REVISIONat startup —NetworkScript.kt:34.)dist/jav_config.ws— pins the 233 gamepack (param=25=233,initial_jar=gamepack_2506588.jar). Repoint to the archiveddist/rev239/artifacts..data/symbols/*.sym— refresh from the rev-239 osrs-dumps.- 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:
| File | Role | Expected work |
|---|---|---|
api/net/.../rsprot/RspCycle.kt | per-tick Player/Npc info driver | The big port — see §4.1 |
api/net/.../provider/MessageConsumerProvider.kt | 25 incoming-message registrations | OpLoc/OpNpc/OpObj → V2 (typealiases may make this zero-touch) |
api/registry/.../zone/ZoneUpdateTransformer.kt | zone-prot payloads | verify V2 ctor shapes; standalone OBJ_* prots are enclosed-only since 234 (we already send enclosed) |
api/player/.../ui/PlayerInterfaceExtensions.kt | IfSetEventsV2 etc. | verify shapes |
api/net/.../NetworkFactory.kt | NetworkService bootstrap | near-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/.worldEntityInfoProtocolare gone →networkService.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/rebuildinfos.updateRootBuildAreaCenteredOnPlayer(x, z); oneinfoProtocols.update()for everyone; theninfos.getPackets()→InfoPackets:rootWorldInfoPackets:activeWorld(SetActiveWorldV2),npcUpdateOrigin,playerInfo/npcInfo/worldEntityInfoasPacketResult<T>(onSuccess/onFailure/getOrThrow). NpcInfo must besafeReleaseOrThrow()d when empty or it leaks (OSRS omits empty npc-info packets).WorldInfoPacketsper 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.
RspCyclehardcodesprivate 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/removeHitMarkremoved;addHitMark(sourceIndex, selfType, sourceType, otherType, value, delay = 0, limit)— no soaking concept. - Facing (rev 238):
setFacePathingEntity(index)removed → explicitsetFaceNpc/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).ResumePCountDialogLongadded (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, plusIfRunScript→IfScriptTrigger. These land in U1.
4.4 Login / bootstrap
AbstractNetworkServiceFactorysurface: unchanged except new optionalgetBinaryHeaderProvider()(§7).LoginBlock.hasExternalAuthenticator: Boolean→externalAuthenticatorType: Int;validationClientType→deprecatedClientType(client-type cross-check disabled since 237). OurConnectionHandlerCRC validation flow is unchanged.WorldEntityAvatarFactory.allocfully 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=239confirmed). - 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;
mainclean.
U1 — Library bump on main (small, independently shippable)
libs.versions.toml:rsprot = "1.0.0-ALPHA-20260630"(artifacts stayosrs-233-*).- Fix compile errors: 20260624 renames (§4.3),
externalAuthenticatorType, anything else the compiler finds. NO info-protocol changes at this step. - Full test suite + normal in-client session on the existing 233 cache.
- Commit to
main. This isolates library churn from revision churn permanently.
U2 — Revision swap on branch retarget-239
libs.versions.toml:osrs-233-*→osrs-239-*.Build.kt:MAJOR = 239; cache URLs → id2615../gradlew cleanInstall(ordownloadCache+packCache) — pulls the 239 cache; empty keys.json expected.- Refresh
.data/symbols/*.symfrom the rev-239 osrs-dumps (open item: locate the dump→sym transform used originally — see §9). - Port compile errors, in this order:
a.
RspCycle→ InfoProtocols facade (§4.1), root world 0, PacketResult flow withsafeReleaseOrThrowon empty NpcInfo, README-documented send order. b.NetworkScript/RspClientalloc/dealloc → singleInfoshandle. c. Extended-info call sites (§4.2): hitmark + facing APIs inRspCycle,RspNpcInfo, and whatever engine paths feed them (NpcPostTickProcess.setFaceAngleetc.). d. Sweep the 1-import files (mostly typealias-covered). - Repack with
verifyIdentityHashes=trueonce; script the harvest ofInvalid hash: X | Cache hash: Y | Reference: Zlines into repins of the ~694find("name", hash)sites (38 files). dist/: new jav_config fromdist/rev239/template (faithful-copy rule from the RSProx jav_config gotcha still applies), self-hostgamepack_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
.symname sets: names that vanished = content that needs attention before it fails at runtime. - In-client sweep (user), priority order:
- Login, rebuild, walking across region boundaries (rebuild V2 / no-XTEA path).
- Combat: hit splats + head bars (reshaped hitmark API), facing (reshaped facing API), all Hunter trap flows (heavy facing/anim users).
- Interface Uplift risk sweep: settings, bank, leagues tier/relic UI, skill guides, world map — the interfaces Jagex reworked between 233.3 and 235.
- 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-239pushed 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
mainin 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
.symproduction: no in-repo generator writes.data/symbols/*.sym. Locate or rebuild the transform from the osrs-dumps repo layout to.symformat (idname; components asiface:child). One-time scriptable.- RSProx launch at 239: smoke test (U0).
- Leagues UI presence in cache 2615: check during U3; determines whether DESIGN.md §7's if3 forward-port is needed at all.
MapProjAnimV2/IfSetEventsV2ctor shapes at 239: expected unchanged (PORTING.md table was about the 227 direction); confirm at compile time.Gamepack self-hosting checkRESOLVED (2026-07-02): the gamepack never needs hosting — RSProx's native/RuneLite clients don't fetchinitial_jar(vanilla-launcher-only field; the 233 flow runs today withgamepack_2506588.jarhosted nowhere).dist/rev239/gamepack_9612560.jar(320 KB, JAGEXLTD-signed) is archival insurance only.
10. References
- rsprot WHATSNEW (vendored):
UPGRADE-239-rsprot-whatsnew.md - rsprot repo: https://github.com/blurite/rsprot (README quick guide = rev-239 flow; world entities design: issue #13)
- rsprot Maven: https://repo1.maven.org/maven2/net/rsprot/ (all rev families at
1.0.0-ALPHA-20260630) - OpenRS2 cache 2615: https://archive.openrs2.org/caches/runescape/2615
- OSRS cache timeline: https://oldschool.runescape.wiki/w/RuneScape:Cache/2025 and /2026
- Our prior art:
PORTING.md(233→227 retarget, abandoned; drift-table method),DESIGN.md§3 (why 233) and §7 (this migration, anticipated)
WIKI-MCP — Local OSRS Wiki mirror + MCP service (feasibility research)
All claims below were verified live against the wiki's endpoints on this date (three parallel research passes).
Cross-Skill Intersections — Implementer's Map
OSRS skills are a directed resource graph. Gathering skills pull raw materials out of the world;