get_axis sits beside get_var as a plain dict lookup over a projection build_resolve_ctx makes at frame construction, so the resolver stays a pure consumer with no ctx dependency. Keyed by binding, matching what the reference addresses. Branches before the var lookup: $outfit.SHOES must resolve even when nothing bound $outfit itself, and must not trip unknown_var on the way past. Never routes through deref_var_value either - an axis value is not a ListVar, so the SP2a accessor contract is untouched. A missing axis renders empty and warns unknown_tag_axis rather than failing, matching how every other unresolvable reference behaves.
80 lines
2.8 KiB
Python
80 lines
2.8 KiB
Python
"""`$outfit.SHOES` reads the tag rolled at pick time — it never re-rolls.
|
|
|
|
That is the whole consistency guarantee: two modules, or two chained nodes,
|
|
read the same stored value rather than each drawing their own.
|
|
"""
|
|
import random
|
|
|
|
from engine.modules import build_resolve_ctx
|
|
from engine.syntax import resolve_text
|
|
|
|
|
|
def _ctx(axes):
|
|
return {
|
|
"__wp_rng__": random.Random(0),
|
|
"__wp_warnings__": [],
|
|
"__wp_axes__": axes,
|
|
"outfit": "white t-shirt",
|
|
}
|
|
|
|
|
|
def test_single_pick_axis_reads_the_rolled_tag():
|
|
rc = build_resolve_ctx(_ctx({"outfit": {"SHOES": "sandals"}}), surface="combine")
|
|
assert resolve_text("wearing $outfit.SHOES", rc) == "wearing sandals"
|
|
|
|
|
|
def test_multi_pick_bare_read_joins():
|
|
rc = build_resolve_ctx(
|
|
_ctx({"outfit": [{"SHOES": "sandals"}, {"SHOES": "sneakers"}]}),
|
|
surface="combine",
|
|
)
|
|
assert resolve_text("$outfit.SHOES", rc) == "sandals, sneakers"
|
|
|
|
|
|
def test_multi_pick_index_selects_one_in_either_order():
|
|
rc = build_resolve_ctx(
|
|
_ctx({"outfit": [{"SHOES": "sandals"}, {"SHOES": "sneakers"}]}),
|
|
surface="combine",
|
|
)
|
|
assert resolve_text("$outfit.1.SHOES", rc) == "sneakers"
|
|
assert resolve_text("$outfit.SHOES.1", rc) == "sneakers"
|
|
|
|
|
|
def test_multi_pick_index_out_of_range_is_empty():
|
|
rc = build_resolve_ctx(
|
|
_ctx({"outfit": [{"SHOES": "sandals"}]}), surface="combine",
|
|
)
|
|
assert resolve_text("$outfit.4.SHOES", rc) == ""
|
|
|
|
|
|
def test_single_pick_behaves_as_a_one_element_list():
|
|
# Mirrors deref_var_value's contract so the two accessors agree.
|
|
rc = build_resolve_ctx(_ctx({"outfit": {"SHOES": "sandals"}}), surface="combine")
|
|
assert resolve_text("$outfit.0.SHOES", rc) == "sandals"
|
|
assert resolve_text("$outfit.9.SHOES", rc) == ""
|
|
|
|
|
|
def test_reading_the_same_axis_twice_gives_one_answer():
|
|
# The point of rolling at pick time. A lazy roll would make these differ.
|
|
rc = build_resolve_ctx(_ctx({"outfit": {"SHOES": "sandals"}}), surface="combine")
|
|
assert resolve_text("$outfit.SHOES / $outfit.SHOES", rc) == "sandals / sandals"
|
|
|
|
|
|
def test_unknown_axis_renders_empty_and_warns():
|
|
c = _ctx({"outfit": {"SHOES": "sandals"}})
|
|
rc = build_resolve_ctx(c, surface="combine")
|
|
assert resolve_text("$outfit.BELTS", rc) == ""
|
|
assert any(w["type"] == "unknown_tag_axis" for w in c["__wp_warnings__"])
|
|
|
|
|
|
def test_axis_on_a_binding_that_rolled_nothing_warns():
|
|
c = _ctx({})
|
|
rc = build_resolve_ctx(c, surface="combine")
|
|
assert resolve_text("$outfit.SHOES", rc) == ""
|
|
assert any(w["type"] == "unknown_tag_axis" for w in c["__wp_warnings__"])
|
|
|
|
|
|
def test_plain_var_read_is_untouched():
|
|
rc = build_resolve_ctx(_ctx({"outfit": {"SHOES": "sandals"}}), surface="combine")
|
|
assert resolve_text("$outfit", rc) == "white t-shirt"
|