build
 cachegrind.out.*
 callgrind.out.*
+client-saves
 saves
 
 server: $(ASSET_DEP) blank
        ./blank --server --save-path saves/
 
+client: $(ASSET_DEP) blank
+       ./blank --client --save-path client-saves/
+
 gdb: $(ASSET_DEP) blank.debug
        gdb ./blank.debug
 
 
 distclean: clean
        rm -f $(BIN) cachegrind.out.* callgrind.out.*
-       rm -Rf build saves
+       rm -Rf build client-saves saves
 
 .PHONY: all release debug profile tests run gdb cachegrind callgrind test clean distclean
 
 
+++ /dev/null
-Dependencies
-============
-
-       GLEW, GLM, SDL2, SDL2_image, SDL2_net, SDL2_ttf, OpenAL, freealut, zlib
-
-       CppUnit for tests
-
-archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_net sdl2_ttf openal freealut zlib cppunit
-
-manual:
-       CppUnit http://sourceforge.net/projects/cppunit/
-       GLEW    http://glew.sourceforge.net/
-       GLM     http://glm.g-truc.net/0.9.6/index.html
-       OpenAL  http://openal.org/
-       SDL     http://www.libsdl.org/
-       zlib    http://zlib.net/
-
-
-Makefile
-========
-
-Targets
--------
-
-all:
-       build everything
-
-release (default), debug, profile:
-       build executables tuned for running, debugging, and profiling
-
-run:
-       build and execute the main binary with state path set to ./saves
-
-server:
-       same as run, only in server mode
-
-test:
-       build and run unittests
-
-gdb, cachegrind, callgrind:
-       build the binary suited for given tool and launch
-
-clean:
-       remove intermediates
-
-distclean:
-       remove intermediates and artifacts
-       (also those generated by tool invocations!)
-
-
-Variables
----------
-
-CXX, LXX:
-       compiler/linker used for C++ sources/objects
-
-LIBS:
-       names of libraries (for pkg-config)
-
-CPPFLAGS, CXXFLAGS, LDXXFLAGS:
-       flags for the preprocessor, compiler, and linker
-
-DEBUG_FLAGS, PROFILE_FLAGS, RELEASE_FLAGS:
-       flags for building binaries in debug, profile, and release mode
 
--- /dev/null
+Dependencies
+============
+
+       GLEW, GLM, SDL2, SDL2_image, SDL2_net, SDL2_ttf, OpenAL, freealut, zlib
+
+       CppUnit for tests
+
+archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_net sdl2_ttf openal freealut zlib cppunit
+
+manual:
+       CppUnit http://sourceforge.net/projects/cppunit/
+       GLEW    http://glew.sourceforge.net/
+       GLM     http://glm.g-truc.net/0.9.6/index.html
+       OpenAL  http://openal.org/
+       SDL     http://www.libsdl.org/
+       zlib    http://zlib.net/
+
+
+Makefile
+========
+
+Targets
+-------
+
+all:
+       build everything
+
+release (default), debug, profile:
+       build executables tuned for running, debugging, and profiling
+
+run:
+       build and execute the main binary with state path set to ./saves
+
+server:
+       same as run, only in server mode
+
+server:
+       same as run, only in client mode and the save path is set to
+       ./client-saved to prevent clashes with a running `make server`
+
+test:
+       build and run unittests
+
+gdb, cachegrind, callgrind:
+       build the binary suited for given tool and launch
+
+clean:
+       remove intermediates
+
+distclean:
+       remove intermediates and artifacts
+       (also those generated by tool invocations!)
+
+
+Variables
+---------
+
+CXX, LXX:
+       compiler/linker used for C++ sources/objects
+
+LIBS:
+       names of libraries (for pkg-config)
+
+CPPFLAGS, CXXFLAGS, LDXXFLAGS:
+       flags for the preprocessor, compiler, and linker
+
+DEBUG_FLAGS, PROFILE_FLAGS, RELEASE_FLAGS:
+       flags for building binaries in debug, profile, and release mode
 
--- /dev/null
+Packets
+=======
+
+Ping
+----
+
+To tell the other side we're still alive.
+Both server and client will send this if they haven't sent something in
+a while.
+
+Code: 0
+Payload: none
+
+
+Login
+-----
+
+Sent from client to serveri as a request to join. The server may
+respond negatively if the player name is already taken or some cap has
+been reached.
+
+Code: 1
+Payload:
+       player name, max 32 byte UTF-8 string,
+       shorter names should be zero terminated
 
