An accepts axis rolls one tag per pick (read as $var.AXIS), but a constraint sourced from it folded over the option's whole menu instead of that rolled tag, so $outfit.SHOES and the constrained $shoes could disagree. The source now sends only the rolled winner; a target accepts axis OR-folds so a multi-tag option survives when any alternative is allowed, then its own roll is pinned to the allowed tag -- $source.AXIS == $target.AXIS under a diagonal. Classify groups are unchanged (multiply). The assembler also rendered $var.AXIS empty: __wp_axes__ crosses the socket on ContextPayload.internals but the node only merged the internal-flag map; it now merges the whole carve-out so the accessor resolves. Import: the community validators silently dropped tag_group_kinds (and the constraint target_select) because the lenient payload object never listed them, degrading a shared accepts axis to classify. Both are now in the v2/v1 validators + the engine-parity fixtures, with preservation tests. Engine math mirrored in TS via the shared corpus.
33 lines
1.4 KiB
Python
33 lines
1.4 KiB
Python
import json
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
from engine.modules._constraint_math import EXCLUDE, combine_constraint_factor
|
|
from engine.modules.derivation_handler import branch_carrier_key
|
|
|
|
_CORPUS_PATH = pathlib.Path(__file__).parents[3] / "tests/fixtures/constraint-corpus.json"
|
|
CORPUS = json.loads(_CORPUS_PATH.read_text())
|
|
|
|
@pytest.mark.parametrize("case", CORPUS["cases"], ids=lambda c: c["name"])
|
|
def test_corpus(case):
|
|
# `source_axis_kinds` is absent on every pre-axis case, and absence must
|
|
# reproduce the old fold exactly — that is what makes the feature additive.
|
|
got = combine_constraint_factor(
|
|
case["picks"], case["option"], case["matrix"], case["exceptions"],
|
|
axis_kinds=case.get("source_axis_kinds"),
|
|
target_axes=case.get("target_axes"))
|
|
if case["expect"] == "EXCLUDE":
|
|
assert got is EXCLUDE
|
|
else:
|
|
assert got == pytest.approx(case["expect"])
|
|
|
|
|
|
# Py≡TS branch-key parity: the carrier key the engine stamps for a derivation
|
|
# branch occurrence MUST be byte-identical to the key TS computes
|
|
# (constraint-pairs.ts `branchKey`). The shared corpus is the single source of
|
|
# truth so the two formats cannot drift independently.
|
|
@pytest.mark.parametrize("case", CORPUS["branch_key_cases"], ids=lambda c: c["name"])
|
|
def test_branch_key_corpus(case):
|
|
assert branch_carrier_key(case["rule_id"], case["branch"]) == case["key"]
|