RF455-044 # RF 4.55: monsters next to you blink out, or vanish until re-sent, when the monster position tick runs late
Game: RF 4.55 (Dragonborn), server v0.2.0, Windows build (gameserver.exe SHA-256 96452fbd…d70cec7).
Client: the supported Taiwan build, RF_Online.bin SHA-256 46528aac…2bbc558 (8,430,808 bytes, 2025-07-16),
Language=Taiwan
At a hunting spot, with other players online and moving, monsters right next to you (50–800 away, alive, sometimes the one you're fighting):
- blink out: they disappear for a while, then pop back in place;
- vanish: they disappear and stay gone until the server sends them again (they move, or you move and they come back into view).
It gets worse the more players are online and moving. With one player alone it almost never shows.
Cause
The client drops any monster it hasn't had a position tick (04/0A) for in 10 s. The server's tick for an idle monster is about 4.6 s apart when the server is quiet. When it's busy, the ticks stretch well past 10 s.
Client side (RF_Online.bin)
Recv_msg_04_0A(0x5c4810, bodyu8 0, u8 kind, u16 index, u32 serial) finds the character and stampstimeGetTime()as "last heard from" (0x7cf050, log string "Real Fix").- Check 1, hide: vtable +0x70
0x7cf0b0reports the character stale whennow - last >= 10000 ms(thecmpat0x7cf0ef).0x51ccd0then treats it as out of range and stops drawing it, until the next tick. That's symptom 1. - Check 2, delete: for monsters, vtable +0x130
0x458750is true whennow - last > 10000 ms(thecmpat0x45878c).0x51ce1fthen deletes the monster from the client's list. Only a fresh 03/10, 04/0B or 04/16 brings it back. That's symptom 2.
These limits are fine as long as every monster in view gets 04/0A at least every 10 s.
Server side (gameserver.exe v0.2.0)
- The tick is counted in loop passes, not time.
(*Server).monsterKeepAlive(0x140885ac0) counts each monster's+0x1c0down and sendsprotocol.MonsterPositionTickwithbroadcastNearScopeat 0. It then resets it to 22 (0x140885b94).- It runs from
monsterWanderLoop(0x14088a300): one 200 ms ticker for every monster on the server. Each pass doesstepMonsterAI,regenMonsterHPandmonsterKeepAliveper monster. - So the tick is every 23 passes, 4.6 s, but only if every pass fits in 200 ms. Go drops ticker ticks when a pass runs long, and the keep-alive stretches with it.
- Passes run long because they wait on the world lock.
- Per monster, several times a pass, the loop calls
(*world).snapshotPlayers(0x140941ac0), throughsearchNearPlayerinstepMonsterAIand throughbroadcastNearScope. - That lock is held by
(*world).monstersInScope(0x140940360) while it walks every monster on the server, to keep the ones on one map and layer.
- Per monster, several times a pass, the loop calls
monstersInScoperuns for normal player activity:updateMonsterVisibility(0x14093a020): every player's 04/02 position report (handlePosReport), and every move start, stop and re-entry. AlsotickVisibleMonstersfor every player every ~4.6 s, andresyncScope.towerNearestMonster(0x1408fcce0): every finished guard tower, fromtowerLoopon its own 200 ms ticker.aoeCandidates,animusAreaSplash,trapHasEnemyNearanddetonateTrap: every area skill, animus splash and trap.
So the lock's load grows with (players moving + towers + area attacks) × (all monsters on the server).
Profiled with settings.cfg PROFILE_PORT:
monsterWanderLoopused about 4% CPU, but was blocked on the world lock for 19.5 s of a 20 s window.- The lock's held time was mostly
monstersInScopeunderupdateMonsterVisibilityand under the tower search.
Measured (time between 04/0A for each monster near one observing player, 150 s at a hunting spot)
| Online | Median | 90% | Max | Gaps ≥ 10 s |
|---|---|---|---|---|
| 1 player alone | 5.2 s | 7–9 s | none | |
| ~120 characters, walking (a position report every 250 ms while moving, as GoMMO's own scripted client does) | 37 s | 57 s | 68.6 s | 40 of 50 |
At the second load, over 20 minutes at one hunting spot, the client deleted 18 live monsters 50–840 away from the player, each at 10.0–10.5 s since its last tick.
Even alone, the longest gap (9 s) was 1 s short of the client's limit.
Sending 04/0A more often on the same counter doesn't help. I tried a reset of 3 instead of 22 (with the tower ticker slowed to 1 s at the same time): the gaps got worse (median 66.5 s, max 147 s), because each tick is one more broadcastNearScope through the same lock.
History
-
cybercyber