--- /dev/null
+Arguments
+=========
+
+Runtime
+-------
+
+-n <n>
+       terminate after <n> frames
+
+-t <t>
+       terminate after <t> milliseconds
+
+if both n and t are given, terminate after n frames and
+assume <t> milliseconds pass each frame
+
+--asset-path <path>
+       load assets from given path
+       default is application dir + "assets"
+
+--save-path <path>
+       store and load saves at given path
+       default is whatever SDL thinks is good
+       (should be ~/.local/share/localhorst/blank/)
+
+Application
+-----------
+
+-d
+       disable double buffering
+
+-m <num>
+       set sample size to <num> (samples per pixel)
+
+--no-vsync
+       disable vsync
+
+--standalone
+       run as standalone (the default)
+
+--client
+       run as client
+
+--server
+       run as server
+
+Interface
+---------
+
+--no-keyboard
+       disable keyboard input handling
+
+--no-mouse
+       disable mouse input handling
+
+--no-hud
+       disable HUD drawing (includes the selected block outline)
+
+--no-audio
+       disable audio
+       the audio device and sounds will still be allocated
+       it just stops the interface from queueing buffers
+
+Network
+-------
+
+--host <hostname>
+       hostname to connect to in client mode
+
+--port <number>
+       port number to connection to (client) or listen on (server)
+
+--player-name <name>
+       use given name to identify with the server (client mode)
+       default player name is "default"
+       the server will reject players with names that are already taken
+
+World
+-----
+
+-s <seed>
+       use <seed> (unsigned integer) as the world seed
+       only used for newly created worlds
+       default is 0
+
+--world-name <name>
+       use given name for the world save
+       no checks are being done right now, so make sure it can be
+       used as a directory name
+
+
+Controls
+========
+
+Move around with WSAD, shift, and space, look around with mouse motion.
+Mouse button 1 deletes the block you're pointing at, button 2 selects it
+as the active block, and button 3 places the active block next to the one
+you're pointing at.
+
+As an alternative to picking, you can also use the mousewheel up/down to
+flip through available blocks.
+
+Q changes the face of the active block (loops over up, down, right, left,
+front, and back) and E changes the turn (none, left, around, and right).
+
+Press N to toggle player/world collision.
+
+F1 toggles UI rendering.
+F3 toggles a display telling how long on average it takes to compute a frame.
+F4 toggles audio.
+
+Controls are interpreted by scancode, meaning you don't have to break your
+fingers when you're on an AZERTY. WSAD will be ZSQD there and the above
+description is just wrong.
+
+Also I've added a plethora of alternate keys that can be used, like arrow
+keys for movement, ins/del for placing/removing blocks, etc.
 
+++ /dev/null
-Arguments
-=========
-
-Runtime
--------
-
--n <n>
-       terminate after <n> frames
-
--t <t>
-       terminate after <t> milliseconds
-
-if both n and t are given, terminate after n frames and
-assume <t> milliseconds pass each frame
-
---asset-path <path>
-       load assets from given path
-       default is application dir + "assets"
-
---save-path <path>
-       store and load saves at given path
-       default is whatever SDL thinks is good
-       (should be ~/.local/share/localhorst/blank/)
-
-Application
------------
-
--d
-       disable double buffering
-
--m <num>
-       set sample size to <num> (samples per pixel)
-
---no-vsync
-       disable vsync
-
---standalone
-       run as standalone (the default)
-
---client
-       run as client
-
---server
-       run as server
-
-Interface
----------
-
---no-keyboard
-       disable keyboard input handling
-
---no-mouse
-       disable mouse input handling
-
---no-hud
-       disable HUD drawing (includes the selected block outline)
-
---no-audio
-       disable audio
-       the audio device and sounds will still be allocated
-       it just stops the interface from queueing buffers
-
-Network
--------
-
---host <hostname>
-       hostname to connect to in client mode
-
---port <number>
-       port number to connection to (client) or listen on (server)
-
---player-name <name>
-       use given name to identify with the server (client mode)
-       default player name is "default"
-       the server will reject players with names that are already taken
-
-World
------
-
--s <seed>
-       use <seed> (unsigned integer) as the world seed
-       only used for newly created worlds
-       default is 0
-
---world-name <name>
-       use given name for the world save
-       no checks are being done right now, so make sure it can be
-       used as a directory name
-
-
-Controls
-========
-
-Move around with WSAD, shift, and space, look around with mouse motion.
-Mouse button 1 deletes the block you're pointing at, button 2 selects it
-as the active block, and button 3 places the active block next to the one
-you're pointing at.
-
-As an alternative to picking, you can also use the mousewheel up/down to
-flip through available blocks.
-
-Q changes the face of the active block (loops over up, down, right, left,
-front, and back) and E changes the turn (none, left, around, and right).
-
-Press N to toggle player/world collision.
-
-F1 toggles UI rendering.
-F3 toggles a display telling how long on average it takes to compute a frame.
-F4 toggles audio.
-
-Controls are interpreted by scancode, meaning you don't have to break your
-fingers when you're on an AZERTY. WSAD will be ZSQD there and the above
-description is just wrong.
-
-Also I've added a plethora of alternate keys that can be used, like arrow
-keys for movement, ins/del for placing/removing blocks, etc.
 
 #include "ClientState.hpp"
 
 #include "Environment.hpp"
