Currently setting up my 634 server and getting this weird Y offset bug where the player location and the camera aren't synced. This server was originally built for 731, but after looking at MGI's release I don't see any difference in the player updating protocol (other than masks of course).

Any suggestions as to what's causing this problem / what the solution could be? Here's some code:

Code:
package com.rs.game.player.masks;


import com.common.io.OutputStream;
import com.common.utils.Logger;
import com.rs.GameSettings;
import com.rs.game.World;
import com.rs.game.map.tiles.RegionHash;
import com.rs.game.map.tiles.WalkStep;
import com.rs.game.player.Player;
import com.rs.json.PacketDb;
import lombok.Getter;

public class LocalPlayerUpdate {

	/**
	 * The maximum amount of local players being added per tick. This is to
	 * decrease time it takes to load crowded places (such as home).
	 */
	private static final int MAX_PLAYER_ADD = 10;
	/**
	 * Hardcoded, cannot be changed
	 */
	private static final int PLAYERS_LIMIT = 2048;

	private Player player;

	/**
	 * both
	 */
	private byte[] skipFlags;

	/**
	 * local
	 */
	private transient Player[] localPlayers;
	private int[] localPlayersIndexes;
	private int localPlayersIndexesCount;

	@Getter
	private int renderDataLength; // the total length of the render blocks updates

	/**
	 * outside
	 */
	private int[] outPlayersIndexes;
	private int outPlayersIndexesCount;
	private RegionHash[] regionHashes;
	private int localAddedPlayers; // The amount of local players added this tick.

	public LocalPlayerUpdate(Player player) {
		this.player = player;
		skipFlags = new byte[PLAYERS_LIMIT];
		outPlayersIndexes = new int[PLAYERS_LIMIT];
		localPlayers = new Player[PLAYERS_LIMIT];
		localPlayersIndexes = new int[PLAYERS_LIMIT];
		regionHashes = new RegionHash[PLAYERS_LIMIT];
	}

	/**
	 * Initializes only player positions
     */
	public void init(OutputStream stream) {
		stream.initBitAccess();
		stream.writeBits(30, player.getTileHash());
		localPlayers[player.getIndex()] = player;
		localPlayersIndexes[localPlayersIndexesCount++] = player.getIndex();

		for (int playerIndex = 1; playerIndex < PLAYERS_LIMIT; playerIndex++) {
			if (playerIndex == player.getIndex()) {
				continue;
			}

			Player player = World.getPlayer(playerIndex);
			regionHashes[playerIndex] = (player == null) ? null : player.getRegionHash();
			int regionHash = regionHashes[playerIndex] == null ? 0 : regionHashes[playerIndex].getRegionHash();
			stream.writeBits(18, regionHash);
			outPlayersIndexes[outPlayersIndexesCount++] = playerIndex;
		}
		stream.finishBitAccess();
	}

	public OutputStream encode() {
		OutputStream stream = new OutputStream();
		OutputStream updateBlockData = new OutputStream();

		stream.writePacketVarShort(player.getIsaacKeyPair(), PacketDb.getGameOut("player_update"));

		processLocalPlayers(stream, updateBlockData, NsnBlock.NSN_0);
		processLocalPlayers(stream, updateBlockData, NsnBlock.NSN_1);
		processOutsidePlayers(stream, updateBlockData, NsnBlock.NSN_2);
		processOutsidePlayers(stream, updateBlockData, NsnBlock.NSN_3);

		stream.writeBytes(updateBlockData.getBuffer(), 0, updateBlockData.getOffset());
		stream.endPacketVarShort();

		renderDataLength = 0;
		localPlayersIndexesCount = 0;
		outPlayersIndexesCount = 0;
		for (int playerIndex = 1; playerIndex < PLAYERS_LIMIT; playerIndex++) {
			skipFlags[playerIndex] >>= 1;
			Player player = localPlayers[playerIndex];
			if (player == null) {
				outPlayersIndexes[outPlayersIndexesCount++] = playerIndex;
			} else {
				localPlayersIndexes[localPlayersIndexesCount++] = playerIndex;
			}
		}
		Logger.log("processing " + localPlayersIndexesCount + " local players");
		return stream;
	}

