From 1ef4d6521099880cac1c6159e81d7c9d01f193e1 Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
 <136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Wed, 27 May 2026 23:53:48 +0000
Subject: [PATCH] CodeRabbit Generated Unit Tests: Add generated unit tests

---
 tests/__init__.py                             |   0
 tests/__pycache__/__init__.cpython-311.pyc    |   0
 .../conftest.cpython-311-pytest-9.0.3.pyc     |   0
 ...ild_templates.cpython-311-pytest-9.0.3.pyc |   0
 .../test_common.cpython-311-pytest-9.0.3.pyc  |   0
 ...reter_helpers.cpython-311-pytest-9.0.3.pyc |   0
 ...compile_types.cpython-311-pytest-9.0.3.pyc |   0
 ...ersion_config.cpython-311-pytest-9.0.3.pyc |   0
 tests/conftest.py                             |  60 ++
 tests/test_build_templates.py                 | 222 +++++++
 tests/test_common.py                          |  88 +++
 tests/test_compile_interpreter_helpers.py     | 127 ++++
 tests/test_compile_types.py                   | 347 +++++++++++
 tests/test_version_config.py                  | 574 ++++++++++++++++++
 14 files changed, 1418 insertions(+)
 create mode 100644 tests/__init__.py
 create mode 100644 tests/__pycache__/__init__.cpython-311.pyc
 create mode 100644 tests/__pycache__/conftest.cpython-311-pytest-9.0.3.pyc
 create mode 100644 tests/__pycache__/test_build_templates.cpython-311-pytest-9.0.3.pyc
 create mode 100644 tests/__pycache__/test_common.cpython-311-pytest-9.0.3.pyc
 create mode 100644 tests/__pycache__/test_compile_interpreter_helpers.cpython-311-pytest-9.0.3.pyc
 create mode 100644 tests/__pycache__/test_compile_types.cpython-311-pytest-9.0.3.pyc
 create mode 100644 tests/__pycache__/test_version_config.cpython-311-pytest-9.0.3.pyc
 create mode 100644 tests/conftest.py
 create mode 100644 tests/test_build_templates.py
 create mode 100644 tests/test_common.py
 create mode 100644 tests/test_compile_interpreter_helpers.py
 create mode 100644 tests/test_compile_types.py
 create mode 100644 tests/test_version_config.py

diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__pycache__/__init__.cpython-311.pyc b/tests/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__pycache__/conftest.cpython-311-pytest-9.0.3.pyc b/tests/__pycache__/conftest.cpython-311-pytest-9.0.3.pyc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__pycache__/test_build_templates.cpython-311-pytest-9.0.3.pyc b/tests/__pycache__/test_build_templates.cpython-311-pytest-9.0.3.pyc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__pycache__/test_common.cpython-311-pytest-9.0.3.pyc b/tests/__pycache__/test_common.cpython-311-pytest-9.0.3.pyc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__pycache__/test_compile_interpreter_helpers.cpython-311-pytest-9.0.3.pyc b/tests/__pycache__/test_compile_interpreter_helpers.cpython-311-pytest-9.0.3.pyc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__pycache__/test_compile_types.cpython-311-pytest-9.0.3.pyc b/tests/__pycache__/test_compile_types.cpython-311-pytest-9.0.3.pyc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__pycache__/test_version_config.cpython-311-pytest-9.0.3.pyc b/tests/__pycache__/test_version_config.cpython-311-pytest-9.0.3.pyc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..a36551c
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,60 @@
+"""Shared pytest fixtures for Minecraft-Script tests."""
+import json
+import pytest
+from pathlib import Path
+
+import minecraft_script.version_config as vc
+
+
+MINIMAL_PROFILE = {
+    "minecraft_version": "0.0.test",
+    "pack_format": 99,
+    "paths": {
+        "function_dir": "functions",
+        "function_tag_dir": "functions",
+    },
+    "constants": {
+        "testConst": "hello",
+    },
+    "templates": {
+        "simple": "hello {{name}}",
+        "multiline": "line1\nline2 {{value}}\nline3",
+        "no_params": "static text",
+        "with_const": "{{testConst}} world",
+        "datapack_ref": "function {{datapack_id}}:something",
+        "conditional": "{{#if flag}}yes{{else}}no{{/if}}",
+        "literal.save": "data modify storage {{storage}} {{nbt}} set value {{value}}",
+        "literal.delete": "data remove storage {{storage}} {{nbt}}",
+        "literal.set_to_current": "data modify storage {{destStorage}} current set from storage {{storage}} {{nbt}}",
+        "literal.list.length": "data modify storage {{storage}} {{nbt}}.length set value {{length}}",
+        "literal.list.element": "data modify storage {{storage}} {{nbt}}.{{index}} set from storage {{srcStorage}} current",
+    },
+}
+
+
+@pytest.fixture(autouse=False)
+def version_context():
+    """Initialize and clean up a version context using the real 1.20.4 profile."""
+    ctx = vc.init_version_context("test_pack")
+    yield ctx
+    vc.clear_version_context()
+
+
+@pytest.fixture
+def minimal_version_context():
+    """Initialize and clean up a version context using a minimal test profile."""
+    vc._profile_cache["0.0.test"] = MINIMAL_PROFILE
+    ctx = vc.VersionContext("test_pack", profile=MINIMAL_PROFILE)
+    # Set as global context
+    vc._version_ctx = ctx
+    yield ctx
+    vc.clear_version_context()
+    vc._profile_cache.pop("0.0.test", None)
+
+
+@pytest.fixture(autouse=True)
+def clear_version_ctx():
+    """Always ensure the version context is cleared between tests."""
+    yield
+    vc.clear_version_context()
+    vc._profile_cache.clear()
\ No newline at end of file
diff --git a/tests/test_build_templates.py b/tests/test_build_templates.py
new file mode 100644
index 0000000..33fdf48
--- /dev/null
+++ b/tests/test_build_templates.py
@@ -0,0 +1,222 @@
+"""Tests for the versioned build template files added/moved in this PR.
+
+Covers:
+- minecraft_script/compiler/build_templates/builtins/1.20.4/*.mcfunction
+- minecraft_script/compiler/build_templates/math/1.20.4/*.mcfunction
+- minecraft_script/compiler/build_templates/tags/1.20.4/block/no_collision.json
+"""
+import json
+import os
+from pathlib import Path
+
+import minecraft_script.version_config as vc
+
+TEMPLATES_BASE = Path(__file__).resolve().parents[1] / "minecraft_script" / "compiler" / "build_templates"
+BUILTINS_1204 = TEMPLATES_BASE / "builtins" / "1.20.4"
+MATH_1204 = TEMPLATES_BASE / "math" / "1.20.4"
+TAGS_1204 = TEMPLATES_BASE / "tags" / "1.20.4"
+
+
+# ===========================================================================
+# Directory structure tests
+# ===========================================================================
+
+class TestVersionedDirectoryStructure:
+    def test_builtins_1204_dir_exists(self):
+        assert BUILTINS_1204.is_dir()
+
+    def test_math_1204_dir_exists(self):
+        assert MATH_1204.is_dir()
+
+    def test_tags_1204_dir_exists(self):
+        assert TAGS_1204.is_dir()
+
+    def test_old_unversioned_builtins_dir_gone(self):
+        old_path = TEMPLATES_BASE / "builtins" / "give_item.mcfunction"
+        # The old file should no longer exist at unversioned path
+        assert not old_path.is_file()
+
+    def test_old_unversioned_math_dir_gone(self):
+        # Old path was math/add.mcfunction — should not exist
+        old_path = TEMPLATES_BASE / "math" / "add.mcfunction"
+        assert not old_path.is_file()
+
+
+# ===========================================================================
+# give_item.mcfunction — key PR change (format changed)
+# ===========================================================================
+
+class TestGiveItemMcfunction:
+    def setup_method(self):
+        self.path = BUILTINS_1204 / "give_item.mcfunction"
+
+    def test_file_exists(self):
+        assert self.path.is_file()
+
+    def test_file_starts_with_macro_prefix(self):
+        content = self.path.read_text(encoding="utf-8")
+        assert content.startswith("$give @s")
+
+    def test_file_has_components_without_brackets(self):
+        """New format uses $(components) directly without square brackets.
+
+        Old format: $give @s $(item)[$(components)] $(count)
+        New format: $give @s $(item)$(components) $(count)
+        """
+        content = self.path.read_text(encoding="utf-8").strip()
+        assert "$(components)" in content
+        # The old format wrapped components in square brackets
+        assert "[$(components)]" not in content
+
+    def test_file_has_item_placeholder(self):
+        content = self.path.read_text(encoding="utf-8")
+        assert "$(item)" in content
+
+    def test_file_has_count_placeholder(self):
+        content = self.path.read_text(encoding="utf-8")
+        assert "$(count)" in content
+
+    def test_file_exact_content(self):
+        content = self.path.read_text(encoding="utf-8").strip()
+        assert content == "$give @s $(item)$(components) $(count)"
+
+
+# ===========================================================================
+# Other builtin mcfunction files
+# ===========================================================================
+
+class TestBuiltinMcfunctions:
+    EXPECTED_FILES = ["command.mcfunction", "give_item.mcfunction", "log.mcfunction", "set_block.mcfunction"]
+
+    def test_all_expected_files_present(self):
+        for fname in self.EXPECTED_FILES:
+            assert (BUILTINS_1204 / fname).is_file(), f"Missing: {fname}"
+
+    def test_command_mcfunction_not_empty(self):
+        content = (BUILTINS_1204 / "command.mcfunction").read_text(encoding="utf-8")
+        assert len(content.strip()) > 0
+
+    def test_log_mcfunction_not_empty(self):
+        content = (BUILTINS_1204 / "log.mcfunction").read_text(encoding="utf-8")
+        assert len(content.strip()) > 0
+
+    def test_set_block_mcfunction_contains_setblock_command(self):
+        content = (BUILTINS_1204 / "set_block.mcfunction").read_text(encoding="utf-8")
+        assert "setblock" in content.lower() or "$(block)" in content
+
+
+# ===========================================================================
+# Math mcfunction files
+# ===========================================================================
+
+class TestMathMcfunctions:
+    EXPECTED_MATH_OPS = [
+        "add", "subtract", "multiply", "divide", "modulus",
+        "equals", "greater_than", "greater_equals_than",
+        "less_than", "less_equals_than",
+        "u_add", "u_subtract", "u_not",
+    ]
+
+    def test_all_math_files_present(self):
+        for op in self.EXPECTED_MATH_OPS:
+            path = MATH_1204 / f"{op}.mcfunction"
+            assert path.is_file(), f"Missing math template: {op}.mcfunction"
+
+    def test_add_uses_scoreboard_operation(self):
+        content = (MATH_1204 / "add.mcfunction").read_text(encoding="utf-8")
+        assert "scoreboard players operation" in content
+
+    def test_add_uses_out_scoreboard(self):
+        content = (MATH_1204 / "add.mcfunction").read_text(encoding="utf-8")
+        assert ".out mcs_math" in content
+
+    def test_add_uses_a_and_b_inputs(self):
+        content = (MATH_1204 / "add.mcfunction").read_text(encoding="utf-8")
+        assert ".a mcs_math" in content
+        assert ".b mcs_math" in content
+
+    def test_subtract_produces_subtraction(self):
+        content = (MATH_1204 / "subtract.mcfunction").read_text(encoding="utf-8")
+        assert "-=" in content
+
+    def test_multiply_produces_multiplication(self):
+        content = (MATH_1204 / "multiply.mcfunction").read_text(encoding="utf-8")
+        assert "*=" in content
+
+    def test_divide_produces_division(self):
+        content = (MATH_1204 / "divide.mcfunction").read_text(encoding="utf-8")
+        assert "/=" in content
+
+    def test_modulus_produces_modulo(self):
+        content = (MATH_1204 / "modulus.mcfunction").read_text(encoding="utf-8")
+        assert "%=" in content
+
+    def test_equals_uses_scoreboard_comparison(self):
+        content = (MATH_1204 / "equals.mcfunction").read_text(encoding="utf-8")
+        assert "scoreboard" in content
+
+    def test_u_not_contains_not_logic(self):
+        content = (MATH_1204 / "u_not.mcfunction").read_text(encoding="utf-8")
+        # u_not should flip a boolean value
+        assert "scoreboard" in content or "execute" in content
+
+    def test_no_math_files_outside_versioned_folder(self):
+        """Ensure math files are not duplicated at the unversioned level."""
+        for op in self.EXPECTED_MATH_OPS:
+            old_path = TEMPLATES_BASE / "math" / f"{op}.mcfunction"
+            assert not old_path.is_file(), f"Unexpected file at unversioned path: {old_path}"
+
+
+# ===========================================================================
+# Tags — no_collision.json
+# ===========================================================================
+
+class TestNoCollisionTag:
+    def setup_method(self):
+        self.path = TAGS_1204 / "block" / "no_collision.json"
+
+    def test_file_exists(self):
+        assert self.path.is_file()
+
+    def test_file_is_valid_json(self):
+        content = self.path.read_text(encoding="utf-8")
+        data = json.loads(content)
+        assert isinstance(data, dict)
+
+    def test_json_has_values_key(self):
+        data = json.loads(self.path.read_text(encoding="utf-8"))
+        assert "values" in data
+
+    def test_values_is_list(self):
+        data = json.loads(self.path.read_text(encoding="utf-8"))
+        assert isinstance(data["values"], list)
+
+    def test_includes_minecraft_air(self):
+        data = json.loads(self.path.read_text(encoding="utf-8"))
+        assert "minecraft:air" in data["values"]
+
+    def test_includes_void_air(self):
+        data = json.loads(self.path.read_text(encoding="utf-8"))
+        assert "minecraft:void_air" in data["values"]
+
+    def test_old_unversioned_no_collision_gone(self):
+        old_path = TEMPLATES_BASE / "tags" / "block" / "no_collision.json"
+        assert not old_path.is_file()
+
+
+# ===========================================================================
+# predefined_root() points to versioned dirs (integration)
+# ===========================================================================
+
+class TestPredefinedRootIntegration:
+    def test_math_predefined_root_contains_add_mcfunction(self):
+        root = vc.predefined_root("math", "1.20.4")
+        assert (Path(root) / "add.mcfunction").is_file()
+
+    def test_builtins_predefined_root_contains_give_item_mcfunction(self):
+        root = vc.predefined_root("builtins", "1.20.4")
+        assert (Path(root) / "give_item.mcfunction").is_file()
+
+    def test_tags_predefined_root_contains_no_collision_json(self):
+        root = vc.predefined_root("tags", "1.20.4")
+        assert (Path(root) / "block" / "no_collision.json").is_file()
\ No newline at end of file
diff --git a/tests/test_common.py b/tests/test_common.py
new file mode 100644
index 0000000..61c4405
--- /dev/null
+++ b/tests/test_common.py
@@ -0,0 +1,88 @@
+"""Tests for minecraft_script/common.py changes in this PR.
+
+The PR replaced platform-specific module_folder detection with a
+cross-platform pathlib-based approach.
+"""
+import os
+from pathlib import Path
+
+import minecraft_script.common as common_module
+
+
+class TestModuleFolder:
+    """Tests for the module_folder variable (changed from platform-specific to pathlib)."""
+
+    def test_module_folder_is_string(self):
+        assert isinstance(common_module.module_folder, str)
+
+    def test_module_folder_is_absolute_path(self):
+        assert os.path.isabs(common_module.module_folder)
+
+    def test_module_folder_exists(self):
+        assert os.path.isdir(common_module.module_folder)
+
+    def test_module_folder_contains_config_json(self):
+        config_path = os.path.join(common_module.module_folder, "config.json")
+        assert os.path.isfile(config_path)
+
+    def test_module_folder_points_to_minecraft_script_package(self):
+        # The folder should be the minecraft_script package directory
+        folder_name = os.path.basename(common_module.module_folder)
+        assert folder_name == "minecraft_script"
+
+    def test_module_folder_matches_pathlib_calculation(self):
+        # Verify the value matches what the new code computes
+        expected = str(Path(common_module.__file__).resolve().parent)
+        assert common_module.module_folder == expected
+
+    def test_module_folder_does_not_contain_backslashes_on_non_windows(self):
+        # The old code joined with '/' explicitly; pathlib should give OS-native separator
+        # On Linux/macOS this means no backslashes
+        if os.name != "nt":
+            assert "\\" not in common_module.module_folder
+
+    def test_module_folder_does_not_end_with_separator(self):
+        # Should not have trailing slash
+        assert not common_module.module_folder.endswith(os.sep)
+        assert not common_module.module_folder.endswith("/")
+
+
+class TestCommonConfig:
+    """Tests that COMMON_CONFIG is loaded correctly using the new module_folder."""
+
+    def test_common_config_is_dict(self):
+        assert isinstance(common_module.COMMON_CONFIG, dict)
+
+    def test_common_config_has_minecraft_version(self):
+        assert "minecraft_version" in common_module.COMMON_CONFIG
+
+    def test_common_config_minecraft_version_is_string(self):
+        assert isinstance(common_module.COMMON_CONFIG["minecraft_version"], str)
+
+    def test_common_config_has_debug_comments(self):
+        assert "debug_comments" in common_module.COMMON_CONFIG
+
+    def test_common_config_has_verbose(self):
+        assert "verbose" in common_module.COMMON_CONFIG
+
+    def test_common_config_has_default_output_path(self):
+        assert "default_output_path" in common_module.COMMON_CONFIG
+
+
+class TestGenerateUuid:
+    """Tests for generate_uuid() (unchanged but used throughout the module)."""
+
+    def test_returns_string(self):
+        uuid = common_module.generate_uuid()
+        assert isinstance(uuid, str)
+
+    def test_returns_unique_values(self):
+        uuids = {common_module.generate_uuid() for _ in range(100)}
+        assert len(uuids) == 100
+
+    def test_uuid_format(self):
+        uuid = common_module.generate_uuid()
+        # UUID4 has 32 hex chars + 4 dashes = 36 chars total
+        assert len(uuid) == 36
+        parts = uuid.split("-")
+        assert len(parts) == 5
\ No newline at end of file
diff --git a/tests/test_compile_interpreter_helpers.py b/tests/test_compile_interpreter_helpers.py
new file mode 100644
index 0000000..a8379bc
--- /dev/null
+++ b/tests/test_compile_interpreter_helpers.py
@@ -0,0 +1,127 @@
+"""Tests for changes in minecraft_script/compiler/compile_interpreter.py.
+
+This PR changed:
+- add_comment(): now preserves list->tuple conversion even when debug_comments=False,
+  and handles list input for the prefix case.
+"""
+import pytest
+from unittest.mock import patch
+
+import minecraft_script.version_config as vc
+from minecraft_script.common import COMMON_CONFIG
+
+
+def _setup_version_ctx():
+    """Helper: ensure a version context exists for tests that indirectly need it."""
+    if vc._version_ctx is None:
+        vc.init_version_context("test_dp")
+
+
+# ---------------------------------------------------------------------------
+# Tests for add_comment()
+# ---------------------------------------------------------------------------
+
+class TestAddComment:
+    """Tests for the add_comment() function changed in this PR."""
+
+    @pytest.fixture(autouse=True)
+    def enable_debug_comments(self):
+        original = COMMON_CONFIG.get("debug_comments")
+        COMMON_CONFIG["debug_comments"] = True
+        yield
+        COMMON_CONFIG["debug_comments"] = original
+
+    def _get_add_comment(self):
+        from minecraft_script.compiler.compile_interpreter import add_comment
+        return add_comment
+
+    # ---- debug_comments = True ----
+
+    def test_tuple_input_prepends_comment(self):
+        add_comment = self._get_add_comment()
+        result = add_comment(("cmd1", "cmd2"), "my comment")
+        assert result[0] == "\n# my comment"
+        assert "cmd1" in result
+        assert "cmd2" in result
+
+    def test_string_input_prepends_comment(self):
+        add_comment = self._get_add_comment()
+        result = add_comment("single cmd", "my comment")
+        assert result.startswith("\n# my comment\n")
+        assert "single cmd" in result
+
+    def test_list_input_becomes_tuple_with_comment(self):
+        add_comment = self._get_add_comment()
+        result = add_comment(["cmd1", "cmd2"], "a comment")
+        assert isinstance(result, tuple)
+        assert result[0] == "\n# a comment"
+        assert "cmd1" in result
+        assert "cmd2" in result
+
+    def test_list_input_returns_tuple_not_list(self):
+        add_comment = self._get_add_comment()
+        result = add_comment(["a", "b"], "comment")
+        assert isinstance(result, tuple)
+
+    # ---- debug_comments = False ----
+
+    def test_tuple_returned_unchanged_when_debug_off(self):
+        add_comment = self._get_add_comment()
+        COMMON_CONFIG["debug_comments"] = False
+        cmds = ("cmd1", "cmd2")
+        result = add_comment(cmds, "ignored")
+        assert result == cmds
+
+    def test_string_returned_unchanged_when_debug_off(self):
+        add_comment = self._get_add_comment()
+        COMMON_CONFIG["debug_comments"] = False
+        result = add_comment("single cmd", "ignored")
+        assert result == "single cmd"
+
+    def test_list_converted_to_tuple_when_debug_off(self):
+        """PR change: list input is now converted to tuple even when debug is off."""
+        add_comment = self._get_add_comment()
+        COMMON_CONFIG["debug_comments"] = False
+        result = add_comment(["a", "b"], "ignored comment")
+        assert isinstance(result, tuple)
+        assert result == ("a", "b")
+
+    def test_list_debug_off_preserves_content(self):
+        add_comment = self._get_add_comment()
+        COMMON_CONFIG["debug_comments"] = False
+        result = add_comment(["x", "y", "z"], "ignored")
+        assert list(result) == ["x", "y", "z"]
+
+    # ---- error cases ----
+
+    def test_raises_value_error_for_non_iterable_input(self):
+        add_comment = self._get_add_comment()
+        with pytest.raises(ValueError) as exc_info:
+            add_comment(42, "comment")
+        assert "tuple, list, or str" in str(exc_info.value)
+
+    def test_raises_value_error_for_dict_input(self):
+        add_comment = self._get_add_comment()
+        with pytest.raises(ValueError):
+            add_comment({"key": "val"}, "comment")
+
+    def test_raises_value_error_for_none_input(self):
+        add_comment = self._get_add_comment()
+        with pytest.raises(ValueError):
+            add_comment(None, "comment")
+
+    def test_empty_tuple_with_comment(self):
+        add_comment = self._get_add_comment()
+        result = add_comment((), "comment")
+        assert result == ("\n# comment",)
+
+    def test_empty_list_with_comment(self):
+        add_comment = self._get_add_comment()
+        result = add_comment([], "comment")
+        assert isinstance(result, tuple)
+        assert result == ("\n# comment",)
+
+    def test_empty_string_with_comment(self):
+        add_comment = self._get_add_comment()
+        result = add_comment("", "comment")
+        assert result.startswith("\n# comment\n")
\ No newline at end of file
diff --git a/tests/test_compile_types.py b/tests/test_compile_types.py
new file mode 100644
index 0000000..9ce25e3
--- /dev/null
+++ b/tests/test_compile_types.py
@@ -0,0 +1,347 @@
+"""Tests for changes in minecraft_script/compiler/compile_types.py.
+
+This PR changed MCSObject, MCSVariable, MCSList, MCSNull to use
+get_version_context() for generating storage commands instead of
+hard-coded f-strings.
+"""
+import pytest
+
+import minecraft_script.version_config as vc
+from minecraft_script.compiler import compile_types
+
+
+# ---------------------------------------------------------------------------
+# Shared fake context for constructing MCS objects in tests
+# ---------------------------------------------------------------------------
+
+class FakeContext:
+    """Minimal context object that satisfies compile_types requirements."""
+    def __init__(self, uuid: str = "deadbeef-0000-0000-0000-000000000001"):
+        self.uuid = uuid
+
+
+FAKE_UUID_1 = "deadbeef-0000-0000-0000-000000000001"
+FAKE_UUID_2 = "cafef00d-0000-0000-0000-000000000002"
+
+
+@pytest.fixture(autouse=True)
+def version_context_1204():
+    """Ensure the 1.20.4 version context is active for all tests in this file."""
+    ctx = vc.init_version_context("test_pack")
+    yield ctx
+    vc.clear_version_context()
+
+
+# ===========================================================================
+# MCSObject
+# ===========================================================================
+
+class TestMCSObjectStorageCommands:
+    """Tests for MCSObject methods changed in this PR."""
+
+    def test_get_storage_returns_mcs_prefix_plus_uuid(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        assert obj.get_storage() == f"mcs_{FAKE_UUID_1}"
+
+    def test_get_nbt_contains_storage_compartment(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        assert obj.get_nbt().startswith("number.")
+
+    def test_save_to_storage_cmd_returns_string(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.save_to_storage_cmd(42)
+        assert isinstance(cmd, str)
+
+    def test_save_to_storage_cmd_contains_storage(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.save_to_storage_cmd(42)
+        assert obj.get_storage() in cmd
+
+    def test_save_to_storage_cmd_contains_nbt(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.save_to_storage_cmd(42)
+        assert obj.get_nbt() in cmd
+
+    def test_save_to_storage_cmd_contains_value(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.save_to_storage_cmd(99)
+        assert "99" in cmd
+
+    def test_save_to_storage_cmd_uses_data_modify(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.save_to_storage_cmd(1)
+        assert "data modify storage" in cmd
+
+    def test_delete_from_storage_cmd_returns_string(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.delete_from_storage_cmd()
+        assert isinstance(cmd, str)
+
+    def test_delete_from_storage_cmd_contains_data_remove(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.delete_from_storage_cmd()
+        assert "data remove storage" in cmd
+
+    def test_delete_from_storage_cmd_contains_storage(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.delete_from_storage_cmd()
+        assert obj.get_storage() in cmd
+
+    def test_delete_from_storage_cmd_contains_nbt(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        cmd = obj.delete_from_storage_cmd()
+        assert obj.get_nbt() in cmd
+
+    def test_set_to_current_cmd_returns_string(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        obj = compile_types.MCSNumber(ctx1)
+        cmd = obj.set_to_current_cmd(ctx2)
+        assert isinstance(cmd, str)
+
+    def test_set_to_current_cmd_contains_dest_storage(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        obj = compile_types.MCSNumber(ctx1)
+        cmd = obj.set_to_current_cmd(ctx2)
+        assert f"mcs_{FAKE_UUID_2}" in cmd
+
+    def test_set_to_current_cmd_contains_source_storage(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        obj = compile_types.MCSNumber(ctx1)
+        cmd = obj.set_to_current_cmd(ctx2)
+        assert f"mcs_{FAKE_UUID_1}" in cmd
+
+    def test_set_to_current_cmd_references_current_key(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        obj = compile_types.MCSNumber(ctx1)
+        cmd = obj.set_to_current_cmd(ctx2)
+        assert "current" in cmd
+
+    def test_set_to_current_cmd_uses_data_modify(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        obj = compile_types.MCSNumber(ctx1)
+        cmd = obj.set_to_current_cmd(ctx2)
+        assert "data modify storage" in cmd
+
+
+# ===========================================================================
+# MCSVariable
+# ===========================================================================
+
+class TestMCSVariable:
+    def test_get_nbt_uses_variable_prefix(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        var = compile_types.MCSVariable("my_var", ctx)
+        assert var.get_nbt() == "variable.my_var"
+
+    def test_get_storage_uses_context_uuid(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        var = compile_types.MCSVariable("x", ctx)
+        assert var.get_storage() == f"mcs_{FAKE_UUID_1}"
+
+    def test_set_to_current_cmd_returns_string(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        var = compile_types.MCSVariable("myvar", ctx1)
+        cmd = var.set_to_current_cmd(ctx2)
+        assert isinstance(cmd, str)
+
+    def test_set_to_current_cmd_references_dest_storage(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        var = compile_types.MCSVariable("myvar", ctx1)
+        cmd = var.set_to_current_cmd(ctx2)
+        assert f"mcs_{FAKE_UUID_2}" in cmd
+
+    def test_set_to_current_cmd_references_variable_nbt(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        var = compile_types.MCSVariable("myvar", ctx1)
+        cmd = var.set_to_current_cmd(ctx2)
+        assert "variable.myvar" in cmd
+
+    def test_set_to_current_uses_data_modify(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        var = compile_types.MCSVariable("v", ctx1)
+        cmd = var.set_to_current_cmd(ctx2)
+        assert "data modify storage" in cmd
+
+
+# ===========================================================================
+# MCSNull
+# ===========================================================================
+
+class TestMCSNull:
+    def test_save_to_storage_cmd_returns_string(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        null = compile_types.MCSNull(ctx)
+        cmd = null.save_to_storage_cmd()
+        assert isinstance(cmd, str)
+
+    def test_save_to_storage_cmd_contains_null_value(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        null = compile_types.MCSNull(ctx)
+        cmd = null.save_to_storage_cmd()
+        assert "0b" in cmd
+
+    def test_save_to_storage_cmd_uses_data_modify(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        null = compile_types.MCSNull(ctx)
+        cmd = null.save_to_storage_cmd()
+        assert "data modify storage" in cmd
+
+    def test_set_to_current_cmd_returns_string(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        null = compile_types.MCSNull(ctx1)
+        cmd = null.set_to_current_cmd(ctx2)
+        assert isinstance(cmd, str)
+
+    def test_set_to_current_cmd_sets_to_null(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        null = compile_types.MCSNull(ctx1)
+        cmd = null.set_to_current_cmd(ctx2)
+        assert "0b" in cmd
+
+    def test_set_to_current_cmd_targets_dest_storage(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        null = compile_types.MCSNull(ctx1)
+        cmd = null.set_to_current_cmd(ctx2)
+        assert f"mcs_{FAKE_UUID_2}" in cmd
+
+    def test_set_to_current_cmd_targets_current_nbt(self):
+        ctx1 = FakeContext(FAKE_UUID_1)
+        ctx2 = FakeContext(FAKE_UUID_2)
+        null = compile_types.MCSNull(ctx1)
+        cmd = null.set_to_current_cmd(ctx2)
+        assert "current" in cmd
+
+    def test_repr(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        null = compile_types.MCSNull(ctx)
+        assert repr(null) == "MCSNull()"
+
+
+# ===========================================================================
+# MCSList
+# ===========================================================================
+
+class TestMCSList:
+    def _make_simple_element(self, ctx, value=42):
+        """Create a MCSNumber element with a save command."""
+        elem = compile_types.MCSNumber(ctx)
+        return elem
+
+    def test_save_to_storage_cmd_returns_list(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        cmds = lst.save_to_storage_cmd([])
+        assert isinstance(cmds, list)
+
+    def test_save_empty_list_has_length_zero(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        cmds = lst.save_to_storage_cmd([])
+        # First command sets the length
+        assert "length" in cmds[0]
+        assert "0" in cmds[0]
+
+    def test_save_list_length_command_is_first(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        elem = compile_types.MCSNumber(ctx)
+        cmds = lst.save_to_storage_cmd([elem])
+        assert "length" in cmds[0]
+
+    def test_save_list_with_elements_produces_commands(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        elem1 = compile_types.MCSNumber(ctx)
+        elem2 = compile_types.MCSString(ctx)
+        cmds = lst.save_to_storage_cmd([elem1, elem2])
+        # length + 2*(set_to_current + element) = 5 total
+        assert len(cmds) == 5
+
+    def test_save_list_element_references_index_zero(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        elem = compile_types.MCSNumber(ctx)
+        cmds = lst.save_to_storage_cmd([elem])
+        # One of the commands should reference ".0"
+        assert any(".0" in cmd for cmd in cmds)
+
+    def test_save_list_element_command_uses_data_modify(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        elem = compile_types.MCSNumber(ctx)
+        cmds = lst.save_to_storage_cmd([elem])
+        assert all("data modify" in cmd or "data remove" in cmd for cmd in cmds if cmd)
+
+    def test_save_list_length_uses_version_template(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        cmds = lst.save_to_storage_cmd([])
+        # Template renders: data modify storage <storage> <nbt>.length set value <len>
+        assert "data modify storage" in cmds[0]
+        assert ".length" in cmds[0]
+
+    def test_repr(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        lst = compile_types.MCSList(ctx)
+        assert "MCSList" in repr(lst)
+
+
+# ===========================================================================
+# Various MCS types: storage compartments
+# ===========================================================================
+
+class TestMCSTypeCompartments:
+    """Verify each type uses the right storage compartment."""
+
+    def test_mcs_number_compartment(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNumber(ctx)
+        assert obj.get_nbt().startswith("number.")
+
+    def test_mcs_string_compartment(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSString(ctx)
+        assert obj.get_nbt().startswith("string.")
+
+    def test_mcs_boolean_compartment(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSBoolean(ctx)
+        assert obj.get_nbt().startswith("boolean.")
+
+    def test_mcs_unknown_compartment(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSUnknown(ctx)
+        assert obj.get_nbt().startswith("unknown.")
+
+    def test_mcs_null_compartment(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSNull(ctx)
+        assert obj.get_nbt().startswith("null.")
+
+    def test_mcs_list_compartment(self):
+        ctx = FakeContext(FAKE_UUID_1)
+        obj = compile_types.MCSList(ctx)
+        assert obj.get_nbt().startswith("list.")
\ No newline at end of file
diff --git a/tests/test_version_config.py b/tests/test_version_config.py
new file mode 100644
index 0000000..ce6b38a
--- /dev/null
+++ b/tests/test_version_config.py
@@ -0,0 +1,574 @@
+"""Tests for minecraft_script/version_config.py (new module added in this PR)."""
+import json
+import pytest
+from pathlib import Path
+from unittest.mock import patch, MagicMock
+
+import minecraft_script.version_config as vc
+from minecraft_script import common
+
+
+# ---------------------------------------------------------------------------
+# Helpers / fixtures
+# ---------------------------------------------------------------------------
+
+MINIMAL_PROFILE = {
+    "minecraft_version": "0.0.test",
+    "pack_format": 99,
+    "paths": {
+        "function_dir": "functions",
+        "function_tag_dir": "functions",
+    },
+    "constants": {
+        "testConst": "hello",
+    },
+    "templates": {
+        "simple": "hello {{name}}",
+        "multiline": "line1\nline2 {{value}}\nline3",
+        "no_params": "static text",
+        "with_const": "{{testConst}} world",
+        "datapack_ref": "function {{datapack_id}}:something",
+        "conditional": "{{#if flag}}yes{{else}}no{{/if}}",
+    },
+}
+
+
+# ===========================================================================
+# get_minecraft_version
+# ===========================================================================
+
+class TestGetMinecraftVersion:
+    def test_returns_value_from_common_config(self):
+        with patch.object(vc, "COMMON_CONFIG", {"minecraft_version": "1.20.4"}):
+            assert vc.get_minecraft_version() == "1.20.4"
+
+    def test_returns_different_version(self):
+        with patch.object(vc, "COMMON_CONFIG", {"minecraft_version": "1.99.0"}):
+            assert vc.get_minecraft_version() == "1.99.0"
+
+
+# ===========================================================================
+# load_version_profile
+# ===========================================================================
+
+class TestLoadVersionProfile:
+    def test_loads_real_1_20_4_profile(self):
+        profile = vc.load_version_profile("1.20.4")
+        assert profile["minecraft_version"] == "1.20.4"
+        assert profile["pack_format"] == 41
+        assert "templates" in profile
+        assert "paths" in profile
+
+    def test_profile_has_required_keys(self):
+        profile = vc.load_version_profile("1.20.4")
+        assert "paths" in profile
+        assert "function_dir" in profile["paths"]
+        assert "function_tag_dir" in profile["paths"]
+        assert "constants" in profile
+        assert "templates" in profile
+
+    def test_caches_profile_after_first_load(self):
+        vc._profile_cache.clear()
+        profile1 = vc.load_version_profile("1.20.4")
+        profile2 = vc.load_version_profile("1.20.4")
+        assert profile1 is profile2  # same object from cache
+
+    def test_uses_common_config_version_when_none_given(self):
+        vc._profile_cache.clear()
+        with patch.object(vc, "COMMON_CONFIG", {"minecraft_version": "1.20.4"}):
+            profile = vc.load_version_profile(None)
+        assert profile["minecraft_version"] == "1.20.4"
+
+    def test_raises_file_not_found_for_unknown_version(self):
+        with pytest.raises(FileNotFoundError) as exc_info:
+            vc.load_version_profile("9.99.99")
+        assert "9.99.99" in str(exc_info.value)
+        assert "Unknown minecraft version" in str(exc_info.value)
+
+    def test_error_message_includes_supported_versions(self):
+        with pytest.raises(FileNotFoundError) as exc_info:
+            vc.load_version_profile("9.99.99")
+        error_text = str(exc_info.value)
+        # Should mention supported versions or 'none'
+        assert "Supported:" in error_text
+
+    def test_uses_cached_profile_without_reading_file(self):
+        vc._profile_cache["fake_version"] = MINIMAL_PROFILE
+        profile = vc.load_version_profile("fake_version")
+        assert profile is MINIMAL_PROFILE
+
+
+# ===========================================================================
+# list_supported_versions
+# ===========================================================================
+
+class TestListSupportedVersions:
+    def test_returns_list(self):
+        versions = vc.list_supported_versions()
+        assert isinstance(versions, list)
+
+    def test_includes_1_20_4(self):
+        versions = vc.list_supported_versions()
+        assert "1.20.4" in versions
+
+    def test_excludes_index_json(self):
+        versions = vc.list_supported_versions()
+        assert "index" not in versions
+
+    def test_returns_sorted_list(self):
+        versions = vc.list_supported_versions()
+        assert versions == sorted(versions)
+
+    def test_returns_empty_when_versions_dir_missing(self, tmp_path):
+        nonexistent = tmp_path / "no_such_dir"
+        with patch.object(vc, "module_folder", str(tmp_path / "fake_module")):
+            result = vc.list_supported_versions()
+        assert result == []
+
+    def test_returns_only_json_stems(self):
+        versions = vc.list_supported_versions()
+        for v in versions:
+            assert "." in v or v.isidentifier()  # version strings should be reasonable
+
+
+# ===========================================================================
+# predefined_root
+# ===========================================================================
+
+class TestPredefinedRoot:
+    def test_returns_string(self):
+        result = vc.predefined_root("math", "1.20.4")
+        assert isinstance(result, str)
+
+    def test_path_contains_version(self):
+        result = vc.predefined_root("math", "1.20.4")
+        assert "1.20.4" in result
+
+    def test_path_contains_category(self):
+        result = vc.predefined_root("math", "1.20.4")
+        assert "math" in result
+
+    def test_path_ends_with_version_and_category(self):
+        result = vc.predefined_root("builtins", "1.20.4")
+        assert result.endswith("builtins/1.20.4")
+
+    def test_uses_current_version_when_none(self):
+        with patch.object(vc, "COMMON_CONFIG", {"minecraft_version": "1.20.4"}):
+            result = vc.predefined_root("tags")
+        assert "1.20.4" in result
+        assert "tags" in result
+
+    def test_explicit_version_overrides_config(self):
+        with patch.object(vc, "COMMON_CONFIG", {"minecraft_version": "1.20.4"}):
+            result = vc.predefined_root("math", "2.0.0")
+        assert "2.0.0" in result
+        assert "1.20.4" not in result
+
+    def test_math_path_exists_for_1_20_4(self):
+        result = vc.predefined_root("math", "1.20.4")
+        assert Path(result).is_dir()
+
+    def test_builtins_path_exists_for_1_20_4(self):
+        result = vc.predefined_root("builtins", "1.20.4")
+        assert Path(result).is_dir()
+
+    def test_tags_path_exists_for_1_20_4(self):
+        result = vc.predefined_root("tags", "1.20.4")
+        assert Path(result).is_dir()
+
+
+# ===========================================================================
+# VersionRenderer
+# ===========================================================================
+
+class TestVersionRenderer:
+    def setup_method(self):
+        self.renderer = vc.VersionRenderer(MINIMAL_PROFILE)
+
+    def test_render_simple_template(self):
+        result = self.renderer.render("simple", name="world")
+        assert result == "hello world"
+
+    def test_render_no_params_template(self):
+        result = self.renderer.render("no_params")
+        assert result == "static text"
+
+    def test_render_strips_whitespace(self):
+        profile = {**MINIMAL_PROFILE, "templates": {"padded": "  hello  "}}
+        renderer = vc.VersionRenderer(profile)
+        assert renderer.render("padded") == "hello"
+
+    def test_render_raises_key_error_for_missing_key(self):
+        with pytest.raises(KeyError) as exc_info:
+            self.renderer.render("nonexistent_key")
+        assert "nonexistent_key" in str(exc_info.value)
+
+    def test_render_raises_key_error_when_no_templates_key(self):
+        renderer = vc.VersionRenderer({"minecraft_version": "0.0"})
+        with pytest.raises(KeyError):
+            renderer.render("anything")
+
+    def test_render_compiles_template_once_and_caches(self):
+        self.renderer.render("simple", name="first")
+        compiled_before = dict(self.renderer._compiled)
+        self.renderer.render("simple", name="second")
+        assert set(self.renderer._compiled.keys()) == set(compiled_before.keys())
+
+    def test_render_lines_splits_on_newlines(self):
+        result = self.renderer.render_lines("multiline", value="test")
+        assert result == ["line1", "line2 test", "line3"]
+
+    def test_render_lines_single_line(self):
+        result = self.renderer.render_lines("simple", name="x")
+        assert result == ["hello x"]
+
+    def test_render_lines_returns_empty_list_for_empty_output(self):
+        profile = {**MINIMAL_PROFILE, "templates": {"empty": ""}}
+        renderer = vc.VersionRenderer(profile)
+        result = renderer.render_lines("empty")
+        assert result == []
+
+    def test_render_conditional_true(self):
+        result = self.renderer.render("conditional", flag=True)
+        assert result == "yes"
+
+    def test_render_conditional_false(self):
+        result = self.renderer.render("conditional", flag=False)
+        assert result == "no"
+
+
+# ===========================================================================
+# VersionContext
+# ===========================================================================
+
+class TestVersionContext:
+    def test_init_with_explicit_profile(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        assert ctx.datapack_id == "my_pack"
+        assert ctx.profile is MINIMAL_PROFILE
+
+    def test_init_reads_function_dir_from_profile(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        assert ctx.function_dir == "functions"
+
+    def test_init_reads_function_tag_dir_from_profile(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        assert ctx.function_tag_dir == "functions"
+
+    def test_init_sets_pack_format_as_string(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        assert ctx.pack_format == "99"
+        assert isinstance(ctx.pack_format, str)
+
+    def test_init_loads_real_profile_when_none_given(self):
+        vc._profile_cache.clear()
+        with patch.object(vc, "COMMON_CONFIG", {"minecraft_version": "1.20.4"}):
+            ctx = vc.VersionContext("dp")
+        assert ctx.profile["minecraft_version"] == "1.20.4"
+
+    def test_params_merges_constants(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        params = ctx._params(extra="val")
+        assert params["testConst"] == "hello"
+        assert params["extra"] == "val"
+
+    def test_params_adds_datapack_id(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        params = ctx._params()
+        assert params["datapack_id"] == "my_pack"
+
+    def test_params_caller_overrides_datapack_id(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        params = ctx._params(datapack_id="override")
+        assert params["datapack_id"] == "override"
+
+    def test_params_caller_overrides_constant(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        params = ctx._params(testConst="overridden")
+        assert params["testConst"] == "overridden"
+
+    def test_render_injects_datapack_id(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        result = ctx.render("datapack_ref")
+        assert result == "function my_pack:something"
+
+    def test_render_injects_constants(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        result = ctx.render("with_const")
+        assert result == "hello world"
+
+    def test_render_passes_caller_params(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        result = ctx.render("simple", name="earth")
+        assert result == "hello earth"
+
+    def test_render_lines_returns_list(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        result = ctx.render_lines("multiline", value="x")
+        assert isinstance(result, list)
+        assert len(result) == 3
+
+    def test_render_lines_splits_correctly(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        result = ctx.render_lines("multiline", value="abc")
+        assert result[1] == "line2 abc"
+
+    def test_render_raises_key_error_for_missing_template(self):
+        ctx = vc.VersionContext("my_pack", profile=MINIMAL_PROFILE)
+        with pytest.raises(KeyError) as exc_info:
+            ctx.render("nonexistent")
+        assert "nonexistent" in str(exc_info.value)
+
+
+# ===========================================================================
+# 1.20.4 profile templates (smoke tests for actual content)
+# ===========================================================================
+
+class TestRealProfile1204Templates:
+    """Smoke tests verifying key templates in the 1.20.4 profile render correctly."""
+
+    def setup_method(self):
+        vc._profile_cache.clear()
+        self.ctx = vc.VersionContext("my_dp", profile=vc.load_version_profile("1.20.4"))
+
+    def test_datapack_init_contains_scoreboard(self):
+        result = self.ctx.render("datapack.init")
+        assert "scoreboard objectives add mcs_math" in result
+
+    def test_datapack_init_references_datapack_id(self):
+        result = self.ctx.render("datapack.init")
+        assert "my_dp" in result
+
+    def test_datapack_kill_contains_datapack_name(self):
+        result = self.ctx.render("datapack.kill", datapackName="test_dp_name")
+        assert "test_dp_name" in result
+
+    def test_datapack_main_references_datapack_id(self):
+        result = self.ctx.render("datapack.main")
+        assert "my_dp" in result
+
+    def test_literal_save_template(self):
+        result = self.ctx.render("literal.save", storage="my_storage", nbt="my.nbt", value=42)
+        assert "data modify storage my_storage my.nbt set value 42" in result
+
+    def test_literal_delete_template(self):
+        result = self.ctx.render("literal.delete", storage="my_storage", nbt="my.nbt")
+        assert "data remove storage my_storage my.nbt" in result
+
+    def test_literal_set_to_current_template(self):
+        result = self.ctx.render(
+            "literal.set_to_current",
+            destStorage="dest_store",
+            storage="src_store",
+            nbt="my.nbt",
+        )
+        assert "data modify storage dest_store current set from storage src_store my.nbt" in result
+
+    def test_variable_declare_template(self):
+        result = self.ctx.render(
+            "variable.declare",
+            ownerStorage="mcs_abc",
+            name="myVar",
+            valueStorage="mcs_xyz",
+            valueNbt="number.123",
+        )
+        assert "data modify storage mcs_abc variable.myVar set from storage mcs_xyz number.123" in result
+
+    def test_math_binary_template(self):
+        result = self.ctx.render(
+            "math.binary",
+            leftStorage="mcs_l",
+            leftNbt="number.1",
+            rightStorage="mcs_r",
+            rightNbt="number.2",
+            operation="add",
+            resultStorage="mcs_res",
+            resultNbt="number.out",
+        )
+        lines = result.split("\n")
+        assert any("score .a mcs_math" in l for l in lines)
+        assert any("score .b mcs_math" in l for l in lines)
+        assert any("math/add" in l for l in lines)
+
+    def test_math_unary_template(self):
+        result = self.ctx.render(
+            "math.unary",
+            rootStorage="mcs_r",
+            rootNbt="number.1",
+            operation="u_not",
+            resultStorage="mcs_res",
+            resultNbt="boolean.out",
+        )
+        assert "math/u_not" in result
+
+    def test_control_if_branch_template(self):
+        result = self.ctx.render(
+            "control.if.branch",
+            exprStorage="mcs_e",
+            exprNbt="boolean.1",
+            branchStorage="mcs_b",
+            parentStorage="mcs_p",
+            branchPath="code_blocks/foo",
+        )
+        assert ".out mcs_math" in result
+        assert "return 0" in result
+
+    def test_control_for_init_template(self):
+        result = self.ctx.render(
+            "control.for.init",
+            loopId="loop123",
+            iterableStorage="mcs_i",
+            iterableNbt="list.1",
+            loopPath="code_blocks/loop",
+        )
+        assert "loop_iter_loop123" in result
+        assert "loop_end_loop123" in result
+
+    def test_kill_remove_compartment_template(self):
+        result = self.ctx.render(
+            "kill.remove_compartment",
+            ctxStorage="mcs_abc123",
+            compartment="number",
+        )
+        assert "data remove storage mcs_abc123 number" in result
+
+    def test_function_call_invoke_template(self):
+        result = self.ctx.render("function.call.invoke", functionName="my_func")
+        assert "my_dp:user_functions/my_func" in result
+
+    def test_click_check_template(self):
+        result = self.ctx.render("click.check")
+        assert "mcs_click" in result
+        assert "my_dp" in result
+
+    def test_builtin_log_template(self):
+        result = self.ctx.render("builtin.log", storageSuffix=" {s0: foo}")
+        assert "builtins/log" in result
+        assert "my_dp" in result
+
+    def test_builtin_give_item_setup_with_components(self):
+        result = self.ctx.render(
+            "builtin.give_item.setup",
+            ctxStorage="mcs_ctx",
+            itemStorage="mcs_item",
+            itemNbt="string.1",
+            hasComponents=True,
+            componentsStorage="mcs_comp",
+            componentsNbt="string.2",
+            hasCount=False,
+        )
+        assert "current.item" in result
+        assert "current.components set from" in result
+        assert "builtins/give_item" in result
+
+    def test_builtin_give_item_setup_without_components(self):
+        result = self.ctx.render(
+            "builtin.give_item.setup",
+            ctxStorage="mcs_ctx",
+            itemStorage="mcs_item",
+            itemNbt="string.1",
+            hasComponents=False,
+            hasCount=False,
+        )
+        assert "current.components set value ''" in result
+
+    def test_builtin_give_item_setup_with_count(self):
+        result = self.ctx.render(
+            "builtin.give_item.setup",
+            ctxStorage="mcs_ctx",
+            itemStorage="mcs_item",
+            itemNbt="string.1",
+            hasComponents=False,
+            hasCount=True,
+            countStorage="mcs_count",
+            countNbt="number.3",
+        )
+        assert "current.count set from" in result
+
+    def test_builtin_give_item_setup_default_count(self):
+        result = self.ctx.render(
+            "builtin.give_item.setup",
+            ctxStorage="mcs_ctx",
+            itemStorage="mcs_item",
+            itemNbt="string.1",
+            hasComponents=False,
+            hasCount=False,
+        )
+        assert "current.count set value 1" in result
+
+    def test_pack_mcmeta_contains_pack_format(self):
+        result = self.ctx.render("pack.mcmeta", pack_format=41)
+        assert "41" in result
+        assert "pack_format" in result
+
+    def test_builtin_raycast_block_loop_has_no_loop_function(self):
+        result = self.ctx.render(
+            "builtin.raycast_block.loop",
+            raycastId="abc",
+            hitFunction="my_hit",
+            hasLoopFunction=False,
+            loopFunction="",
+            loopPath="code_blocks/rc",
+        )
+        assert "# No loop function" in result
+
+    def test_builtin_raycast_block_loop_with_loop_function(self):
+        result = self.ctx.render(
+            "builtin.raycast_block.loop",
+            raycastId="abc",
+            hitFunction="my_hit",
+            hasLoopFunction=True,
+            loopFunction="my_loop",
+            loopPath="code_blocks/rc",
+        )
+        assert "user_functions/my_loop" in result
+        assert "# No loop function" not in result
+
+
+# ===========================================================================
+# init_version_context / get_version_context / clear_version_context
+# ===========================================================================
+
+class TestVersionContextLifecycle:
+    def test_init_version_context_returns_context(self):
+        ctx = vc.init_version_context("test_pack")
+        assert isinstance(ctx, vc.VersionContext)
+
+    def test_init_version_context_sets_datapack_id(self):
+        ctx = vc.init_version_context("my_dp_id")
+        assert ctx.datapack_id == "my_dp_id"
+
+    def test_get_version_context_returns_same_object(self):
+        ctx = vc.init_version_context("dp")
+        got = vc.get_version_context()
+        assert got is ctx
+
+    def test_get_version_context_raises_when_not_initialized(self):
+        vc.clear_version_context()
+        with pytest.raises(RuntimeError) as exc_info:
+            vc.get_version_context()
+        assert "not initialized" in str(exc_info.value).lower()
+
+    def test_clear_version_context_causes_get_to_raise(self):
+        vc.init_version_context("dp")
+        vc.clear_version_context()
+        with pytest.raises(RuntimeError):
+            vc.get_version_context()
+
+    def test_init_replaces_existing_context(self):
+        ctx1 = vc.init_version_context("dp1")
+        ctx2 = vc.init_version_context("dp2")
+        assert vc.get_version_context() is ctx2
+        assert ctx1 is not ctx2
+
+    def test_clear_allows_re_init(self):
+        vc.init_version_context("dp")
+        vc.clear_version_context()
+        ctx = vc.init_version_context("dp2")
+        assert vc.get_version_context() is ctx
+
+    def test_get_raises_with_helpful_message(self):
+        vc.clear_version_context()
+        with pytest.raises(RuntimeError) as exc_info:
+            vc.get_version_context()
+        assert "init_version_context" in str(exc_info.value)
