diff --git a/src/main/java/ru/windcorp/progressia/common/world/context/Context.java b/src/main/java/ru/windcorp/progressia/common/world/context/Context.java
new file mode 100644
index 0000000..bfbd164
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/context/Context.java
@@ -0,0 +1,105 @@
+/*
+ * 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.common.world.context;
+
+/**
+ * A cursor-like object for retrieving information about an in-game environment.
+ * A context object typically holds a reference to some sort of data structure
+ * and a cursor pointing to a location in that data structure. The exact meaning
+ * of "environment" and "location" is defined by extending interfaces.
+ *
+ * Context objects are intended to be the primary way of interacting for in-game
+ * content. Wherever possible, context objects should be preferred over other
+ * means of accessing game structures.
+ *
Context Validity
+ * Context objects may only be used while they are valid to avoid undefined
+ * behavior. There exists no programmatic way to determine a context's validity;
+ * it is the responsibility of the programmer to avoid interacting with invalid
+ * contexts.
+ *
+ * Contexts are usually acquired as method parameters. Unless stated otherwise,
+ * the context is valid until the invoked method returns; the only exception to
+ * this rule is subcontexting (see below). Consequently, contexts should never
+ * be stored outside their intended methods.
+ *
+ * In practice, context objects are typically highly volatile. They are not
+ * thread-safe and are often pooled and reused.
+ *
+ *
Subcontexting
+ * Subcontexting is the invocation of user-provided code with a context
+ * object derived from an existing one. For example, block context provides a
+ * convenience method for referencing the block's neighbor:
+ *
+ *
+ * blockContextA.forNeighbor(RelFace.UP, blockContextB -> {
+ * foo(blockContextA); // undefined behavior!
+ * foo(blockContextB); // correct
+ * });
+ *
+ *
+ * In this example, {@code forNeighbor} is a subcontexting method,
+ * {@code blockContextA} is the parent context, {@code blockContextB} is the
+ * subcontext, and the lambda is the context consumer.
+ *
+ * Parent contexts are invalid while the subcontexting method is
+ * running. Referencing {@code blockContextA} from inside the lambda
+ * creates undefined behavior.
+ *
+ * This restriction exists because some implementations of contexts may
+ * implement subcontexting by simply modifying the parent context for the
+ * duration of the call and presenting the temporarily modified parent context
+ * as the subcontext:
+ *
+ *
+ * public void forNeighbor(BlockFace face, Consumer<BlockContext> action) {
+ * this.position.add(face);
+ * action.accept(this);
+ * this.position.sub(face);
+ * }
+ *
+ */
+public interface Context {
+
+ /**
+ * Tests whether the environment is "real". Any actions carried out in an
+ * environment that is not "real" should not have any side effects outside
+ * of the environment.
+ *
+ * A typical "real" environment is the world of the client that is actually
+ * displayed or a world of the server that the clients actually interact
+ * with. An example of a non-"real" environment is a fake world used by
+ * developer tools to query the properties or behaviors of in-game content.
+ * While in-game events may well trigger global-scope actions, such as
+ * writing files, this may become an unintended or even harmful byproduct in
+ * some scenarios that are not actually linked to an actual in-game world.
+ *
+ * This flag should generally only be consulted before taking action through
+ * means other than a provided changer object. The interactions with the
+ * context should otherwise remain unaltered.
+ *
+ * When querying game content for purposes other than directly applying
+ * results in-game, {@code isReal()} should return {@code false}. In all
+ * other cases, where possible, the call should be delegated to a provided
+ * context object.
+ *
+ * @return {@code false} iff side effects outside the environment should be
+ * suppressed
+ */
+ boolean isReal();
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROBlockContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROBlockContext.java
new file mode 100644
index 0000000..4411407
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROBlockContext.java
@@ -0,0 +1,119 @@
+/*
+ * 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.common.world.generic.context;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.GenericBlock;
+import ru.windcorp.progressia.common.world.generic.GenericROChunk;
+import ru.windcorp.progressia.common.world.generic.GenericEntity;
+import ru.windcorp.progressia.common.world.generic.GenericTile;
+import ru.windcorp.progressia.common.world.generic.GenericROTileReference;
+import ru.windcorp.progressia.common.world.generic.GenericROTileStack;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+
+/**
+ * A {@link Context} referencing a world with a block location specified. The
+ * location may or may not be loaded.
+ */
+//@formatter:off
+public interface GenericROBlockContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericROTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericROChunk ,
+ E extends GenericEntity
+> extends GenericROWorldContext {
+//@formatter:on
+
+ /**
+ * Returns the location of the block.
+ *
+ * The coordinate system in use is not specified, but it is consistent
+ * across all methods of this context.
+ *
+ * The object returned by this method must not be modified. It is only valid
+ * while the context is {@linkplain valid}.
+ *
+ * @return a vector describing the block's position
+ */
+ Vec3i getLocation();
+
+ /**
+ * Determines whether the location relevant to this context is currently
+ * loaded.
+ *
+ * @return {@code true} iff the location is loaded
+ */
+ default boolean isLoaded() {
+ return isBlockLoaded(getLocation());
+ }
+
+ /**
+ * Gets the block relevant in this context.
+ *
+ * @return the block or {@code null} if the location is not loaded
+ */
+ default B getBlock() {
+ return getBlock(getLocation());
+ }
+
+ /**
+ * Gets the tile stack at the specified position; block location is implied
+ * by the context.
+ *
+ * @return the specified tile stack or {@code null} if the location is not
+ * loaded or the tile stack does not exist
+ */
+ default TS getTilesOrNull(BlockFace face) {
+ return getTilesOrNull(getLocation(), face);
+ }
+
+ /**
+ * Determines whether the location relevant to this context has a tile stack
+ * at the specified side.
+ *
+ * @return {@code true} iff the tile stack exists
+ */
+ default boolean hasTiles(BlockFace face) {
+ return hasTiles(getLocation(), face);
+ }
+
+ /**
+ * Determines whether the specified position has a tile; block location is
+ * implied by the context.
+ *
+ * @return {@code true} iff the tile exists
+ */
+ default boolean hasTile(BlockFace face, int layer) {
+ return hasTile(getLocation(), face, layer);
+ }
+
+ /**
+ * Gets the tile at the specified position; block location is implied by the
+ * context.
+ *
+ * @return the specified tile or {@code null} if the location is not loaded
+ * or the tile does not exist
+ */
+ default T getTile(BlockFace face, int layer) {
+ return getTile(getLocation(), face, layer);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROBlockFaceContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROBlockFaceContext.java
new file mode 100644
index 0000000..430a4a3
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROBlockFaceContext.java
@@ -0,0 +1,93 @@
+/*
+ * 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.common.world.generic.context;
+
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.GenericBlock;
+import ru.windcorp.progressia.common.world.generic.GenericROChunk;
+import ru.windcorp.progressia.common.world.generic.GenericEntity;
+import ru.windcorp.progressia.common.world.generic.GenericTile;
+import ru.windcorp.progressia.common.world.generic.GenericROTileReference;
+import ru.windcorp.progressia.common.world.generic.GenericROTileStack;
+import ru.windcorp.progressia.common.world.rels.RelFace;
+
+/**
+ * A {@link Context} referencing a world with a block location and a block face
+ * specified, effectively pointing to a tile stack. The tile stack may or may
+ * not actually exist.
+ */
+//@formatter:off
+public interface GenericROBlockFaceContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericROTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericROChunk ,
+ E extends GenericEntity
+> extends GenericROBlockContext {
+//@formatter:on
+
+ /**
+ * Returns the face relevant to this context.
+ *
+ * @return the block face
+ */
+ RelFace getFace();
+
+ /**
+ * Gets the tile stack at the relevant position.
+ *
+ * @return the specified tile stack or {@code null} if the location is not
+ * loaded or the tile stack does not exist
+ */
+ default TS getTilesOrNull() {
+ return getTilesOrNull(getLocation(), getFace());
+ }
+
+ /**
+ * Determines whether the location relevant to this context has a tile
+ * stack.
+ *
+ * @return {@code true} iff the tile stack exists
+ */
+ default boolean hasTiles() {
+ return hasTiles(getLocation(), getFace());
+ }
+
+ /**
+ * Determines whether the specified position has a tile; block location and
+ * face are implied by the context.
+ *
+ * @return {@code true} iff the tile exists
+ */
+ default boolean hasTile(int layer) {
+ return hasTile(getLocation(), getFace(), layer);
+ }
+
+ /**
+ * Gets the tile at the specified position; block location and face are
+ * implied by the context.
+ *
+ * @return the specified tile or {@code null} if the location is not loaded
+ * or the tile does not exist
+ */
+ default T getTile(int layer) {
+ return getTile(getLocation(), getFace(), layer);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROTileContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROTileContext.java
new file mode 100644
index 0000000..0ebc768
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROTileContext.java
@@ -0,0 +1,85 @@
+/*
+ * 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.common.world.generic.context;
+
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.GenericBlock;
+import ru.windcorp.progressia.common.world.generic.GenericROChunk;
+import ru.windcorp.progressia.common.world.generic.GenericEntity;
+import ru.windcorp.progressia.common.world.generic.GenericTile;
+import ru.windcorp.progressia.common.world.generic.GenericROTileReference;
+import ru.windcorp.progressia.common.world.generic.GenericROTileStack;
+
+/**
+ * A {@link Context} referencing a world with a block location, a block face and
+ * a tile layer specified, effectively pointing to a single tile. The tile may
+ * or may not actually exist.
+ */
+//@formatter:off
+public interface GenericROTileContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericROTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericROChunk ,
+ E extends GenericEntity
+> extends GenericROBlockFaceContext {
+//@formatter:on
+
+ /**
+ * Returns the tile layer relevant to this context.
+ *
+ * @return the tile layer
+ */
+ int getLayer();
+
+ /**
+ * Determines whether the location relevant to this context has a tile.
+ *
+ * @return {@code true} iff the tile exists
+ */
+ default boolean hasTile() {
+ return hasTile(getLocation(), getFace(), getLayer());
+ }
+
+ /**
+ * Gets the tile at the relevant position.
+ *
+ * @return the specified tile or {@code null} if the location is not loaded
+ * or the tile does not exist
+ */
+ default T getTile() {
+ return getTile(getLocation(), getFace(), getLayer());
+ }
+
+ /**
+ * Gets the tag of the tile at the relevant position.
+ *
+ * @return the tag of the tile or {@code -1} if the location is not loaded
+ * or the tile does not exist
+ */
+ default int getTag() {
+ TS tileStack = getTilesOrNull();
+ if (tileStack == null) {
+ return -1;
+ }
+
+ return tileStack.getTagByIndex(getLayer());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROWorldContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROWorldContext.java
new file mode 100644
index 0000000..09629da
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericROWorldContext.java
@@ -0,0 +1,45 @@
+/*
+ * 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.common.world.generic.context;
+
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.GenericBlock;
+import ru.windcorp.progressia.common.world.generic.GenericROChunk;
+import ru.windcorp.progressia.common.world.generic.GenericEntity;
+import ru.windcorp.progressia.common.world.generic.GenericTile;
+import ru.windcorp.progressia.common.world.generic.GenericROTileReference;
+import ru.windcorp.progressia.common.world.generic.GenericROTileStack;
+import ru.windcorp.progressia.common.world.generic.GenericROWorld;
+
+/**
+ * A {@link Context} with a world instance.
+ */
+// @formatter:off
+public interface GenericROWorldContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericROTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericROChunk ,
+ E extends GenericEntity
+> extends Context, GenericROWorld {
+// @formatter:on
+
+ // currently empty
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWBlockContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWBlockContext.java
new file mode 100644
index 0000000..23fb6fe
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWBlockContext.java
@@ -0,0 +1,92 @@
+/*
+ * 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.common.world.generic.context;
+
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.*;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+
+/**
+ * A writable {@link Context} referencing a world with a block location
+ * specified. This context provides methods for affecting the world. The
+ * application of requested changes may or may not be immediate, see
+ * {@link #isImmediate()}. The location may or may not be loaded.
+ */
+//@formatter:off
+public interface GenericRWBlockContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericRWTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericRWChunk ,
+ E extends GenericEntity
+> extends GenericRWWorldContext, GenericROBlockContext {
+//@formatter:on
+
+ /**
+ * Requests that a block is changed. The object provided may be stored until
+ * the change is applied. The location of the block is implied by the
+ * context.
+ *
+ * @param block the new block
+ * @see #isImmediate()
+ */
+ default void setBlock(B block) {
+ setBlock(getLocation(), block);
+ }
+
+ /**
+ * Requests that a tile is added to the top of the tile stack at the given
+ * location. The object provided may be stored until the change is applied.
+ * If the tile could not be added at the time of application this method
+ * fails silently. The location of the block is implied by the context.
+ *
+ * @param face the face of the block to add the tile to
+ * @param tile the tile to add
+ */
+ default void addTile(BlockFace face, T tile) {
+ addTile(getLocation(), face, tile);
+ }
+
+ /**
+ * Requests that a tile identified by its tag is removed from the specified
+ * tile stack. If the tile could not be found at the time of application
+ * this method fails silently. The location of the block is implied by the
+ * context.
+ *
+ * @param face the of the block to remove the tile from
+ * @param tag the tag of the tile to remove
+ */
+ default void removeTile(BlockFace face, int tag) {
+ removeTile(getLocation(), face, tag);
+ }
+
+ /**
+ * Requests that the referenced tile is removed from the specified tile
+ * stack. If the tile could not be found at the time of application this
+ * method fails silently. The location of the block is implied by the
+ * context.
+ *
+ * @param face the of the block to remove the tile from
+ * @param tileReference a reference to the tile
+ */
+ default void removeTile(BlockFace face, TR tileReference) {
+ removeTile(getLocation(), face, tileReference.getTag());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWBlockFaceContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWBlockFaceContext.java
new file mode 100644
index 0000000..b2152f8
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWBlockFaceContext.java
@@ -0,0 +1,78 @@
+/*
+ * 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.common.world.generic.context;
+
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.*;
+
+/**
+ * A writable {@link Context} referencing a world with a block location and a
+ * block face specified, effectively pointing to a tile stack. This context
+ * provides methods for affecting the world. The application of requested
+ * changes may or may not be immediate, see {@link #isImmediate()}. The tile
+ * stack may or may not actually exist.
+ */
+//@formatter:off
+public interface GenericRWBlockFaceContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericRWTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericRWChunk ,
+ E extends GenericEntity
+> extends GenericRWBlockContext, GenericROBlockFaceContext {
+//@formatter:on
+
+ /**
+ * Requests that a tile is added to the top of the tile stack at the given
+ * location. The object provided may be stored until the change is applied.
+ * If the tile could not be added at the time of application this method
+ * fails silently. The location and the face of the block are implied by the
+ * context.
+ *
+ * @param tile the tile to add
+ */
+ default void addTile(T tile) {
+ addTile(getLocation(), getFace(), tile);
+ }
+
+ /**
+ * Requests that a tile identified by its tag is removed from the specified
+ * tile stack. If the tile could not be found at the time of application
+ * this method fails silently. The location and the face of the block are
+ * implied by the context.
+ *
+ * @param tag the tag of the tile to remove
+ */
+ default void removeTile(int tag) {
+ removeTile(getLocation(), getFace(), tag);
+ }
+
+ /**
+ * Requests that the referenced tile is removed from the specified tile
+ * stack. If the tile could not be found at the time of application this
+ * method fails silently. The location and the face of the block are implied
+ * by the context.
+ *
+ * @param tileReference a reference to the tile
+ */
+ default void removeTile(TR tileReference) {
+ removeTile(getLocation(), getFace(), tileReference.getTag());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWTileContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWTileContext.java
new file mode 100644
index 0000000..c7c0d5c
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWTileContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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.common.world.generic.context;
+
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.*;
+
+/**
+ * A writable {@link Context} referencing a world with a block location, a block
+ * face and a tile layer specified, effectively pointing to a single tile. This
+ * context provides methods for affecting the world. The application of
+ * requested changes may or may not be immediate, see {@link #isImmediate()}.
+ * The tile may or may not actually exist.
+ */
+//@formatter:off
+public interface GenericRWTileContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericRWTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericRWChunk ,
+ E extends GenericEntity
+> extends GenericRWBlockFaceContext, GenericROTileContext {
+//@formatter:on
+
+ /**
+ * Requests that the tile relevant to this context be removed from its tile
+ * stack. If the tile could not be found at the time of application this
+ * method fails silently.
+ */
+ default void removeTile() {
+ removeTile(getLocation(), getFace(), getTag());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWWorldContext.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWWorldContext.java
new file mode 100644
index 0000000..55d83ff
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/GenericRWWorldContext.java
@@ -0,0 +1,151 @@
+/*
+ * 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.common.world.generic.context;
+
+import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.state.StateChange;
+import ru.windcorp.progressia.common.state.StatefulObject;
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.common.world.generic.*;
+import ru.windcorp.progressia.common.world.rels.BlockFace;
+
+/**
+ * A writable {@link Context} with a world instance. This context provides
+ * methods for affecting the world. The application of requested changes may or
+ * may not be immediate, see {@link #isImmediate()}.
+ */
+// @formatter:off
+public interface GenericRWWorldContext<
+ B extends GenericBlock,
+ T extends GenericTile,
+ TS extends GenericRWTileStack ,
+ TR extends GenericROTileReference ,
+ C extends GenericRWChunk ,
+ E extends GenericEntity
+> extends GenericROWorldContext, GenericRWWorld {
+// @formatter:on
+
+ /**
+ * Queries whether changes requested with this context are guaranteed to be
+ * applied immediately.
+ *
+ * When the changes are applied immediately, all subsequent queries will
+ * reflect the change. When the changes are not applied immediately, none of
+ * the subsequent queries will be affected by the requests while the context
+ * is {@linkplain Context#validity valid}. Immediate mode does not change
+ * while the context is valid.
+ *
+ * @return {@code true} iff changes are visible immediately
+ */
+ boolean isImmediate();
+
+ /**
+ * Requests that a block is changed. The object provided may be stored until
+ * the change is applied.
+ *
+ * @param location the location of the change
+ * @param block the new block
+ * @see #isImmediate()
+ */
+ default void setBlock(Vec3i location, B block) {
+ setBlock(location, block);
+ }
+
+ /**
+ * Requests that a tile is added to the top of the tile stack at the given
+ * location. The object provided may be stored until the change is applied.
+ * If the tile could not be added at the time of application this method
+ * fails silently.
+ *
+ * @param location the location of the block to which the tile is to be
+ * added
+ * @param face the face of the block to add the tile to
+ * @param tile the tile to add
+ */
+ void addTile(Vec3i location, BlockFace face, T tile);
+
+ /**
+ * Requests that a tile identified by its tag is removed from the specified
+ * tile stack. If the tile could not be found at the time of application
+ * this method fails silently.
+ *
+ * @param location the location of the block from which the tile is to be
+ * removed
+ * @param face the of the block to remove the tile from
+ * @param tag the tag of the tile to remove
+ */
+ void removeTile(Vec3i location, BlockFace face, int tag);
+
+ /**
+ * Requests that the referenced tile is removed from the specified tile
+ * stack. If the tile could not be found at the time of application this
+ * method fails silently.
+ *
+ * @param location the location of the block from which the tile is to
+ * be removed
+ * @param face the of the block to remove the tile from
+ * @param tileReference a reference to the tile
+ */
+ default void removeTile(Vec3i location, BlockFace face, TR tileReference) {
+ removeTile(location, face, tileReference.getTag());
+ }
+
+ /**
+ * Requests that an entity is added to the world. The object provided may be
+ * stored until the change is applied. If the entity was already added to
+ * the world at the time of application this method does nothing.
+ *
+ * @param entity the entity to add
+ * @see #isImmediate()
+ */
+ @Override
+ void addEntity(E entity);
+
+ /**
+ * Requests that an entity with the given entity ID is removed from the
+ * world. If the entity did not exist at the time of application this method
+ * fails silently.
+ *
+ * @param entityId the ID of the entity to remove
+ * @see #isImmediate()
+ * @see #removeEntity(GenericEntity)
+ */
+ @Override
+ void removeEntity(long entityId);
+
+ /**
+ * Requests that the entity is removed from the world. If the entity did not
+ * exist at the time of application this method fails silently.
+ *
+ * @param entity the entity to remove
+ * @see #isImmediate()
+ * @see #removeEntity(long)
+ */
+ @Override
+ void removeEntity(E entity);
+
+ /**
+ * Requests that the specified change is applied to the given entity. The {@code change} object provided may be stored until the change is applied.
+ *
+ * @param entity the entity to change
+ * @param change the change to apply
+ */
+ @Override
+ void changeEntity(SE entity, StateChange change);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/server/world/context/ServerContext.java b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContext.java
new file mode 100644
index 0000000..bb218ab
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/server/world/context/ServerContext.java
@@ -0,0 +1,84 @@
+/*
+ * 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 java.util.Random;
+
+import ru.windcorp.progressia.common.world.context.Context;
+import ru.windcorp.progressia.server.Server;
+import ru.windcorp.progressia.server.ServerState;
+
+/**
+ * A server-side {@link Context}. This context has a {@link Server} instance.
+ */
+public interface ServerContext extends Context {
+
+ /**
+ * Gets the {@link Server} instance relevant to this context. This method
+ * should always be preferred to {@link ServerState#getInstance()} when
+ * possible.
+ *
+ * @return the server instance
+ */
+ Server getServer();
+
+ /**
+ * Retrieves a context-appropriate source of randomness. This source should
+ * always be preferred to any other when possible.
+ *
+ * @return an intended {@link Random} instance
+ */
+ Random getRandom();
+
+ /**
+ * Returns the duration of the last server tick. Server logic should assume
+ * that this much in-world time has passed.
+ *
+ * @return the length of the last server tick
+ */
+ default double getTickLength() {
+ return getServer().getTickLength();
+ }
+
+ /**
+ * Adjusts the provided value according to tick length assuming the value
+ * scales linearly. The call {@code ctxt.adjustValue(x)} is equivalent to
+ * {@code ((float) ctxt.getTickLength()) * x}.
+ *
+ * @param valueForOneSecond the value to adjust, normalized to one second
+ * @return the value adjust to account for the actual tick length
+ * @see #getTickLength()
+ */
+ default float adjustTime(float valueForOneSecond) {
+ return ((float) getTickLength()) * valueForOneSecond;
+ }
+
+ /**
+ * Adjusts the provided value according to tick length assuming the value
+ * scales linearly. The call {@code ctxt.adjustValue(x)} is equivalent to
+ * {@code ctxt.getTickLength() * x}.
+ *
+ * @param valueForOneSecond the value to adjust, normalized to one second
+ * @return the value adjust to account for the actual tick length
+ * @see #getTickLength()
+ */
+ default double adjustTime(double valueForOneSecond) {
+ return getTickLength() * valueForOneSecond;
+ }
+
+}