	/**
	 *
	 * @param stream
	 * The main stream that will be used to update the player's basic information
	 *
	 * @param updateBlockData
	 * Used for player masks only, appended at the end to the main stream
	 *
	 * @param nsn
	 * Current nsn block being processed
	 * There's 2 nsn blocks (nsn2 + nsn3) used to save bandwidth by regrouping inactive players in one block,
	 * making skips more efficient
     */
	private void processOutsidePlayers(OutputStream stream, OutputStream updateBlockData, NsnBlock nsn) {
		stream.initBitAccess();

		int skip = 0;
		localAddedPlayers = 0;
		for (int i = 0; i < outPlayersIndexesCount; i++) {
			int playerIndex = outPlayersIndexes[i];

			if (shouldSkipPlayer(nsn, playerIndex)) {
				continue;
			}

			if (skip > 0) {
				skip--;
				skipPlayer(playerIndex);
				continue;
			}

			Player p = World.getPlayer(playerIndex);
			if (needsAdd(p)) {
				// adds a new local player to the local players

				stream.writeBits(1, 1); // is not skipped
				stream.writeBits(2, 0); // request add to local players
				writePosHash(stream, p); // writes and updates the region hash if necessary

				stream.writeBits(6, p.getXInRegion());
				stream.writeBits(6, p.getYInRegion());

				appendUpdateBlock(p, updateBlockData, true);

				stream.writeBits(1, 1); // needs update
				addLocalPlayer(p);
			} else {
				// the player is either pos updated, or marked as skipped
				writePosHash(stream, p);
				if (p == null) {
					for (int j = i + 1; j < outPlayersIndexesCount; j++) {
						int p2Index = outPlayersIndexes[j];

						if (shouldSkipPlayer(nsn, p2Index)) {
							continue;
						}

						Player p2 = World.getPlayer(p2Index);
						if (needsAdd(p2) || needsPositionUpdate(p2)) {
							break;
						}

						skip++;
					}
					skipPlayers(stream, skip);
					skipPlayer(playerIndex);
				}
			}
		}
		stream.finishBitAccess();
	}

