diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldContexts.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldContexts.java
index 30b85ce..5cf9f44 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldContexts.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldContexts.java
@@ -20,7 +20,9 @@ package ru.windcorp.progressia.common.world.generic.context;
 
 import glm.vec._3.i.Vec3i;
 import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.common.world.rels.AbsRelation;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
 import ru.windcorp.progressia.common.world.rels.RelFace;
 
 /**
@@ -81,6 +83,80 @@ class WorldContexts {
 		 */
 		Tile push(Vec3i location, RelFace face, int layer);
 
+		/**
+		 * Converts the provided location given in the context's coordinate
+		 * space to the underlying absolute coordinate space.
+		 * 
+		 * The definition of "absolute coordinate space" for contexts that are
+		 * not {@linkplain #isReal() real} may be arbitrary, but methods
+		 * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)},
+		 * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are
+		 * guaranteed to be consistent for all contexts.
+		 * 
+		 * @param contextLocation the location expressed in context coordinate
+		 *                        system
+		 * @param output          the {@link Vec3i} object to output the result
+		 *                        into, or {@code null}
+		 * @return The location expressed in absolute coordinate system. If
+		 *         {@code output} is not {@code null}, {@code output} is
+		 *         returned; otherwise, a new {@link Vec3i} is instantiated and
+		 *         returned
+		 */
+		Vec3i toAbsolute(Vec3i contextLocation, Vec3i output);
+
+		/**
+		 * Converts the provided location given in the absolute coordinate
+		 * space to the context coordinate space.
+		 * 
+		 * The definition of "absolute coordinate space" for contexts that are
+		 * not {@linkplain #isReal() real} may be arbitrary, but methods
+		 * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)},
+		 * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are
+		 * guaranteed to be consistent for all contexts.
+		 * 
+		 * @param contextLocation the location expressed in absolute coordinate
+		 *                        system
+		 * @param output          the {@link Vec3i} object to output the result
+		 *                        into, or {@code null}
+		 * @return The location expressed in context coordinate system. If
+		 *         {@code output} is not {@code null}, {@code output} is
+		 *         returned; otherwise, a new {@link Vec3i} is instantiated and
+		 *         returned
+		 */
+		Vec3i toContext(Vec3i absoluteLocation, Vec3i output);
+
+		/**
+		 * Converts the provided face given in the context's coordinate
+		 * space to the underlying absolute coordinate space.
+		 * 
+		 * The definition of "absolute coordinate space" for contexts that are
+		 * not {@linkplain #isReal() real} may be arbitrary, but methods
+		 * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)},
+		 * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are
+		 * guaranteed to be consistent for all contexts.
+		 * 
+		 * @param contextLocation the face expressed in context coordinate
+		 *                        system
+		 * @return the face expressed in absolute coordinate system
+		 */
+		AbsFace toAbsolute(BlockFace contextFace);
+
+		/**
+		 * Converts the provided face given in the absolute coordinate
+		 * space to the context coordinate space.
+		 * 
+		 * The definition of "absolute coordinate space" for contexts that are
+		 * not {@linkplain #isReal() real} may be arbitrary, but methods
+		 * {@link #toAbsolute(Vec3i, Vec3i)}, {@link #toAbsolute(BlockFace)},
+		 * {@link #toContext(Vec3i, Vec3i)} and {@link #toContext(AbsFace)} are
+		 * guaranteed to be consistent for all contexts.
+		 * 
+		 * @param contextLocation the face expressed in absolute coordinate
+		 *                        system
+		 * @return the face expressed in context coordinate system
+		 */
+		RelFace toContext(AbsFace absoluteFace);
+
 	}
 
 	/**
diff --git a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java
index 9be01e2..41cffef 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/tile/PacketAffectTile.java
@@ -51,6 +51,10 @@ public abstract class PacketAffectTile extends PacketAffectChunk {
 	}
 
 	public void set(Vec3i blockInWorld, AbsFace face, int tag) {
+		if (tag < 0) {
+			throw new IllegalArgumentException("Cannot affect tile with tag -1");
+		}
+		
 		this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z);
 		this.face = face;
 		this.tag = tag;
diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java
index 0efc551..915c262 100644
--- a/src/main/java/ru/windcorp/progressia/server/Server.java
+++ b/src/main/java/ru/windcorp/progressia/server/Server.java
@@ -24,20 +24,26 @@ import org.apache.logging.log4j.LogManager;
 
 import com.google.common.eventbus.EventBus;
 
+import glm.vec._3.i.Vec3i;
 import ru.windcorp.jputil.functions.ThrowingRunnable;
 import ru.windcorp.progressia.common.Units;
 import ru.windcorp.progressia.common.util.TaskQueue;
 import ru.windcorp.progressia.common.util.crash.ReportingEventBus;
 import ru.windcorp.progressia.common.world.DefaultWorldData;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.AxisRotations;
 import ru.windcorp.progressia.server.comms.ClientManager;
 import ru.windcorp.progressia.server.events.ServerEvent;
 import ru.windcorp.progressia.server.management.load.ChunkRequestDaemon;
 import ru.windcorp.progressia.server.management.load.EntityRequestDaemon;
 import ru.windcorp.progressia.server.management.load.LoadManager;
 import ru.windcorp.progressia.server.world.DefaultWorldLogic;
+import ru.windcorp.progressia.server.world.context.ServerBlockContext;
+import ru.windcorp.progressia.server.world.context.ServerTileContext;
 import ru.windcorp.progressia.server.world.context.ServerWorldContext;
 import ru.windcorp.progressia.server.world.context.impl.DefaultServerContext;
 import ru.windcorp.progressia.server.world.context.impl.ReportingServerContext;
+import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext;
 import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
 import ru.windcorp.progressia.server.world.ticking.Change;
 import ru.windcorp.progressia.server.world.ticking.Evaluation;
@@ -102,15 +108,54 @@ public class Server {
 
 	/**
 	 * Instantiates and returns an new {@link ServerWorldContext} instance
-	 * suitable for read and write access to the server's world. This is the
-	 * preferred way to query or change the world.
+	 * suitable for read and write access to the server's world. This context
+	 * uses the absolute coordinate space (not rotated to match positive Z =
+	 * up).
 	 * 
 	 * @return the context
+	 * @see #createContext(AbsFace)
 	 */