+#include "init.hpp"
 #include "TextureIndex.hpp"
 
 namespace blank {
        Environment &env,
        const World::Config &wc,
        const WorldSave &ws,
+       const Interface::Config &ic,
        const Client::Config &cc
 )
 : env(env)
 , block_types()
 , world(block_types, wc, ws)
+, chunk_renderer(world, wc.load.load_dist)
+, interface(ic, env, world)
 , client(cc, world) {
+       TextureIndex tex_index;
+       env.loader.LoadBlockTypes("default", block_types, tex_index);
+       chunk_renderer.LoadTextures(env.loader, tex_index);
+       chunk_renderer.FogDensity(wc.fog_density);
+       // TODO: better solution for initializing HUD
+       interface.SelectNext();
+       client.SendLogin(ic.player_name);
+}
+
 
+void ClientState::OnEnter() {
+       env.window.GrabMouse();
 }
 
 
 void ClientState::Handle(const SDL_Event &event) {
-       if (event.type == SDL_QUIT) {
-               env.state.PopAll();
+       switch (event.type) {
+               case SDL_KEYDOWN:
+                       interface.HandlePress(event.key);
+                       break;
+               case SDL_KEYUP:
+                       interface.HandleRelease(event.key);
+                       break;
+               case SDL_MOUSEBUTTONDOWN:
+                       interface.HandlePress(event.button);
+                       break;
+               case SDL_MOUSEBUTTONUP:
+                       interface.HandleRelease(event.button);
+                       break;
+               case SDL_MOUSEMOTION:
+                       interface.Handle(event.motion);
+                       break;
+               case SDL_MOUSEWHEEL:
+                       interface.Handle(event.wheel);
+                       break;
+               case SDL_QUIT:
+                       env.state.Pop();
+                       break;
+               default:
+                       break;
        }
 }
 
        if (client.TimedOut()) {
                env.state.Pop();
        }
+
+       interface.Update(dt);
+       world.Update(dt);
+       chunk_renderer.Rebase(interface.Player().ChunkCoords());
+       chunk_renderer.Update(dt);
+
+       glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords());
+       glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
+       glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
+       env.audio.Position(interface.Player().Position());
+       env.audio.Velocity(interface.Player().Velocity());
+       env.audio.Orientation(dir, up);
 }
 
 
 void ClientState::Render(Viewport &viewport) {
-
+       viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords()));
+       chunk_renderer.Render(viewport);
+       world.Render(viewport);
+       interface.Render(viewport);
 }
 
 }
 
 
 #include "State.hpp"
 #include "../net/Client.hpp"
+#include "../ui/Interface.hpp"
 #include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkRenderer.hpp"
 #include "../world/World.hpp"
 
 
                Environment &,
                const World::Config &,
                const WorldSave &,
+               const Interface::Config &,
                const Client::Config &
        );
 
+       void OnEnter() override;
+
        void Handle(const SDL_Event &) override;
        void Update(int dt) override;
        void Render(Viewport &) override;
        Environment &env;
        BlockTypeRegistry block_types;
        World world;