	/**
	 *
	 * @param stream
	 * The main stream that will be used to update the player's basic information
	 *
	 * @param updateBlockData
	 * Used for player masks only, appended at the end to the main stream
	 *
	 * @param nsn
	 * Current nsn block being processed
	 * There's 2 nsn blocks (nsn0 + nsn1) used to save bandwidth by regrouping inactive players in one block,
	 * making skips more efficient
	 */
	private void processLocalPlayers(OutputStream stream, OutputStream updateBlockData, NsnBlock nsn) {
		stream.initBitAccess();

		int skip = 0;
		for (int i = 0; i < localPlayersIndexesCount; i++) {
			int playerIndex = localPlayersIndexes[i];

			if (shouldSkipPlayer(nsn, playerIndex)) {
				continue;
			}

			if (skip > 0) {
				skip--;
				skipPlayer(playerIndex);
				continue;
			}

			Player p = localPlayers[playerIndex];
			if (needsRemove(p)) {
				stream.writeBits(1, 1); // is not skipped
				stream.writeBits(1, 0); // no mask update
				stream.writeBits(2, 0); // request remove

				writePosHash(stream, p); // writes and updates the region hash if necessary
				localPlayers[playerIndex] = null;
			} else {
				boolean needsMaskUpdate = p.getMaskHandler().needsUpdate();

				if (needsLocalUpdate(p)) {
					stream.writeBits(1, 1); // is not skipped
					stream.writeBits(1, needsMaskUpdate ? 1 : 0); // needs mask update
				}

				if (needsMaskUpdate) {
					appendUpdateBlock(p, updateBlockData, false);
				}

				if (p.getMaskHandler().hasTeleported()) {
					stream.writeBits(2, 3); // teleporting

					int xOffset = p.getX() - p.getLastWorldTile().getX();
					int yOffset = p.getY() - p.getLastWorldTile().getY();
					int planeOffset = p.getPlane() - p.getLastWorldTile().getPlane();

					// -2 for safety measure
					if (Math.abs(xOffset) <= GameSettings.NORMAL_VIEWPORT - 2 && Math.abs(yOffset) <= GameSettings.NORMAL_VIEWPORT - 2) {
						stream.writeBits(1, 0);

						if (xOffset < 0) {
							xOffset += 32;
						}
						if (yOffset < 0) {
							yOffset += 32;
						}

						stream.writeBits(12, yOffset + (xOffset << 5) + (planeOffset << 10));
					} else {
						stream.writeBits(1, 1);
						stream.writeBits(30, (yOffset & 0x3fff) + ((xOffset & 0x3fff) << 14) + ((planeOffset & 0x3) << 28));
					}
				} else if (p.getMaskHandler().getCurrentMovementOpcode() != -1) {
					int opcode = p.getMaskHandler().getCurrentMovementOpcode();
					boolean running = p.getMaskHandler().isRunMovementOpcode();

					stream.writeBits(2, running ? 2 : 1); // running or walking
					stream.writeBits(running ? 4 : 3, opcode); // direction opcode
				} else if (needsMaskUpdate) {
					stream.writeBits(2, 0); // just update
				} else {
					stream.writeBits(1, 0); // skipped

					for (int j = i + 1; j < localPlayersIndexesCount; j++) {
						int p2Index = localPlayersIndexes[j];

						if (shouldSkipPlayer(nsn, p2Index)) {
							continue;
						}

						Player p2 = localPlayers[p2Index];
						if (needsRemove(p2) || needsLocalUpdate(p2)) {
							break;
						}

						skip++;
					}
					skipPlayers(stream, skip);
					skipPlayer(playerIndex);
				}

			}
		}
		stream.finishBitAccess();
	}

	/**
	 * Writes the flag that says if the non-player needs a position update
	 * If he does, it also writes his new position
     */
	private void writePosHash(OutputStream stream, Player p) {
		if (p == null) {
			stream.writeBits(1, 0); // no pos hash update
			return;
		}
		if (needsPositionUpdate(p)) {
			stream.writeBits(1, 0); // no pos hash update
		} else {
			RegionHash hash = p.getRegionHash();
			RegionHash lastHash = regionHashes[p.getIndex()];

			stream.writeBits(1, 1); // request pos hash update
			updateRegionHash(stream, hash, lastHash);
			regionHashes[p.getIndex()] = hash;
		}
	}

	/**
	 * Checks if the player needs his position updated
     */
	private boolean needsPositionUpdate(Player p) {
		if (p == null) {
			return false;
		}
		RegionHash hash = p.getRegionHash();
		RegionHash lastHash = regionHashes[p.getIndex()];
		return hash.equals(lastHash);
	}

	/**
	 * Checks if the player needs any type of local update
	 */
	private boolean needsLocalUpdate(Player p) {
		return p.getMaskHandler().needsUpdate() ||	p.getMaskHandler().hasTeleported() ||
				p.getMaskHandler().getCurrentMovementOpcode() != -1;
	}


	/**
	 * Sets the new chunk position of the player
     */
	private void updateRegionHash(OutputStream stream, RegionHash currentRegionHash, RegionHash lastRegionHash) {
		if (lastRegionHash == null) {
			lastRegionHash = new RegionHash(0);
		}

		int lastRegionX = lastRegionHash.getRegionX();
		int lastRegionY = lastRegionHash.getRegionY();
		int lastPlane = lastRegionHash.getPlane();

		int currentRegionX = currentRegionHash.getRegionX();
		int currentRegionY = currentRegionHash.getRegionY();
		int currentPlane = currentRegionHash.getPlane();

		int planeOffset = currentPlane - lastPlane;

		if (lastRegionX == currentRegionX && lastRegionY == currentRegionY) {
			stream.writeBits(2, 1); // plane update
			stream.writeBits(2, planeOffset); // sends the plane
		} else if (Math.abs(currentRegionX - lastRegionX) <= 1 && Math.abs(currentRegionY - lastRegionY) <= 1) {
			int dx = currentRegionX - lastRegionX;
			int dy = currentRegionY - lastRegionY;
			int opcode = WalkStep.getDirectionFromOffset(dx, dy); // usually used for walk, but same thing

			stream.writeBits(2, 2); // moved +-1 region update
			stream.writeBits(5, (planeOffset << 3) + (opcode & 0x7)); // new region move direction (and plane)
		} else {
			int xOffset = currentRegionX - lastRegionX;
			int yOffset = currentRegionY - lastRegionY;
			stream.writeBits(2, 3); // new region update (more than +-1)
			stream.writeBits(18, (yOffset & 0xff) + ((xOffset & 0xff) << 8) + (planeOffset << 16)); // new region offset
		}
	}

