ChatState chat;
 
+       int time_skipped;
+       unsigned int packets_skipped;
+
 };
 
 }
 
 public:
        explicit NetworkedInput(World &, Player &, Client &);
 
+       bool UpdateImportant() const noexcept;
        void Update(Entity &, float dt) override;
        void PushPlayerUpdate(int dt);
        void MergePlayerCorrection(std::uint16_t, const EntityState &);
        };
        std::list<PlayerHistory> player_hist;
 
+       glm::vec3 old_movement;
+
+       std::uint8_t old_actions;
        std::uint8_t actions;
 
 };
 
 , stat_timer(1000)
 , sky(master.GetEnv().loader.LoadCubeMap("skybox"))
 , update_status()
-, chat(master.GetEnv(), *this, *this) {
+, chat(master.GetEnv(), *this, *this)
+, time_skipped(0)
+, packets_skipped(0) {
        if (!save.Exists()) {
                save.Write(master.GetWorldConf());
        }
                hud.FocusNone();
        }
        if (world_dt > 0) {
-               input.PushPlayerUpdate(world_dt);
+               if (input.UpdateImportant() || packets_skipped >= master.NetStat().SuggestedPacketSkip()) {
+                       input.PushPlayerUpdate(time_skipped + world_dt);
+                       time_skipped = 0;
+                       packets_skipped = 0;
+               } else {
+                       time_skipped += world_dt;
+                       ++packets_skipped;
+               }
        }
        hud.Display(res.block_types[player.GetInventorySlot() + 1]);
        if (stat_timer.Hit()) {
 
 : PlayerController(world, player)
 , client(client)
 , player_hist()
+, old_movement(0.0f)
+, old_actions(0)
 , actions(0) {
 
 }
 
+bool NetworkedInput::UpdateImportant() const noexcept {
+       return old_actions != actions || !iszero(old_movement - GetMovement());
+}
+
 void NetworkedInput::Update(Entity &, float dt) {
        Invalidate();
        UpdatePlayer();
                entry->packet = packet;
                player_hist.splice(player_hist.end(), player_hist, entry);
        }
+       old_movement = GetMovement();
+       old_actions = actions;
 }
 
 void NetworkedInput::MergePlayerCorrection(uint16_t seq, const EntityState &corrected_state) {
 
 
        /// get recommended mode of operation
        Mode GetMode() const noexcept { return mode; }
+       /// according to current mode, drop this many unimportant packets
+       unsigned int SuggestedPacketSkip() const noexcept { return (1 << mode) - 1; }
 
        /// packet loss as factor
        float PacketLoss() const noexcept { return packet_loss; }
 
 
        void SendSpawn(SpawnStatus &);
        void SendDespawn(SpawnStatus &);
+       /// true if updates are pushed to the client this frame
+       bool SendingUpdates() const noexcept;
        void QueueUpdate(SpawnStatus &);
        void SendUpdates();
 
        unsigned int confirm_wait;
 
        std::vector<SpawnStatus *> entity_updates;
+       unsigned int entity_updates_skipped;
 
        EntityState player_update_state;
        std::uint16_t player_update_pack;
 
 , spawns()
 , confirm_wait(0)
 , entity_updates()
+, entity_updates_skipped(0)
 , player_update_state()
 , player_update_pack(0)
 , player_update_timer(1500)
                                // they're the same
                                if (CanDespawn(*global_iter)) {
                                        SendDespawn(*local_iter);
-                               } else {
+                               } else if (SendingUpdates()) {
                                        // update
                                        QueueUpdate(*local_iter);
                                }
        ++confirm_wait;
 }
 
+bool ClientConnection::SendingUpdates() const noexcept {
+       return entity_updates_skipped >= NetStat().SuggestedPacketSkip();
+}
+
 void ClientConnection::QueueUpdate(SpawnStatus &status) {
        // don't send updates while spawn not ack'd or despawn sent
        if (status.spawn_pack == -1 && status.despawn_pack == -1) {
 }
 
 void ClientConnection::SendUpdates() {
+       if (!SendingUpdates()) {
+               entity_updates.clear();
+               ++entity_updates_skipped;
+               return;
+       }
        auto base = PlayerChunks().Base();
        auto pack = Prepare<Packet::EntityUpdate>();
        pack.WriteChunkBase(base);
                Send(Packet::EntityUpdate::GetSize(entity_pos));
        }
        entity_updates.clear();
+       entity_updates_skipped = 0;
 }
 
 void ClientConnection::CheckPlayerFix() {