-	public ServerWorldContext createContext() {
+	public ServerWorldContext createAbsoluteContext() {
+		return doCreateAbsoluteContext();
+	}
 
-		return new ReportingServerContext(DefaultServerContext.empty().inRealWorldOf(this).build()).withListener(worldAccessor).setPassToParent(false);
-		
+	private ServerTileContext doCreateAbsoluteContext() {
+		return new ReportingServerContext(DefaultServerContext.empty().inRealWorldOf(this).build())
+			.withListener(worldAccessor).setPassToParent(false);
+	}
+
+	/**
+	 * Instantiates and returns an new {@link ServerWorldContext} instance
+	 * suitable for read and write access to the server's world. This is the
+	 * preferred way to query or change the world. This context uses the
+	 * coordinate space in which positive Z = {@code up}.
+	 * 
+	 * @param up the desired up direction
+	 * @return the context
+	 * @see #createContext(Vec3i)
+	 * @see #createAbsoluteContext()
+	 */
+	public ServerWorldContext createContext(AbsFace up) {
+		return new RotatingServerContext(doCreateAbsoluteContext(), up);
+	}
+
+	/**
+	 * Instantiates and returns an new {@link ServerBlockContext} instance
+	 * suitable for read and write access to the server's world. The context is
+	 * initialized to point to the provided block. This is the preferred way to
+	 * query or change the world. This context uses the coordinate space in
+	 * which positive Z matches the discrete up direction of the provided
+	 * location.
+	 * 
+	 * @param up the desired up direction
+	 * @return the context
+	 * @see #createContext(AbsFace)
+	 * @see #createAbsoluteContext()
+	 */
+	public ServerBlockContext createContext(Vec3i blockInWorld) {
+		AbsFace up = getWorld().getUp(blockInWorld);
+		Vec3i relativeBlockInWorld = AxisRotations.relativize(blockInWorld, up, null);
+		return new RotatingServerContext(doCreateAbsoluteContext(), up).push(relativeBlockInWorld);
 	}
 
 	/**
@@ -247,7 +292,7 @@ public class Server {
 //	public WorldAccessor getWorldAccessor() {
 //		return worldAccessor;
 //	}
-	
+
 	public WorldAccessor getWorldAccessor___really_bad_dont_use() {
 		return worldAccessor;
 	}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/DefaultChunkLogic.java b/src/main/java/ru/windcorp/progressia/server/world/DefaultChunkLogic.java
index 978fae3..d18ff11 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/DefaultChunkLogic.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/DefaultChunkLogic.java
@@ -28,17 +28,18 @@ import java.util.function.BiConsumer;
 
 import glm.vec._3.i.Vec3i;
 import ru.windcorp.progressia.common.world.DefaultChunkData;
-import ru.windcorp.progressia.common.world.Coordinates;
 import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.common.world.rels.BlockFace;
 import ru.windcorp.progressia.common.world.rels.RelFace;
 import ru.windcorp.progressia.common.world.TileDataStack;
+import ru.windcorp.progressia.common.world.generic.GenericChunks;
 import ru.windcorp.progressia.common.world.TileDataReference;
 import ru.windcorp.progressia.server.Server;
 import ru.windcorp.progressia.server.world.block.BlockLogic;
 import ru.windcorp.progressia.server.world.block.BlockLogicRegistry;
 import ru.windcorp.progressia.server.world.block.TickableBlock;
 import ru.windcorp.progressia.server.world.context.ServerBlockContextRO;
+import ru.windcorp.progressia.server.world.context.ServerContexts;
 import ru.windcorp.progressia.server.world.context.ServerTileContextRO;
 import ru.windcorp.progressia.server.world.context.ServerWorldContextRO;
 import ru.windcorp.progressia.server.world.tasks.TickChunk;
@@ -225,15 +226,12 @@ public class DefaultChunkLogic implements ChunkLogic {
 	}
 
 	private void tmp_generateTickLists() {
-		ServerWorldContextRO context = Server.getCurrentServer().createContext();
-		Vec3i blockInChunk = new Vec3i();
+		ServerWorldContextRO context = Server.getCurrentServer().createContext(getUp());
 		
-		forEachBiW(location -> {
+		GenericChunks.forEachBiC(blockInChunk -> {
 			
-			ServerBlockContextRO blockContext = context.push(location);
-			
-			BlockLogic block = blockContext.logic().getBlock();
-			Coordinates.convertInWorldToInChunk(location, blockInChunk);
+			ServerBlockContextRO blockContext = ServerContexts.pushAbs(context, this, blockInChunk);
+			BlockLogic block = getBlock(blockInChunk);
 
 			if (!(block instanceof TickableBlock)) {
 				blockContext.pop();
@@ -241,7 +239,7 @@ public class DefaultChunkLogic implements ChunkLogic {
 			}
 
 			if (((TickableBlock) block).getTickingPolicy(blockContext) == TickingPolicy.REGULAR) {
-				tickingBlocks.add(blockInChunk);
+				tickingBlocks.add(blockInChunk.add_(0));
 			}
 
 			for (RelFace face : RelFace.getFaces()) {
@@ -249,16 +247,14 @@ public class DefaultChunkLogic implements ChunkLogic {
 				if (stack == null || stack.isEmpty()) continue;
 				
 				for (int i = 0; i < stack.size(); ++i) {
-					ServerTileContextRO tileContext = blockContext.push(face, i);
+					ServerTileContextRO tileContext = blockContext.push(context.toContext(face.resolve(getUp())), i);
 					TileLogic tile = stack.get(i);
 
-					if (!(tile instanceof TickableTile)) {
-						tileContext.pop();
-						continue;
-					}
-
-					if (((TickableTile) tile).getTickingPolicy(tileContext) == TickingPolicy.REGULAR) {
-						tickingTiles.add(stack.getData().getReference(i));
+					if (tile instanceof TickableTile) {
+						TickingPolicy policy = ((TickableTile) tile).getTickingPolicy(tileContext);
+						if (policy == TickingPolicy.REGULAR) {
+							tickingTiles.add(stack.getData().getReference(i));
+						}
 					}
 
 					tileContext.pop();
diff --git a/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java
index 286a557..9e21c63 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java
@@ -21,12 +21,13 @@ package ru.windcorp.progressia.server.world;
 import glm.vec._3.i.Vec3i;
 import ru.windcorp.progressia.common.util.crash.CrashReports;
 import ru.windcorp.progressia.common.world.entity.EntityData;
-import ru.windcorp.progressia.common.world.rels.BlockFace;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.server.Server;
 import ru.windcorp.progressia.server.world.block.BlockLogic;
 import ru.windcorp.progressia.server.world.block.TickableBlock;
 import ru.windcorp.progressia.server.world.block.UpdateableBlock;
 import ru.windcorp.progressia.server.world.context.ServerBlockContext;
+import ru.windcorp.progressia.server.world.context.ServerContexts;
 import ru.windcorp.progressia.server.world.context.ServerTileContext;
 import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
 import ru.windcorp.progressia.server.world.context.ServerWorldContext;
@@ -56,7 +57,7 @@ public class TickAndUpdateUtil {
 		if (!(block instanceof TickableBlock)) {
 			return;
 		}
-		ServerBlockContext context = server.createContext().push(blockInWorld);
+		ServerBlockContext context = server.createContext(blockInWorld);
 		tickBlock(context);
 	}
 
@@ -73,23 +74,22 @@ public class TickAndUpdateUtil {
 		}
 	}
 
-	public static void tickTile(Server server, Vec3i blockInWorld, BlockFace face, int layer) {
+	public static void tickTile(Server server, Vec3i blockInWorld, AbsFace face, int layer) {
 		TileLogic tile = server.getWorld().getTile(blockInWorld, face, layer);
 		if (!(tile instanceof TickableTile)) {
 			return;
 		}
-		ServerTileContext context = server.createContext()
-			.push(blockInWorld, face.relativize(server.getWorld().getUp(blockInWorld)), layer);
+		ServerTileContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face)
+			.push(layer);
 		tickTile(context);
 	}
 
-	public static void tickTiles(Server server, Vec3i blockInWorld, BlockFace face) {
+	public static void tickTiles(Server server, Vec3i blockInWorld, AbsFace face) {
 		if (!server.getWorld().hasTiles(blockInWorld, face)) {
 			return;
 		}
 
-		ServerTileStackContext context = server.createContext()
-			.push(blockInWorld, face.relativize(server.getWorld().getUp(blockInWorld)));
+		ServerTileStackContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face);
 		for (int i = 0; i < context.getTileCount(); ++i) {
 			tickTile(context.push(i));
 			context.pop();
@@ -114,7 +114,7 @@ public class TickAndUpdateUtil {
 		if (!(block instanceof UpdateableBlock)) {
 			return;
 		}
-		ServerBlockContext context = server.createContext().push(blockInWorld);
+		ServerBlockContext context = server.createContext(blockInWorld);
 		updateBlock(context);
 	}
 
@@ -131,23 +131,22 @@ public class TickAndUpdateUtil {
 		}
 	}
 
-	public static void updateTile(Server server, Vec3i blockInWorld, BlockFace face, int layer) {
+	public static void updateTile(Server server, Vec3i blockInWorld, AbsFace face, int layer) {
 		TileLogic tile = server.getWorld().getTile(blockInWorld, face, layer);
 		if (!(tile instanceof UpdateableTile)) {
 			return;
 		}
-		ServerTileContext context = server.createContext()
-			.push(blockInWorld, face.relativize(server.getWorld().getUp(blockInWorld)), layer);
+		ServerTileContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face)
+			.push(layer);
 		updateTile(context);
 	}
 
-	public static void updateTiles(Server server, Vec3i blockInWorld, BlockFace face) {
+	public static void updateTiles(Server server, Vec3i blockInWorld, AbsFace face) {
 		if (!server.getWorld().hasTiles(blockInWorld, face)) {
 			return;
 		}
 
-		ServerTileStackContext context = server.createContext()
-			.push(blockInWorld, face.relativize(server.getWorld().getUp(blockInWorld)));
+		ServerTileStackContext context = ServerContexts.pushAbs(server.createContext(blockInWorld), blockInWorld, face);
 		for (int i = 0; i < context.getTileCount(); ++i) {
 			updateTile(context.push(i));
 			context.pop();
@@ -166,7 +165,7 @@ public class TickAndUpdateUtil {
 		tickEntity(
 			EntityLogicRegistry.getInstance().get(data.getId()),
 			data,
-			server.createContext()
+			server.createContext(data.getUpFace())
 		);
 	}
 
diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerContexts.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContexts.java
new file mode 100644
index 0000000..b0cf996
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContexts.java
@@ -0,0 +1,141 @@
+/*
+ * Progressia
+ * Copyright (C)  2020-2021  Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+package ru.windcorp.progressia.server.world.context;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.util.Vectors;
+import ru.windcorp.progressia.common.world.Coordinates;
+import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
+import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+
+public class ServerContexts {
+	
+	/*
+	 * RW
+	 */
+	
+	public static ServerBlockContext pushAbs(ServerWorldContext context, Vec3i blockInWorld) {
+		Vec3i contextLocation = Vectors.grab3i();
+		context.toContext(blockInWorld, contextLocation);
+		ServerBlockContext blockContext = context.push(contextLocation);
+		Vectors.release(contextLocation);
+		return blockContext;
+	}
+	
+	public static ServerBlockContext pushAbs(ServerWorldContext context, ChunkGenericRO, ?, ?, ?, ?> chunk, Vec3i blockInChunk) {
+		Vec3i contextLocation = Vectors.grab3i();
+		Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation);
+		context.toContext(contextLocation, contextLocation);
+		ServerBlockContext blockContext = context.push(contextLocation);
+		Vectors.release(contextLocation);
+		return blockContext;
+	}
+	
+	public static ServerTileStackContext pushAbs(ServerWorldContext context, Vec3i blockInWorld, AbsFace face) {
+		Vec3i contextLocation = Vectors.grab3i();
+		context.toContext(blockInWorld, contextLocation);
+		RelFace contextFace = context.toContext(face);
+		ServerTileStackContext tileStackContext = context.push(contextLocation, contextFace);
+		Vectors.release(contextLocation);
+		return tileStackContext;
+	}
+	
+	public static ServerTileStackContext pushAbs(ServerWorldContext context, ChunkGenericRO, ?, ?, ?, ?> chunk, Vec3i blockInChunk, AbsFace face) {
+		Vec3i contextLocation = Vectors.grab3i();
+		Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation);
+		context.toContext(contextLocation, contextLocation);
+		RelFace contextFace = context.toContext(face);
+		ServerTileStackContext tileStackContext = context.push(contextLocation, contextFace);
+		Vectors.release(contextLocation);
+		return tileStackContext;
+	}
+	
+	public static ServerTileStackContext pushAbs(ServerBlockContext context, AbsFace face) {
+		return context.push(context.toContext(face));
+	}
+	
+	public static ServerTileContext pushAbs(ServerWorldContext context, AbsFace up, TileGenericReferenceRO, ?, ?, ?, ?> ref) {
+		Vec3i contextLocation = Vectors.grab3i();
+		ref.getStack().getBlockInWorld(contextLocation);
+		context.toContext(contextLocation, contextLocation);
+		RelFace contextFace = context.toContext(ref.getStack().getFace().resolve(up));
+		ServerTileContext tileContext = context.push(contextLocation, contextFace, ref.getIndex());
+		Vectors.release(contextLocation);
+		return tileContext;
+	}
+	
+	/*
+	 * RO
+	 */
+	
+	public static ServerBlockContextRO pushAbs(ServerWorldContextRO context, Vec3i blockInWorld) {
+		Vec3i contextLocation = Vectors.grab3i();
+		context.toContext(blockInWorld, contextLocation);
+		ServerBlockContextRO blockContextRO = context.push(contextLocation);
+		Vectors.release(contextLocation);
+		return blockContextRO;
+	}
+	
+	public static ServerBlockContextRO pushAbs(ServerWorldContextRO context, ChunkGenericRO, ?, ?, ?, ?> chunk, Vec3i blockInChunk) {
+		Vec3i contextLocation = Vectors.grab3i();
+		Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation);
+		context.toContext(contextLocation, contextLocation);
+		ServerBlockContextRO blockContextRO = context.push(contextLocation);
+		Vectors.release(contextLocation);
+		return blockContextRO;
+	}
+	
+	public static ServerTileStackContextRO pushAbs(ServerWorldContextRO context, Vec3i blockInWorld, AbsFace face) {
+		Vec3i contextLocation = Vectors.grab3i();
+		context.toContext(blockInWorld, contextLocation);
+		RelFace contextFace = context.toContext(face);
+		ServerTileStackContextRO tileStackContextRO = context.push(contextLocation, contextFace);
+		Vectors.release(contextLocation);
+		return tileStackContextRO;
+	}
+	
+	public static ServerTileStackContextRO pushAbs(ServerWorldContextRO context, ChunkGenericRO, ?, ?, ?, ?> chunk, Vec3i blockInChunk, AbsFace face) {
+		Vec3i contextLocation = Vectors.grab3i();
+		Coordinates.getInWorld(chunk.getPosition(), blockInChunk, contextLocation);
+		context.toContext(contextLocation, contextLocation);
+		RelFace contextFace = context.toContext(face);
+		ServerTileStackContextRO tileStackContextRO = context.push(contextLocation, contextFace);
+		Vectors.release(contextLocation);
+		return tileStackContextRO;
+	}
+	
+	public static ServerTileStackContextRO pushAbs(ServerBlockContextRO context, AbsFace face) {
+		return context.push(context.toContext(face));
+	}
+	
+	public static ServerTileContextRO pushAbs(ServerWorldContextRO context, AbsFace up, TileGenericReferenceRO, ?, ?, ?, ?> ref) {
+		Vec3i contextLocation = Vectors.grab3i();
+		ref.getStack().getBlockInWorld(contextLocation);
+		context.toContext(contextLocation, contextLocation);
+		RelFace contextFace = context.toContext(ref.getStack().getFace().resolve(up));
+		ServerTileContextRO tileContextRO = context.push(contextLocation, contextFace, ref.getIndex());
+		Vectors.release(contextLocation);
+		return tileContextRO;
+	}
+
+	private ServerContexts() {
+	}
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextImpl.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextImpl.java
index ec89e47..aeda6e8 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextImpl.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextImpl.java
@@ -29,6 +29,7 @@ import ru.windcorp.progressia.common.world.WorldData;
 import ru.windcorp.progressia.common.world.block.BlockData;
 import ru.windcorp.progressia.common.world.entity.EntityData;
 import ru.windcorp.progressia.common.world.generic.EntityGeneric;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.common.world.rels.BlockFace;
 import ru.windcorp.progressia.common.world.rels.RelFace;
 import ru.windcorp.progressia.common.world.tile.TileData;