	/**
	 * Adds a local player from the non-local players
     */
	private void addLocalPlayer(Player p) {
		localAddedPlayers++;
		localPlayers[p.getIndex()] = p;
		skipPlayer(p.getIndex());
	}

	/**
	 * Skips X players of the 2048
	 */
	private void skipPlayers(OutputStream stream, int amount) {
		int skipSize = (amount == 0) ? 0 : (amount > 255) ? 3 : (amount > 31) ? 2 : 1;
		stream.writeBits(2, skipSize);
		if (amount > 0) {
			int bitsAmt = (amount > 255) ? 11 : (amount > 31) ? 8 : 5;
			stream.writeBits(bitsAmt, amount);
		}
	}

	/**
	 * Flags the player as "skipped" so he can be processed in the other nsn block next tick
     */
	public void skipPlayer(int index) {
		skipFlags[index] |= SKIPPED_THIS_CYCLE;
	}

	/**
	 * Checks if we should skip this player based on the nsn and which flag the player has
     */
	private boolean shouldSkipPlayer(NsnBlock nsn, int index) {
		int skipFlag = skipFlags[index];
		return (nsn == NsnBlock.NSN_0 || nsn == NsnBlock.NSN_3) ? (skipFlag & SKIPPED_LAST_CYCLE) != 0 :
				(skipFlag & SKIPPED_LAST_CYCLE) == 0;
	}

	/**
	 * Checks if a player needs to be added to the local players
     */
	private boolean needsAdd(Player p) {
		return p != null && player.withinDistance(p, player.getViewSceneDistance()) && localAddedPlayers < MAX_PLAYER_ADD;
	}

	/**
	 * Checks if a plyer needs to be removed from the local players
     */
	private boolean needsRemove(Player p) {
		return !player.withinDistance(p, player.getViewSceneDistance());
	}

	private enum NsnBlock {
		NSN_0, NSN_1, NSN_2, NSN_3
	}

	private final byte SKIPPED_THIS_CYCLE = 0x2;
	private final byte SKIPPED_LAST_CYCLE = 0x1;
	private final byte NOT_SKIPPED = 0;

	private void appendUpdateBlock(Player p, OutputStream stream, boolean firstTime) {
		p.getMaskHandler().encodeUpdateBlock(stream, firstTime);
	}

	public void increaseRenderDataLength(int length) {
		renderDataLength += length;
	}

}
Code:
package com.rs.game.player.masks.appearance;

import com.common.cache.reader.loaders.NPCDefinitions;
import com.common.cache.reader.loaders.item.ItemDefinitions;
import com.common.io.OutputStream;
import com.common.utils.Logger;
import com.rs.game.item.Item;
import com.rs.game.item.ItemConstants;
import com.rs.game.player.Player;
import com.rs.game.player.containers.equipment.AuraManager;
import com.rs.game.player.containers.equipment.EquipmentSlot;
import lombok.Getter;
import lombok.Setter;

/**
 * Created by clem585 on 2018-06-18.
 */
public class PlayerAppearance {

    private transient Player player;

    private Title title;
    private PlayerBody playerBody;
    private CustomColorHandler customColorHandler;

