but that's by accident and will break if conversion code is ever
 added)
 
+Common Types
+------------
+
+Name          Size  Type
+vec3          12    3x 32bit float
+vec3i         12    3x 32bit signed int
+quat          16    4x 32bit float
+entity state  64    vec3i, vec3, vec3, quat, vec3
+
 
 Packets
 =======
 Code: 2
 Payload:
         0 entity ID of the player, 32bit unsigned int
-        4 chunk coords of the player, 3x 32bit signed int
-       16 pos/vel/rot/ang of the player, 13x 32bit float
+        4 entity state of the player
        68 name of the world the server's currently running
           max 32 byte UTF-8 string
 Length: 68-100
 
 Code: 4
 Payload:
-        0 chunk coords of the player, 3x 32bit signed int
-       12 pos/vel/rot/ang of the player, 13x 32bit float
+        0 entity state of the player as seen by the client
 Length: 64
 
 
 Payload:
          0 entity ID, 32bit unsigned int
          4 entity's skeleton ID, 32bit unsigned int
-         8 chunk coords of the entity, 3x 32bit signed int
-        20 pos/vel/rot/ang of the entity, 13x 32bit float
+         8 entity state
         72 bounding box of the entity, 6x 32bit float
         96 flags, 32bit bitfield with boolean values
            1: world collision
 Code: 7
 Payload:
         0 number of entities, 32bit int, 1-7
-        4 chunk coords of the entity, 3x 32bit signed int
-       16 pos/vel/rot/ang of the entity, 13x 32bit float
-       68 next entity...
-Length: 4 + multiple of 64, max 452
+        4 entity ID, 32bit unsigned int
+        8 entity state
+       72 next entity...
+Length: 4 + multiple of 68, max 452
+
+
+Player Correction
+-----------------
+
+Sent by the server to tell a client that its prediction is way off.
+
+Code: 8
+Payload:
+        0 sequence number of the offending packet, 16bit unsigned int
+        2 entity state of the player's entity on the server
+Length: 66
 
 #include "../ui/Interface.hpp"
 #include "../world/BlockTypeRegistry.hpp"
 #include "../world/ChunkRenderer.hpp"
+#include "../world/EntityState.hpp"
 #include "../world/World.hpp"
 
+#include <list>
+
 
 namespace blank {
 
        void Update(int dt) override;
        void Render(Viewport &) override;
 
+       void PushPlayerUpdate(const Entity &);
+       void MergePlayerCorrection(std::uint16_t, const EntityState &);
+
 private:
        MasterState &master;
        BlockTypeRegistry block_types;
        Skeletons skeletons;
        IntervalTimer update_timer;
 
+       struct PlayerHistory {
+               EntityState state;
+               int timestamp;
+               std::uint16_t packet;
+               PlayerHistory(EntityState s, int t, std::uint16_t p)
+               : state(s), timestamp(t), packet(p) { }
+       };
+       std::list<PlayerHistory> player_hist;
+
 };
 
 }
 
        void On(const Packet::SpawnEntity &) override;
        void On(const Packet::DespawnEntity &) override;
        void On(const Packet::EntityUpdate &) override;
+       void On(const Packet::PlayerCorrection &) override;
 
 private:
        /// flag entity as updated by given packet
 
 )
 , chunk_renderer(*interface.GetPlayer().chunks)
 , skeletons()
-, update_timer(16) {
+, update_timer(16)
+, player_hist() {
        TextureIndex tex_index;
        master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index);
        chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index);
        Entity &player = *interface.GetPlayer().entity;
 
        if (update_timer.Hit()) {
-               master.GetClient().SendPlayerUpdate(player);
+               PushPlayerUpdate(player);
        }
 
        glm::mat4 trans = player.Transform(player.ChunkCoords());
        master.GetEnv().audio.Orientation(dir, up);
 }
 
+void InteractiveState::PushPlayerUpdate(const Entity &player) {
+       std::uint16_t packet = master.GetClient().SendPlayerUpdate(player);
+       if (player_hist.size() < 16) {
+               player_hist.emplace_back(player.GetState(), update_timer.Elapsed(), packet);
+       } else {
+               auto entry = player_hist.begin();
+               entry->state = player.GetState();
+               entry->timestamp = update_timer.Elapsed();
+               entry->packet = packet;
+               player_hist.splice(player_hist.end(), player_hist, entry);
+       }
+}
+
+void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &corrected_state) {
+       if (player_hist.empty()) return;
+
+       auto entry = player_hist.begin();
+       auto end = player_hist.end();
+
+       // drop anything older than the fix
+       while (entry != end) {
+               int pack_diff = int16_t(seq) - int16_t(entry->packet);
+               if (pack_diff < 0) {
+                       entry = player_hist.erase(entry);
+               } else {
+                       break;
+               }
+       }
+       if (entry == end) return;
+}
+
 void InteractiveState::Render(Viewport &viewport) {
        Entity &player = *interface.GetPlayer().entity;
        viewport.WorldPosition(player.Transform(player.ChunkCoords()));
        update_status.erase(entity_id);
 }
 