@@ -290,6 +291,41 @@ class DefaultServerContextImpl extends DefaultServerContext
 		assert requireContextRole(Role.TILE);
 		return frame.layer;
 	}
+	
+	/*
+	 * ABSOLUTE COORDINATE CONVERSIONS
+	 * (or lack thereof)
+	 */
+	
+	@Override
+	public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) {
+		if (output == null) {
+			output = new Vec3i();
+		}
+		
+		output.set(contextLocation.x, contextLocation.y, contextLocation.z);
+		return output;
+	}
+	
+	@Override
+	public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) {
+		if (output == null) {
+			output = new Vec3i();
+		}
+		
+		output.set(absoluteLocation.x, absoluteLocation.y, absoluteLocation.z);
+		return output;
+	}
+	
+	@Override
+	public AbsFace toAbsolute(BlockFace contextFace) {
+		return contextFace.resolve(AbsFace.POS_Z);
+	}
+	
+	@Override
+	public RelFace toContext(AbsFace absoluteFace) {
+		return absoluteFace.relativize(AbsFace.POS_Z);
+	}
 
 	/*
 	 * RO CONTEXT INTERFACE
diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextLogic.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextLogic.java
index 4461d23..640a475 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextLogic.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/DefaultServerContextLogic.java
@@ -23,6 +23,7 @@ import java.util.Random;
 import glm.vec._3.i.Vec3i;
 import ru.windcorp.progressia.common.world.block.BlockData;
 import ru.windcorp.progressia.common.world.entity.EntityData;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.common.world.rels.BlockFace;
 import ru.windcorp.progressia.common.world.rels.RelFace;
 import ru.windcorp.progressia.common.world.tile.TileData;
@@ -84,6 +85,26 @@ public class DefaultServerContextLogic implements ServerTileContext.Logic {
 		parent.push(location, face, layer);
 		return this;
 	}
+	
+	@Override
+	public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) {
+		return parent.toAbsolute(contextLocation, output);
+	}
+	
+	@Override
+	public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) {
+		return parent.toContext(absoluteLocation, output);
+	}
+	
+	@Override
+	public AbsFace toAbsolute(BlockFace contextFace) {
+		return parent.toAbsolute(contextFace);
+	}
+	
+	@Override
+	public RelFace toContext(AbsFace absoluteFace) {
+		return parent.toContext(absoluteFace);
+	}
 
 	@Override
 	public double getTickLength() {
diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/FilterServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/FilterServerContext.java
index b7b5ff5..677f9d3 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/context/impl/FilterServerContext.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/FilterServerContext.java
@@ -27,6 +27,7 @@ import ru.windcorp.progressia.common.world.GravityModel;
 import ru.windcorp.progressia.common.world.block.BlockData;
 import ru.windcorp.progressia.common.world.entity.EntityData;
 import ru.windcorp.progressia.common.world.generic.EntityGeneric;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.common.world.rels.BlockFace;
 import ru.windcorp.progressia.common.world.rels.RelFace;
 import ru.windcorp.progressia.common.world.tile.TileData;
@@ -199,6 +200,26 @@ public abstract class FilterServerContext implements ServerTileContext {
 		parent.push(location, face, layer);
 		return this;
 	}
+	
+	@Override
+	public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) {
+		return parent.toAbsolute(contextLocation, output);
+	}
+
+	@Override
+	public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) {
+		return parent.toContext(absoluteLocation, output);
+	}
+
+	@Override
+	public AbsFace toAbsolute(BlockFace contextFace) {
+		return parent.toAbsolute(contextFace);
+	}
+
+	@Override
+	public RelFace toContext(AbsFace absoluteFace) {
+		return parent.toContext(absoluteFace);
+	}
 
 	@Override
 	public Server getServer() {
diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/RotatingServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/RotatingServerContext.java
new file mode 100644
index 0000000..2ccc86c
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/RotatingServerContext.java
@@ -0,0 +1,54 @@
+/*
+ * Progressia
+ * Copyright (C)  2020-2021  Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+package ru.windcorp.progressia.server.world.context.impl;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+import ru.windcorp.progressia.server.world.context.ServerTileContext;
+
+public class RotatingServerContext extends TransformingServerContext {
+	
+	private final AbsFace up;
+
+	public RotatingServerContext(ServerTileContext parent, AbsFace up) {
+		super(parent);
+		this.up = up;
+	}
+
+	@Override
+	protected void transform(Vec3i userLocation, Vec3i output) {
+		output.set(userLocation.x, userLocation.y, userLocation.z);
+	}
+
+	@Override
+	protected void untransform(Vec3i parentLocation, Vec3i output) {
+		output.set(parentLocation.x, parentLocation.y, parentLocation.z);
+	}
+
+	@Override
+	protected RelFace transform(RelFace userFace) {
+		return userFace.resolve(up).relativize(AbsFace.POS_Z);
+	}
+
+	@Override
+	protected RelFace untransform(RelFace parentFace) {
+		return parentFace.resolve(AbsFace.POS_Z).relativize(up);
+	}
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/impl/TransformingServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/impl/TransformingServerContext.java
new file mode 100644
index 0000000..7305876
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/world/context/impl/TransformingServerContext.java
@@ -0,0 +1,273 @@
+/*
+ * Progressia
+ * Copyright (C)  2020-2021  Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+package ru.windcorp.progressia.server.world.context.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.block.BlockData;
+import ru.windcorp.progressia.common.world.rels.AbsFace;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+import ru.windcorp.progressia.common.world.tile.TileData;
+import ru.windcorp.progressia.server.world.context.ServerBlockContext;
+import ru.windcorp.progressia.server.world.context.ServerTileContext;
+import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
+
+public abstract class TransformingServerContext extends FilterServerContext {
+
+	private final Vec3i location = new Vec3i();
+	private boolean isLocationValid = false;
+
+	private RelFace face;
+
+	private final List vectorCache = new ArrayList<>(1);
+
+	public TransformingServerContext(ServerTileContext parent) {
+		super(parent);
+	}
+
+	protected abstract void transform(Vec3i userLocation, Vec3i output);
+
+	protected abstract void untransform(Vec3i parentLocation, Vec3i output);
+
+	protected abstract RelFace transform(RelFace userFace);
+
+	protected abstract RelFace untransform(RelFace parentFace);
+
+	protected void invalidateCache() {
+		isLocationValid = false;
+		face = null;
+	}
+
+	private Vec3i grabVector(Vec3i userVector) {
+		Vec3i parentVector;
+
+		if (vectorCache.isEmpty()) {
+			parentVector = new Vec3i();
+		} else {
+			parentVector = vectorCache.remove(vectorCache.size() - 1);
+		}
+
+		transform(userVector, parentVector);
+
+		return parentVector;
+	}
+
+	private void releaseVector(Vec3i parentVector) {
+		vectorCache.add(parentVector);
+	}
+
+	@Override
+	public Vec3i getLocation() {
+		// Always invoke parent method to allow parent to determine validity
+		Vec3i parentLocation = super.getLocation();
+
+		if (!isLocationValid) {
+			untransform(parentLocation, location);
+			isLocationValid = true;
+		}
+
+		return location;
+	}
+
+	@Override
+	public RelFace getFace() {
+		// Always invoke parent method to allow parent to determine validity
+		RelFace parentFace = super.getFace();
+
+		if (face == null) {
+			face = untransform(parentFace);
+		}
+
+		return face;
+	}
+
+	@Override
+	public void pop() {
+		super.pop();
+		invalidateCache();
+	}
+
+	@Override
+	public ServerBlockContext push(Vec3i userLocation) {
+		transform(userLocation, location);
+		isLocationValid = true;
+		super.push(location);
+		face = null;
+		return this;
+	}
+
+	@Override
+	public ServerTileStackContext push(Vec3i userLocation, RelFace userFace) {
+		transform(userLocation, location);
+		isLocationValid = true;
+		face = transform(userFace);
+		super.push(location, face);
+		return this;
+	}
+
+	@Override
+	public ServerTileContext push(Vec3i userLocation, RelFace userFace, int layer) {
+		transform(userLocation, location);
+		isLocationValid = true;
+		face = transform(userFace);
+		super.push(location, face, layer);
+		return this;
+	}
+	
+	@Override
+	public Vec3i toAbsolute(Vec3i contextLocation, Vec3i output) {
+		if (output == null) {
+			output = new Vec3i();
+		}
+		
+		transform(contextLocation, output);
+		
+		return super.toAbsolute(output, output);
+	}
+	
+	@Override
+	public Vec3i toContext(Vec3i absoluteLocation, Vec3i output) {
+		output = super.toContext(absoluteLocation, output);
+		untransform(output, output);
+		return output;
+	}
+	
+	@Override
+	public AbsFace toAbsolute(BlockFace contextFace) {
+		return super.toAbsolute(transform(contextFace.relativize(AbsFace.POS_Z)));
+	}
+	
+	@Override
+	public RelFace toContext(AbsFace absoluteFace) {
+		return untransform(super.toContext(absoluteFace));
+	}
+	
+	@Override
+	public boolean isLocationLoaded(Vec3i userLocation) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			return super.isLocationLoaded(parentLocation);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public BlockData getBlock(Vec3i userLocation) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			return super.getBlock(parentLocation);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public void setBlock(Vec3i userLocation, BlockData block) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			super.setBlock(parentLocation, block);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public boolean hasTile(Vec3i userLocation, BlockFace userFace, int layer) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			return super.hasTile(parentLocation, transform(userFace.relativize(AbsFace.POS_Z)), layer);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public TileData getTile(Vec3i userLocation, BlockFace userFace, int layer) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			return super.getTile(parentLocation, transform(userFace.relativize(AbsFace.POS_Z)), layer);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public boolean isTagValid(Vec3i userLocation, BlockFace userFace, int tag) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			return super.isTagValid(parentLocation, transform(userFace.relativize(AbsFace.POS_Z)), tag);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public TileData getTileByTag(Vec3i userLocation, BlockFace userFace, int tag) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			return super.getTileByTag(parentLocation, transform(userFace.relativize(AbsFace.POS_Z)), tag);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public int getTileCount(Vec3i userLocation, BlockFace userFace) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			return super.getTileCount(parentLocation, transform(userFace.relativize(AbsFace.POS_Z)));
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public void addTile(Vec3i userLocation, BlockFace userFace, TileData tile) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			super.addTile(parentLocation, transform(userFace.relativize(AbsFace.POS_Z)), tile);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+	@Override
+	public void removeTile(Vec3i userLocation, BlockFace userFace, int tag) {
+		Vec3i parentLocation = grabVector(userLocation);
+
+		try {
+			super.removeTile(parentLocation, transform(userFace.relativize(AbsFace.POS_Z)), tag);
+		} finally {
+			releaseVector(parentLocation);
+		}
+	}
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java
index 4ed21f1..a2a59f9 100644
--- a/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java
+++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java
@@ -27,17 +27,15 @@ import com.google.common.collect.ImmutableList;
 
 import glm.vec._3.i.Vec3i;
 import ru.windcorp.progressia.common.util.FloatMathUtil;
-import ru.windcorp.progressia.common.util.Vectors;
-import ru.windcorp.progressia.common.world.Coordinates;
 import ru.windcorp.progressia.common.world.DefaultChunkData;
 import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.common.world.TileDataStack;
-import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
 import ru.windcorp.progressia.server.Server;
 import ru.windcorp.progressia.server.world.DefaultChunkLogic;
 import ru.windcorp.progressia.server.world.block.BlockLogic;
 import ru.windcorp.progressia.server.world.block.TickableBlock;
 import ru.windcorp.progressia.server.world.context.ServerBlockContext;
+import ru.windcorp.progressia.server.world.context.ServerContexts;
 import ru.windcorp.progressia.server.world.context.ServerTileContext;
 import ru.windcorp.progressia.server.world.context.ServerTileStackContext;
 import ru.windcorp.progressia.server.world.context.ServerWorldContext;
@@ -53,20 +51,21 @@ public class TickChunk extends Evaluation {
 		DefaultChunkData.BLOCKS_PER_CHUNK *
 		DefaultChunkData.BLOCKS_PER_CHUNK;
 
-	private final List> randomTickMethods;
+	private final List> randomTickMethods;
 
 	{
-		List> randomTickMethods = new ArrayList<>();
+		List> randomTickMethods = new ArrayList<>();
 		randomTickMethods.add(this::tickRandomBlock);
 
 		for (AbsFace face : AbsFace.getFaces()) {
-			randomTickMethods.add(s -> this.tickRandomTile(s, face));
+			randomTickMethods.add(context -> this.tickRandomTile(face, context));
 		}
 
 		this.randomTickMethods = ImmutableList.copyOf(randomTickMethods);
 	}
 
 	private final DefaultChunkLogic chunk;
+	private ServerWorldContext context;
 
 	public TickChunk(DefaultChunkLogic chunk) {
 		this.chunk = chunk;
@@ -74,44 +73,41 @@ public class TickChunk extends Evaluation {
 
 	@Override
 	public void evaluate(Server server) {
-		tickRegulars(server);
-		tickRandom(server);
+		if (context == null || context.getServer() != server) {
+			context = server.createContext(chunk.getUp());
+		}
+		
+		tickRegulars(context);
+		tickRandom(context);
 	}
 
-	private void tickRegulars(Server server) {
-		tickRegularBlocks(server);
-		tickRegularTiles(server);
+	private void tickRegulars(ServerWorldContext context) {
+		tickRegularBlocks(context);
+		tickRegularTiles(context);
 	}
 
-	private void tickRegularBlocks(Server server) {
+	private void tickRegularBlocks(ServerWorldContext context) {
 		if (!chunk.hasTickingBlocks())
 			return;
 
-		ServerWorldContext context = server.createContext();
-
 		chunk.forEachTickingBlock((blockInChunk, block) -> {
-			((TickableBlock) block).tick(contextPushBiC(context, chunk, blockInChunk));
+			((TickableBlock) block).tick(ServerContexts.pushAbs(context, chunk, blockInChunk));
 			context.pop();
 		});
 	}
 
-	private void tickRegularTiles(Server server) {
+	private void tickRegularTiles(ServerWorldContext context) {
 		if (!chunk.hasTickingTiles())
 			return;
 
-		ServerWorldContext context = server.createContext();
-		Vec3i blockInWorld = new Vec3i();
-
 		chunk.forEachTickingTile((ref, tile) -> {
-			((TickableTile) tile).tick(
-				context.push(ref.getStack().getBlockInWorld(blockInWorld), ref.getStack().getFace(), ref.getIndex())
-			);
+			((TickableTile) tile).tick(ServerContexts.pushAbs(context, chunk.getUp(), ref));
 			context.pop();
 		});
 	}
 
-	private void tickRandom(Server server) {
-		float ticks = computeRandomTicks(server);
+	private void tickRandom(ServerWorldContext context) {
+		float ticks = computeRandomTicks(context.getServer());
 
 		/*
 		 * If we are expected to run 3.25 random ticks per tick
@@ -122,23 +118,23 @@ public class TickChunk extends Evaluation {
 		float extraTickChance = ticks - unconditionalTicks;
 
 		for (int i = 0; i < unconditionalTicks; ++i) {
-			tickRandomOnce(server);
+			tickRandomOnce(context);
 		}
 
-		if (server.getAdHocRandom().nextFloat() < extraTickChance) {
-			tickRandomOnce(server);
+		if (context.getRandom().nextFloat() < extraTickChance) {
+			tickRandomOnce(context);
 		}
 	}
 
-	private void tickRandomOnce(Server server) {
+	private void tickRandomOnce(ServerWorldContext context) {
 		// Pick a target at random: a block or one of 3 primary block faces
 		randomTickMethods.get(
-			server.getAdHocRandom().nextInt(randomTickMethods.size())
-		).accept(server);
+			context.getRandom().nextInt(randomTickMethods.size())
+		).accept(context);
 	}
 
-	private void tickRandomBlock(Server server) {
-		Random random = server.getAdHocRandom();
+	private void tickRandomBlock(ServerWorldContext context) {
+		Random random = context.getRandom();
 
 		Vec3i blockInChunk = new Vec3i(
 			random.nextInt(BLOCKS_PER_CHUNK),
@@ -152,15 +148,17 @@ public class TickChunk extends Evaluation {
 			return;
 		TickableBlock tickable = (TickableBlock) block;
 
-		ServerBlockContext context = contextPushBiC(server.createContext(), chunk, blockInChunk);
+		ServerBlockContext blockContext = ServerContexts.pushAbs(context, chunk, blockInChunk);
 
-		if (tickable.getTickingPolicy(context) != TickingPolicy.RANDOM)
+		if (tickable.getTickingPolicy(blockContext) != TickingPolicy.RANDOM)
 			return;
-		tickable.tick(context);
+		tickable.tick(blockContext);
+		
+		blockContext.pop();
 	}
 
-	private void tickRandomTile(Server server, AbsFace face) {
-		Random random = server.getAdHocRandom();
+	private void tickRandomTile(AbsFace face, ServerWorldContext context) {
+		Random random = context.getRandom();
 
 		Vec3i blockInChunk = new Vec3i(
 			random.nextInt(BLOCKS_PER_CHUNK),
@@ -172,10 +170,10 @@ public class TickChunk extends Evaluation {
 		if (tiles == null || tiles.isEmpty())
 			return;
 
-		ServerTileStackContext context = contextPushBiC(server.createContext(), chunk, blockInChunk).push(face.relativize(chunk.getUp()));
+		ServerTileStackContext tsContext = ServerContexts.pushAbs(context, chunk, blockInChunk, face);
 
 		for (int i = 0; i < tiles.size(); ++i) {
-			ServerTileContext tileContext = context.push(i);
+			ServerTileContext tileContext = tsContext.push(i);
 			
 			TileLogic logic = tileContext.logic().getTile();
 			if (!(logic instanceof TickableTile)) {
@@ -192,6 +190,8 @@ public class TickChunk extends Evaluation {
 			
 			tileContext.pop();
 		}
+		
+		tsContext.pop();
 	}
 
 	private float computeRandomTicks(Server server) {
@@ -200,18 +200,6 @@ public class TickChunk extends Evaluation {
 			server.getTickLength());
 	}
 
-	private ServerBlockContext contextPushBiC(
-		ServerWorldContext context,
-		ChunkGenericRO, ?, ?, ?, ?> chunk,
-		Vec3i blockInChunk
-	) {
-		Vec3i blockInWorld = Vectors.grab3i();
-		Coordinates.getInWorld(chunk.getPosition(), blockInChunk, blockInWorld);
-		ServerBlockContext blockContext = context.push(blockInWorld);
-		Vectors.release(blockInWorld);
-		return blockContext;
-	}
-
 	@Override
 	public void getRelevantChunk(Vec3i output) {
 		Vec3i p = chunk.getData().getPosition();
diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java
index 0ddc3e5..7b9122f 100644
--- a/src/main/java/ru/windcorp/progressia/test/TestContent.java
+++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java
@@ -383,7 +383,7 @@ public class TestContent {
 		ru.windcorp.progressia.server.comms.Client client
 	) {
 		Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld();
-		server.createContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
+		server.createAbsoluteContext().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air"));
 	}
 
 	private static void onBlockPlaceTrigger(ControlData control) {
@@ -403,7 +403,7 @@ public class TestContent {
 		Vec3i blockInWorld = controlData.getBlockInWorld();
 		if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null)
 			return;
-		server.createContext().setBlock(blockInWorld, block);
+		server.createAbsoluteContext().setBlock(blockInWorld, block);
 	}
 
 	private static void onTilePlaceTrigger(ControlData control) {
@@ -428,7 +428,7 @@ public class TestContent {
 			return;
 		if (server.getWorld().getData().getTiles(blockInWorld, face).isFull())
 			return;
-		server.createContext().addTile(blockInWorld, face, tile);
+		server.createAbsoluteContext().addTile(blockInWorld, face, tile);
 	}
 
 	private static void registerMisc() {
diff --git a/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java b/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java
index bfe438f..c197ce1 100644
--- a/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java
+++ b/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java
@@ -18,6 +18,7 @@
  
 package ru.windcorp.progressia.test;
 
+import ru.windcorp.progressia.common.world.rels.AbsFace;
 import ru.windcorp.progressia.common.world.rels.RelFace;
 import ru.windcorp.progressia.server.world.block.BlockLogic;
 import ru.windcorp.progressia.server.world.context.ServerTileContext;
@@ -65,7 +66,7 @@ public class TestTileLogicGrass extends HangingTileLogic implements TickableTile
 
 	private boolean isBlockAboveTransparent(ServerTileContextRO context) {
 		// TODO rework
-		context.pushRelative(RelFace.UP.resolve(context.getServer().getWorld().getUp(context.getLocation())));
+		context.pushRelative(RelFace.UP.resolve(AbsFace.POS_Z));
 		BlockLogic block = context.logic().getBlock();
 		return context.popAndReturn(block == null || block.isTransparent(context));
 	}