    private boolean hidePlayer;
    private transient int renderEmote;
    private transient int transformedNpcId;
    private int overheadSkull;

    @Getter @Setter
    private transient boolean needsUpdate;

    @Getter
    private transient byte[] appearanceData;

    public PlayerAppearance() {
        this.playerBody = new PlayerBody();
        this.title = Title.NEWCOMER;
        this.customColorHandler = new CustomColorHandler();
        this.overheadSkull = -1;
    }

    public void setPlayer(Player player) {
        this.player = player;
        this.transformedNpcId = -1;
        this.renderEmote = 1426;
    }

    public void updateAppearence() {
        OutputStream stream = new OutputStream();

        int flag = 0;

        if (!playerBody.isMale()) {
            flag |= 0x1;
        }

        /*if (transformedNpcId >= 0) {
            flag |= 0x2;
        }

        if (title != null) {
            flag |= title.isAfterName() ? 0x80 : 0x40;
        }*/

        stream.writeByte(flag);
        stream.writeByte(0);

        /*if (title != null) {
            stream.writeGJString(title.getFullTitle(playerBody.isMale()));
        }*/

        stream.writeByte(overheadSkull);

        stream.writeByte(player.getPrayer().getPrayerHeadIcon());
        stream.writeByte(hidePlayer ? 1 : 0);

        if (transformedNpcId >= 0) {
            stream.writeShort(-1); // 65535 tells it a npc
            stream.writeShort(transformedNpcId);
            stream.writeByte(0);
        } else {
            //hat, cape, amulet, weapon
            for (int index = 0; index < 4; index++) {
                Item item = player.getEquipment().get(index);
                if (item == null) {
                    stream.writeByte(0);
                } else {
                    stream.writeShort(32768 + item.getId());//TODO import equip ids from 718+
                }
            }

            //chest
            Item item = player.getEquipment().get(EquipmentSlot.CHEST);
            stream.writeShort(item == null ? 0x100 + playerBody.getTop().getId() : 32768 + item.getId());

            //shield
            item = player.getEquipment().get(EquipmentSlot.SHIELD);
            if (item == null) {
                stream.writeByte(0);
            } else {
                stream.writeShort(32768 + item.getId());
            }

            //arms
            item = player.getEquipment().get(EquipmentSlot.CHEST);
            if (item == null || !ItemConstants.hideArms(item)) {
                stream.writeShort(0x100 + playerBody.getArms().getId());
            } else {
                stream.writeByte(0);
            }

            //legs
            item = player.getEquipment().get(EquipmentSlot.LEGS);
            stream.writeShort(item == null ? 0x100 + playerBody.getLegs().getId() : 32768 + item.getId());

            //hat
            item = player.getEquipment().get(EquipmentSlot.HAT);
            if (item == null || !ItemConstants.hideHair(item)) {
                stream.writeShort(0x100 + playerBody.getHair().getId());
            } else {
                stream.writeByte(0);
            }

            //hands
            item = player.getEquipment().get(EquipmentSlot.HANDS);
            stream.writeShort(item == null ? 0x100 + playerBody.getWrist().getId() : 16384 + item.getId());

            //feet
            item = player.getEquipment().get(EquipmentSlot.FEET);
            stream.writeShort(item == null ? 0x100 + playerBody.getShoes().getId() : 16384 + item.getId());

            // tits for female, bear for male - Really Fox?
            item = player.getEquipment().get(playerBody.isMale() ? EquipmentSlot.HAT : EquipmentSlot.CHEST);
            if (item == null || (playerBody.isMale() && ItemConstants.showBear(item))) {
                stream.writeShort(0x100 + playerBody.getBeard().getId());
            } else {
                stream.writeByte(0);
            }


            /*item = player.getEquipment().get(EquipmentSlot.AURA);
            if (item != null) {
                stream.writeShort(32768 + item.getId());
            } else {
                stream.writeByte(0);
            }*/

            /*int pos = stream.getOffset();
            stream.writeShort(0);
            int hash = 0;
            int slotFlag = -1;

            for (int slotId = 0; slotId < player.getEquipment().getSize(); slotId++) {
                if (slotId == EquipmentSlot.RING.getId() || slotId == EquipmentSlot.ARROWS.getId()) {
                    continue;
                }
                slotFlag++;

                //can be used for custom capes colors later on.
                item = player.getEquipment().get(slotId);
                if (item != null) {
                    if (customColorHandler.isCustomColors(item.getId())) {
                        hash |= 1 << slotFlag;
                        stream.writeByte(0x4); // modify 4 model colors

                        int slots = 1 << 4 | 2 << 8 | 3 << 12;
                        stream.writeShort(slots);

                        int[] hat = customColorHandler.getCustomColors(item.getId());
                        for (int i = 0; i < 4; i++) {
                            stream.writeShort(hat[i]);
                        }
                    }

                    if (slotId == EquipmentSlot.AURA.getId()) {
                        AuraManager auraManager = player.getEquipment().getAuraManager();
                        if (!auraManager.isActivated()) {
                            continue;
                        }

                        ItemDefinitions auraDefs = ItemDefinitions.getItemDefinitions(item.getId());
                        if (auraDefs.getMaleEquip1() == -1 || auraDefs.getFemaleEquip1() == -1) {
                            continue;
                        }

                        hash |= 1 << slotFlag;
                        stream.writeByte(0x1); // modify model ids

                        int weaponModelId = auraManager.getWeaponAuraId();
                        stream.writeBigSmart(weaponModelId); // male modelid1
                        stream.writeBigSmart(weaponModelId); // female modelid1

                        if (auraDefs.getMaleEquip2() != -1 || auraDefs.getFemaleEquip2() != -1) {
                            int wingsId = auraManager.getWingsId();
                            stream.writeBigSmart(wingsId);
                            stream.writeBigSmart(wingsId);
                        }
                    }
                }
            }
            int pos2 = stream.getOffset();
            stream.setOffset(pos);
            stream.writeShort(hash);
            stream.setOffset(pos2);*/
        }

        playerBody.encodeColors(stream);

        Item item = player.getEquipment().get(EquipmentSlot.WEAPON);
        if (item == null || item.getDefinitions().getRenderAnimId() == 0) {
            stream.writeShort(renderEmote);
        } else {
            stream.writeShort(item.getDefinitions().getRenderAnimId());
        }

        stream.writeString(player.getDisplayName());

        //boolean pvpArea = player.canAttack();
        ///stream.writeByte(pvpArea ? (int)player.getSkills().getCombatLevel() : (int)player.getSkills().getCombatLevelWithSummoning());
        //stream.writeByte(pvpArea ? (int)player.getSkills().getCombatLevelWithSummoning() : 0);
        stream.writeByte((int)player.getSkills().getCombatLevel());

        stream.writeByte(0);
        stream.writeByte(0);
        stream.writeByte(0);

        byte[] bufferData = stream.getBuffer();
        for (int i = 0; i < stream.getOffset(); ++i) {
            Logger.flash("bufferData[" + i + "]: " + bufferData[i]);
        }

        //stream.writeByte(stream.getOffset());

        /*stream.writeByte(-1);

        stream.writeByte(transformedNpcId >= 0 ? 1 : 0);
        if (transformedNpcId >= 0) {
            NPCDefinitions defs = NPCDefinitions.getNPCDefinitions(transformedNpcId);
            stream.writeShort(defs.getAnInt876());
            stream.writeShort(defs.getAnInt842());
            stream.writeShort(defs.getAnInt884());
            stream.writeShort(defs.getAnInt875());
            stream.writeByte(defs.getAnInt875());
        }*/

        byte[] appeareanceData = new byte[stream.getOffset()];
        System.arraycopy(stream.getBuffer(), 0, appeareanceData, 0, appeareanceData.length);
        this.appearanceData = appeareanceData;

        needsUpdate = true;
    }

}
*No errors client-sided.


Lol figured it out 2 minutes after posting this. Condition for writePosHash(...) wasn't correct. Should've been !needsPositionUpdate(...). Player is invisible now though... Any suggestions?