--- a/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -64,7 +_,7 @@
     protected final ShortList[] postProcessing;
     private volatile boolean unsaved;
     private volatile boolean isLightCorrect;
-    protected final ChunkPos chunkPos;
+    protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key
     private long inhabitedTime;
     @Nullable
     @Deprecated
@@ -82,6 +_,11 @@
     public final Map<BlockPos, BlockEntity> blockEntities = new Object2ObjectOpenHashMap<>();
     protected final LevelHeightAccessor levelHeightAccessor;
     protected final LevelChunkSection[] sections;
+    // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading.
+    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
+    public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
+    // CraftBukkit end
+    public final Registry<Biome> biomeRegistry; // CraftBukkit
 
     public ChunkAccess(
         ChunkPos chunkPos,
@@ -92,7 +_,8 @@
         @Nullable LevelChunkSection[] sections,
         @Nullable BlendingData blendingData
     ) {
-        this.chunkPos = chunkPos;
+        this.locX = chunkPos.x; this.locZ = chunkPos.z; // Paper - reduce need for field lookups
+        this.chunkPos = chunkPos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
         this.upgradeData = upgradeData;
         this.levelHeightAccessor = levelHeightAccessor;
         this.sections = new LevelChunkSection[levelHeightAccessor.getSectionsCount()];
@@ -109,6 +_,7 @@
         }
 
         replaceMissingSections(biomeRegistry, this.sections);
+        this.biomeRegistry = biomeRegistry; // CraftBukkit
     }
 
     private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
@@ -123,6 +_,7 @@
         return GameEventListenerRegistry.NOOP;
     }
 
+    public abstract BlockState getBlockState(final int x, final int y, final int z); // Paper
     @Nullable
     public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving);
 
@@ -273,6 +_,7 @@
     public boolean tryMarkSaved() {
         if (this.unsaved) {
             this.unsaved = false;
+            this.persistentDataContainer.dirty(false); // CraftBukkit - SPIGOT-6814: chunk was saved, pdc is no longer dirty
             return true;
         } else {
             return false;
@@ -280,7 +_,7 @@
     }
 
     public boolean isUnsaved() {
-        return this.unsaved;
+        return this.unsaved || this.persistentDataContainer.dirty(); // CraftBukkit - SPIGOT-6814: chunk is unsaved if pdc was mutated
     }
 
     public abstract ChunkStatus getPersistedStatus();
@@ -446,6 +_,22 @@
             throw new ReportedException(crashReport);
         }
     }
+    // CraftBukkit start
+    public void setBiome(int x, int y, int z, Holder<Biome> biome) {
+        try {
+            int minY = QuartPos.fromBlock(this.getMinY());
+            int maxY = minY + QuartPos.fromBlock(this.getHeight()) - 1;
+            int clampedY = Mth.clamp(y, minY, maxY);
+            int sectionIndex = this.getSectionIndex(QuartPos.toBlock(clampedY));
+            this.sections[sectionIndex].setBiome(x & 3, clampedY & 3, z & 3, biome);
+        } catch (Throwable throwable) {
+            CrashReport report = CrashReport.forThrowable(throwable, "Setting biome");
+            CrashReportCategory reportCategory = report.addCategory("Biome being set");
+            reportCategory.setDetail("Location", () -> CrashReportCategory.formatLocation(this, x, y, z));
+            throw new ReportedException(report);
+        }
+    }
+    // CraftBukkit end
 
     public void fillBiomesFromNoise(BiomeResolver resolver, Climate.Sampler sampler) {
         ChunkPos pos = this.getPos();