+void MasterState::On(const Packet::PlayerCorrection &pack) {
+       if (!state) {
+               cout << "got player correction without a player :S" << endl;
+               Quit();
+               return;
+       }
+       uint16_t pack_seq;
+       EntityState corrected_state;
+       pack.ReadPacketSeq(pack_seq);
+       pack.ReadPlayerState(corrected_state);
+       state->MergePlayerCorrection(pack_seq, corrected_state);
+}
+
 }
 }
 
        void SendDespawn(SpawnStatus &);
        void SendUpdate(SpawnStatus &);
 
+       void CheckPlayerFix();
+
 private:
        Server &server;
        Connection conn;
 
        virtual void On(const Packet::SpawnEntity &) { }
        virtual void On(const Packet::DespawnEntity &) { }
        virtual void On(const Packet::EntityUpdate &) { }
+       virtual void On(const Packet::PlayerCorrection &) { }
 
 };
 
 
                void ReadEntityState(EntityState &, std::uint32_t) const noexcept;
        };
 
+       struct PlayerCorrection : public Payload {
+               static constexpr std::uint8_t TYPE = 8;
+               static constexpr std::size_t MAX_LEN = 66;
+
+               void WritePacketSeq(std::uint16_t) noexcept;
+               void ReadPacketSeq(std::uint16_t &) const noexcept;
+               void WritePlayer(const Entity &) noexcept;
+               void ReadPlayerState(EntityState &) const noexcept;
+       };
+
 
        template<class PayloadType>
        PayloadType As() {
 
 constexpr size_t Packet::SpawnEntity::MAX_LEN;
 constexpr size_t Packet::DespawnEntity::MAX_LEN;
 constexpr size_t Packet::EntityUpdate::MAX_LEN;
+constexpr size_t Packet::PlayerCorrection::MAX_LEN;
 
 namespace {
 
                        SendDespawn(*local_iter);
                        ++local_iter;
                }
+
+               CheckPlayerFix();
        }
        if (conn.ShouldPing()) {
                conn.SendPing(server.GetPacket(), server.GetSocket());
        conn.Send(server.GetPacket(), server.GetSocket());
 }
 
+void ClientConnection::CheckPlayerFix() {
+       // check always succeeds for now ;)
+       auto pack = Packet::Make<Packet::PlayerCorrection>(server.GetPacket());
+       pack.WritePacketSeq(player_update_pack);
+       pack.WritePlayer(Player());
+       conn.Send(server.GetPacket(), server.GetSocket());
+}
+
 void ClientConnection::AttachPlayer(Entity &new_player) {
        DetachPlayer();
        player = &new_player;
                        return "DespawnEntity";
                case EntityUpdate::TYPE:
                        return "EntityUpdate";
+               case PlayerCorrection::TYPE:
+                       return "PlayerCorrection";
                default:
                        return "Unknown";
        }
        Read(state, off + 4);
 }
 
+void Packet::PlayerCorrection::WritePacketSeq(std::uint16_t s) noexcept {
+       Write(s, 0);
+}
+
+void Packet::PlayerCorrection::ReadPacketSeq(std::uint16_t &s) const noexcept {
+       Read(s, 0);
+}
+
+void Packet::PlayerCorrection::WritePlayer(const Entity &player) noexcept {
+       Write(player.GetState(), 2);
+}
+
+void Packet::PlayerCorrection::ReadPlayerState(EntityState &state) const noexcept {
+       Read(state, 2);
+}
+
 
 void ConnectionHandler::Handle(const UDPpacket &udp_pack) {
        const Packet &pack = *reinterpret_cast<const Packet *>(udp_pack.data);
                case Packet::EntityUpdate::TYPE:
                        On(Packet::As<Packet::EntityUpdate>(udp_pack));
                        break;
+               case Packet::PlayerCorrection::TYPE:
+                       On(Packet::As<Packet::PlayerCorrection>(udp_pack));
+                       break;
                default:
                        // drop unknown or unhandled packets
                        break;