File size: 6,701 Bytes
0db945a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16d7054
 
 
0db945a
16d7054
 
 
 
 
 
 
 
 
0db945a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""The world map is a contract with the rest of the runtime.

Voyager's placement, navigation and prompts are all pinned to a walkable surface
at Y=64, and generator settings are only read when a world is first created, so
a careless layer edit silently moves the ground under the bot or leaves the old
map in place. These tests lock both properties down.
"""
from __future__ import annotations

import json
import tempfile
from pathlib import Path

import minecraft_stack as mc

# Every ore a farming bot is expected to be able to reach on foot.
FARMABLE_ORES = (
    "coal_ore",
    "copper_ore",
    "iron_ore",
    "gold_ore",
    "redstone_ore",
    "diamond_ore",
    "lapis_ore",
    "emerald_ore",
)


def _column() -> list[tuple[str, int, int]]:
    """Return (block, lowest_y, highest_y) from the bottom of the world up."""
    y = mc.WORLD_FLOOR_Y
    column = []
    for block, height in mc.WORLD_FLAT_LAYERS:
        column.append((block, y, y + height - 1))
        y += height
    return column


def test_layer_stack_reaches_the_declared_surface():
    column = _column()
    assert sum(height for _, height in mc.WORLD_FLAT_LAYERS) == (
        mc.WORLD_SURFACE_Y - mc.WORLD_FLOOR_Y
    )
    # The top layer is the walkable surface and its first air block is Y=64.
    assert column[-1][0] == "minecraft:grass_block"
    assert column[-1][2] == mc.WORLD_SURFACE_Y - 1
    assert column[-1][1] == column[-1][2]


def test_surface_has_farmable_soil_over_the_ores():
    column = _column()
    by_block = {}
    for block, low, high in column:
        by_block.setdefault(block, []).append((low, high))
    # Soil and grass on top so crops and building have something to work with.
    assert "minecraft:dirt" in by_block
    assert by_block["minecraft:dirt"][-1][1] == mc.WORLD_SURFACE_Y - 2
    # A resource band close enough to the surface to mine without a long shaft.
    for ore in FARMABLE_ORES:
        assert f"minecraft:{ore}" in by_block, f"{ore} missing from the map"
        shallow = [span for span in by_block[f"minecraft:{ore}"] if span[0] > 0]
        assert shallow, f"{ore} is only in the deepslate band"
        assert min(low for low, _ in shallow) >= mc.WORLD_SURFACE_Y - 32, (
            f"{ore} sits too deep to mine from the surface"
        )
    # The deepslate band duplicates the table at the bottom of the column.
    for ore in FARMABLE_ORES:
        assert f"minecraft:deepslate_{ore}" in by_block, f"deepslate {ore} missing"
    assert "minecraft:bedrock" in by_block


def test_every_layer_block_exists_in_minecraft_1_19():
    # An unknown block name makes the server refuse to generate the world, which
    # would take the whole Space down on boot. The registry shipped in the repo
    # is the same minecraft-data dataset the runtime ships to Mineflayer.
    registry = json.loads(
        (Path(__file__).resolve().parents[1] / "minecraft_ids_1_19.json").read_text(
            encoding="utf-8"
        )
    )
    known = {
        entry if isinstance(entry, str) else entry.get("name")
        for entry in registry["diggable_blocks"]
    }
    # bedrock is the one layer that is never diggable, so it is absent by design.
    known.add("bedrock")
    unknown = [
        block for block, _ in mc.WORLD_FLAT_LAYERS if block.split(":", 1)[1] not in known
    ]
    assert unknown == [], f"blocks absent from Minecraft 1.19: {unknown}"


def test_generator_settings_match_the_declared_layers():
    settings = json.loads(mc.FLAT_GENERATOR_SETTINGS)
    assert settings["layers"] == [
        {"block": block, "height": height} for block, height in mc.WORLD_FLAT_LAYERS
    ]
    # Lava lakes stay off: they are the only thing `lakes` controls, and they
    # would put lava on the surface the bot builds and pathfinds on.
    # (minecraft.wiki/w/Superflat/Settings)
    assert settings["lakes"] is False
    # `features` was previously asserted False on the theory that vegetation
    # would sit on the walkable surface. It does not: biome features such as
    # trees and grass occupy blocks *above* WORLD_SURFACE_Y, which is unchanged.
    # Leaving it off left a plains superflat with no wood at all, so the official
    # curriculum kept proposing crafting tasks the bot could not satisfy.
    assert settings["features"] is True
    # Omitted on purpose: the documented default is "all the structure sets",
    # while an empty list disables every structure. This used to pass [].
    assert "structure_overrides" not in settings


def test_map_change_uses_a_new_world_directory():
    # Generator settings are only applied when level.dat is created, so the map
    # can only be replaced by pointing at a directory that has no world yet.
    assert mc.WORLD_DIR != mc.PREVIOUS_WORLD_DIR
    assert mc.WORLD_DIR.parent == mc.PERSIST_ROOT


def test_carrying_player_saves_keeps_the_human_session_and_resets_the_bot():
    with tempfile.TemporaryDirectory() as tmp:
        previous = Path(tmp) / "old-world"
        current = Path(tmp) / "new-world"
        human = f"{mc._offline_player_uuid(mc.ADMIN_USERNAME)}.dat"
        bot = f"{mc._offline_player_uuid(mc.BOT_USERNAME)}.dat"
        (previous / "playerdata").mkdir(parents=True)
        (previous / "playerdata" / human).write_bytes(b"human-save")
        (previous / "playerdata" / bot).write_bytes(b"bot-save")
        (previous / "playerdata" / "README.txt").write_text("not a save", encoding="utf-8")
        current.mkdir(parents=True)

        mc._carry_player_saves(previous, current)
        carried = sorted(p.name for p in (current / "playerdata").iterdir())
        assert carried == [human]
        assert (current / "playerdata" / human).read_bytes() == b"human-save"
        # A full bot inventory is what pins the official CurriculumAgent to its
        # hardcoded full-inventory branch, so the bot must start clean.
        assert not (current / "playerdata" / bot).exists()

        # Running again on an already-populated world must not resurrect the bot
        # save, and must be safe when there is no previous world at all.
        mc._carry_player_saves(previous, current)
        mc._carry_player_saves(Path(tmp) / "missing", current)
        assert sorted(p.name for p in (current / "playerdata").iterdir()) == [human]


if __name__ == "__main__":
    test_layer_stack_reaches_the_declared_surface()
    test_surface_has_farmable_soil_over_the_ores()
    test_every_layer_block_exists_in_minecraft_1_19()
    test_generator_settings_match_the_declared_layers()
    test_map_change_uses_a_new_world_directory()
    test_carrying_player_saves_keeps_the_human_session_and_resets_the_bot()
    print("PASS resource map, surface Y=64 and player save carry-over")