35    public const DIRTY_FLAG_BLOCKS = 1 << 0;
 
   36    public const DIRTY_FLAG_BIOMES = 1 << 3;
 
   38    public const DIRTY_FLAGS_ALL = ~0;
 
   39    public const DIRTY_FLAGS_NONE = 0;
 
   41    public const MIN_SUBCHUNK_INDEX = -4;
 
   42    public const MAX_SUBCHUNK_INDEX = 19;
 
   43    public const MAX_SUBCHUNKS = self::MAX_SUBCHUNK_INDEX - self::MIN_SUBCHUNK_INDEX + 1;
 
   45    public const EDGE_LENGTH = SubChunk::EDGE_LENGTH;
 
   46    public const COORD_BIT_SIZE = SubChunk::COORD_BIT_SIZE;
 
   47    public const COORD_MASK = SubChunk::COORD_MASK;
 
   49    private int $terrainDirtyFlags = self::DIRTY_FLAGS_ALL;
 
   51    protected ?
bool $lightPopulated = 
false;
 
   52    protected bool $terrainPopulated = 
false;
 
   58    protected \SplFixedArray $subChunks;
 
   64    protected array $tiles = [];
 
   71    public function __construct(array $subChunks, 
bool $terrainPopulated){
 
   72        $this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
 
   74        foreach($this->subChunks as $y => $null){
 
   76            $this->subChunks[$y] = $subChunks[$y + self::MIN_SUBCHUNK_INDEX] ?? 
new SubChunk(Block::EMPTY_STATE_ID, [], 
new PalettedBlockArray(BiomeIds::OCEAN));
 
   79        $val = (self::MAX_SUBCHUNK_INDEX + 1) * SubChunk::EDGE_LENGTH;
 
   80        $this->heightMap = HeightArray::fill($val); 
 
   82        $this->terrainPopulated = $terrainPopulated;
 
 
   89        return $this->subChunks->getSize();
 
 
  102        return $this->getSubChunk($y >> 
SubChunk::COORD_BIT_SIZE)->getBlockStateId($x, $y & 
SubChunk::COORD_MASK, $z);
 
 
  109        $this->getSubChunk($y >> 
SubChunk::COORD_BIT_SIZE)->setBlockStateId($x, $y & 
SubChunk::COORD_MASK, $z, $block);
 
  110        $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
 
 
  122        for($y = self::MAX_SUBCHUNK_INDEX; $y >= self::MIN_SUBCHUNK_INDEX; --$y){
 
  123            $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z);
 
  124            if($height !== 
null){
 
  125                return $height | ($y << SubChunk::COORD_BIT_SIZE);
 
 
  139        return $this->heightMap->get($x, $z);
 
 
  149        $this->heightMap->set($x, $z, $value);
 
 
  162        return $this->getSubChunk($y >> 
SubChunk::COORD_BIT_SIZE)->getBiomeArray()->get($x, $y, $z);
 
 
  175    public function setBiomeId(
int $x, 
int $y, 
int $z, 
int $biomeId) : void{
 
  176        $this->getSubChunk($y >> 
SubChunk::COORD_BIT_SIZE)->getBiomeArray()->set($x, $y, $z, $biomeId);
 
  177        $this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES;
 
 
  180    public function isLightPopulated() : ?bool{
 
  181        return $this->lightPopulated;
 
  184    public function setLightPopulated(?
bool $value = 
true) : void{
 
  185        $this->lightPopulated = $value;
 
  188    public function isPopulated() : bool{
 
  189        return $this->terrainPopulated;
 
  192    public function setPopulated(
bool $value = 
true) : void{
 
  193        $this->terrainPopulated = $value;
 
  194        $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
 
  197    public function addTile(Tile $tile) : void{
 
  198        if($tile->isClosed()){
 
  199            throw new \InvalidArgumentException(
"Attempted to add a garbage closed Tile to a chunk");
 
  202        $pos = $tile->getPosition();
 
  203        if(isset($this->tiles[$index = Chunk::blockHash($pos->x, $pos->y, $pos->z)]) && $this->tiles[$index] !== $tile){
 
  204            throw new \InvalidArgumentException(
"Another tile is already at this location");
 
  206        $this->tiles[$index] = $tile;
 
  209    public function removeTile(Tile $tile) : void{
 
  211        unset($this->tiles[Chunk::blockHash($pos->x, $pos->y, $pos->z)]);
 
  230        return $this->tiles[
Chunk::blockHash($x, $y, $z)] ?? null;
 
 
  237        foreach($this->getTiles() as $tile){
 
 
  247        return $this->heightMap->getValues();
 
 
  258    public function isTerrainDirty() : bool{
 
  259        return $this->terrainDirtyFlags !== self::DIRTY_FLAGS_NONE;
 
  262    public function getTerrainDirtyFlag(
int $flag) : bool{
 
  263        return ($this->terrainDirtyFlags & $flag) !== 0;
 
  266    public function getTerrainDirtyFlags() : int{
 
  267        return $this->terrainDirtyFlags;
 
  270    public function setTerrainDirtyFlag(
int $flag, 
bool $value) : void{
 
  272            $this->terrainDirtyFlags |= $flag;
 
  274            $this->terrainDirtyFlags &= ~$flag;
 
  278    public function setTerrainDirty() : void{
 
  279        $this->terrainDirtyFlags = self::DIRTY_FLAGS_ALL;
 
  282    public function clearTerrainDirtyFlags() : void{
 
  283        $this->terrainDirtyFlags = self::DIRTY_FLAGS_NONE;
 
  286    public function getSubChunk(
int $y) : SubChunk{
 
  287        if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){
 
  288            throw new \InvalidArgumentException(
"Invalid subchunk Y coordinate $y");
 
  290        return $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX];
 
  297        if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){
 
  298            throw new \InvalidArgumentException(
"Invalid subchunk Y coordinate $y");
 
  301        $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX] = $subChunk ?? 
new SubChunk(Block::EMPTY_STATE_ID, [], 
new PalettedBlockArray(BiomeIds::OCEAN));
 
  302        $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
 
 
  311        foreach($this->subChunks as $yOffset => $subChunk){
 
  312            $result[$yOffset + self::MIN_SUBCHUNK_INDEX] = $subChunk;
 
 
  321        foreach($this->subChunks as $y => $subChunk){
 
  322            $subChunk->collectGarbage();
 
 
  326    public function __clone(){
 
  328        $this->subChunks = \SplFixedArray::fromArray(array_map(
function(
SubChunk $subChunk) : 
SubChunk{
 
  329            return clone $subChunk;
 
  330        }, $this->subChunks->toArray()));
 
  331        $this->heightMap = clone $this->heightMap;
 
  341    public static function blockHash(
int $x, 
int $y, 
int $z) : int{
 
  342        return ($y << (2 * 
SubChunk::COORD_BIT_SIZE)) |