+       ChunkRenderer chunk_renderer;
+       Interface interface;
        Client client;
 
 };
 
        env.audio.Position(interface.Player().Position());
        env.audio.Velocity(interface.Player().Velocity());
        env.audio.Orientation(dir, up);
-
 }
 
 void WorldState::Render(Viewport &viewport) {
 
        }
 
        Application app(env);
-       ClientState client_state(env, config.world, save, config.client);
+       ClientState client_state(env, config.world, save, config.interface, config.client);
        app.PushState(&client_state);
        Run(app);
 }
 
 
        bool TimedOut() { return conn.TimedOut(); }
 
+       void SendPing();
+       void SendLogin(const std::string &);
+
 private:
        void HandlePacket(const UDPpacket &);
 
 
 #define BLANK_NET_PACKET_HPP_
 
 #include <cstdint>
+#include <string>
 
 
 namespace blank {
        static constexpr std::uint32_t TAG = 0xFB1AB1AF;
 
        enum Type {
-               PING,
+               PING = 0,
+               LOGIN = 1,
        };
 
        struct Header {
        void Tag() noexcept;
 
        std::size_t Ping() noexcept;
+       std::size_t Login(const std::string &name) noexcept;
 
 };
 
 
        void OnConnect(Connection &);
        void OnDisconnect(Connection &);
 
+       void HandleLogin(Connection &client, const UDPpacket &);
+
 private:
        UDPsocket serv_sock;
        UDPpacket serv_pack;
 
 #include "Server.hpp"
 
 #include "../app/init.hpp"
+#include "../world/World.hpp"
 
 #include <cstring>
 #include <iostream>
        client_pack.data = new Uint8[sizeof(Packet)];
        client_pack.maxlen = sizeof(Packet);
        // establish connection
-       conn.SendPing(client_pack, client_sock);
+       SendPing();
 }
 
 Client::~Client() {
        if (conn.TimedOut()) {
                cout << "connection timed out :(" << endl;
        } else if (conn.ShouldPing()) {
-               conn.SendPing(client_pack, client_sock);
+               SendPing();
        }
 }
 
+void Client::SendPing() {
+       conn.SendPing(client_pack, client_sock);
+}
+
+void Client::SendLogin(const string &name) {
+       Packet &pack = *reinterpret_cast<Packet *>(client_pack.data);
+       client_pack.len = pack.Login(name);
+       conn.Send(client_pack, client_sock);
+}
+
 
 Connection::Connection(const IPaddress &addr)
 : addr(addr)
        return sizeof(Header);
 }
 
+size_t Packet::Login(const string &name) noexcept {
+       constexpr size_t maxname = 32;
+
+       Tag();
+       header.type = LOGIN;
+       if (name.size() < maxname) {
+               memset(payload, '\0', maxname);
+               memcpy(payload, name.c_str(), name.size());
+       } else {
+               memcpy(payload, name.c_str(), maxname);
+       }
+       return sizeof(Header) + maxname;
+}
+
 
 Server::Server(const Config &conf, World &world)
 : serv_sock(nullptr)
 
        Connection &client = GetClient(udp_pack.address);
        client.FlagRecv();
+
+       switch (pack.header.type) {
+               case Packet::PING:
+                       // already done all that's supposed to do
+                       break;
+               case Packet::LOGIN:
+                       HandleLogin(client, udp_pack);
+                       break;
+               default:
+                       // just drop packets of unknown type
+                       break;
+       }
 }
 
 Connection &Server::GetClient(const IPaddress &addr) {
        cout << "connection timeout from " << client.Address() << endl;
 }
 
+
+void Server::HandleLogin(Connection &client, const UDPpacket &udp_pack) {
+       const Packet &pack = *reinterpret_cast<const Packet *>(udp_pack.data);
+       size_t maxlen = min(udp_pack.len - int(sizeof(Packet::Header)), 32);
+       string name;
+       name.reserve(maxlen);
+       for (size_t i = 0; i < maxlen && pack.payload[i] != '\0'; ++i) {
+               name.push_back(pack.payload[i]);
+       }
+       cout << "got login request from player \"" << name << '"' << endl;
+
+       Entity *player = world.AddPlayer(name);
+       if (player) {
+               // success!
+               cout << "\taccepted" << endl;
+       } else {
+               // aw no :(
+               cout << "\trejected" << endl;
+       }
+}
+
 }