From 2860f822150d4ef505ef3b8743aaf56a15ff9e4c Mon Sep 17 00:00:00 2001 From: MEEPofFaith <54301439+MEEPofFaith@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:57:04 -0700 Subject: [PATCH 1/5] Swizzling special handler --- data/src/HexBug/data/special_handlers.py | 91 ++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/data/src/HexBug/data/special_handlers.py b/data/src/HexBug/data/special_handlers.py index 35defee05c..ec2c6ac65a 100644 --- a/data/src/HexBug/data/special_handlers.py +++ b/data/src/HexBug/data/special_handlers.py @@ -18,6 +18,7 @@ VALID_MASK_PATTERN = re.compile(r"[-v]+") VALID_HEXFLOW_COPY_MASK_PATTERN = re.compile(r"[-n]+") +VALID_SWIZZLE_PATTERN = re.compile(r"[xyz]{3}") class SpecialHandlerInfo(BaseModel): @@ -657,3 +658,93 @@ def parse_tail_depth(value: str): if all(c == "-" for c in value): return len(value) raise ValueError(f"Invalid tail depth (expected an integer or dashes): {value}") + +class HextrapatsSwizzlingSpecialHandler(PrefixSpecialHandler[int, Any]): + prefix = "eeeeqaawddea" + + @property + @override + def prefix_map(self): + return {self.prefix: None} + + @override + def try_match(self, registry: HexBugRegistry, pattern: HexPattern) -> str | None: + for prefix, value in self.prefix_map.items(): + if pattern.signature.startswith(prefix): + flat_dir = pattern.direction.rotated_by(HexAngle.BACK) + + dirs = pattern.iter_directions() + result = "" + side = 0 + + # Skip the first 13 directions in the worst way possible :hexxy: + dirs.next() for _ in range(13) + + for direction in dirs: + match direction.angle_from(flat_dir): + case HexAngle.FORWARD if side == 0: + result += "Y" + case HexAngle.RIGHT if side == 0: + side = 1 + elif side == -1: + side = 0 + result += "X" + case HexAngle.LEFT if side == 0: + side = -1 + elif side == 1: + side = 0 + result += "Z" + case _: + return None + + if side != 0 or result.length != 3: + return None + + return result + + def generate_pattern( + self, + registry: HexBugRegistry, + value: str, + ) -> tuple[int, HexPattern]: + value = value.lower().strip() + if not VALID_SWIZZLE_PATTERN.fullmatch(value): + raise ValueError(f"Invalid swizzle (expected exactly 3 of x, y, or z): {value}") + + signature = self.prefix + current = HexDir.SOUTH_EAST + + # Surely there's a better way to do this + for c in value: + match c: + case "x": + match current: + case HexDir.NORTH_EAST: + signature += "wd" + case HexDir.EAST: + signature += "qd" + case HexDir.SOUTH_EAST: + signature += "ad" + current = HexDir.SOUTH_EAST + case "y": + match current: + case HexDir.NORTH_EAST: + signature += "e" + case HexDir.EAST: + signature += "w" + case HexDir.SOUTH_EAST: + signature += "q" + current = HexDir.EAST + case "z": + match current: + case HexDir.NORTH_EAST: + signature += "da" + case HexDir.EAST: + signature += "ea" + case HexDir.SOUTH_EAST: + signature += "wa" + current = HexDir.NORTH_EAST + case _: + raise RuntimeError("unreachable") + + return value, HexPattern(HexDir.WEST, signature) From fb05915a092d453bddca1feb04a355f3b6b1ee15 Mon Sep 17 00:00:00 2001 From: MEEPofFaith <54301439+MEEPofFaith@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:11:04 -0700 Subject: [PATCH 2/5] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a16355f750..a0769c6849 100644 --- a/.gitignore +++ b/.gitignore @@ -199,3 +199,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# VSCode +.vs/ From efcf33151a44e082ad87286765c4b2c5f421617b Mon Sep 17 00:00:00 2001 From: MEEPofFaith <54301439+MEEPofFaith@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:08:51 -0700 Subject: [PATCH 3/5] Unit test --- data/test/data/test_special_handlers.py | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/data/test/data/test_special_handlers.py b/data/test/data/test_special_handlers.py index e75b97d003..0b9c96a864 100644 --- a/data/test/data/test_special_handlers.py +++ b/data/test/data/test_special_handlers.py @@ -7,6 +7,7 @@ from HexBug.data.registry import HexBugRegistry from HexBug.data.special_handlers import ( HexFlowCopyMaskSpecialHandler, + HextrapatsSwizzlingSpecialHandler, MaskSpecialHandler, ) @@ -130,3 +131,49 @@ def generate_pattern( registry = cast(HexBugRegistry, None) # lie _, pattern = special_handler.generate_pattern(registry, value) assert pattern.signature == want_signature + + +def describe_HextrapatsSwizzlingSpecialHandler(): + @pytest.fixture() + def special_handler() -> HextrapatsSwizzlingSpecialHandler: + return HextrapatsSwizzlingSpecialHandler( + id=ResourceLocation("hextrapats", "vec/swizzle"), + ) + + patterns = [ + ("eeeeqaawddeawdadad", "XXX"), + ("eeeeqaawddeaeww", "YYY"), + ("eeeeqaawddeadadada", "ZZZ"), + ("eeeeqaawddeawdqea", "XYZ"), + ("eeeeqaawddeaeeae", "YZY"), + ("eeeeqaawddeawdadwa", "XXZ"), + ] + + @pytest.mark.parametrize("direction", HexDir) + @pytest.mark.parametrize( + ["signature", "want_value"], + [*patterns, ("eeeeqaawddeadaww", None), ("eeeeqaawddeaeweawd", None)], + ids=lambda v: f"'{v}'", + ) + def try_match( + special_handler: HextrapatsSwizzlingSpecialHandler, + direction: HexDir, + signature: str, + want_value: str | None, + ): + registry = cast(HexBugRegistry, None) # lie + pattern = HexPattern(direction, signature) + assert special_handler.try_match(registry, pattern) == want_value + + @pytest.mark.parametrize( + ["value", "want_signature"], + [(value, signature) for (signature, value) in patterns], + ) + def generate_pattern( + special_handler: HextrapatsSwizzlingSpecialHandler, + value: str, + want_signature: str, + ): + registry = cast(HexBugRegistry, None) # lie + _, pattern = special_handler.generate_pattern(registry, value) + assert pattern.signature == want_signature From 5508f8f00bdcf558ef888e6a25f0f0f50bdd3fb5 Mon Sep 17 00:00:00 2001 From: MEEPofFaith <54301439+MEEPofFaith@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:09:34 -0700 Subject: [PATCH 4/5] Bug fixes --- data/src/HexBug/data/special_handlers.py | 84 ++++++++++++------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/data/src/HexBug/data/special_handlers.py b/data/src/HexBug/data/special_handlers.py index ec2c6ac65a..0ae6343368 100644 --- a/data/src/HexBug/data/special_handlers.py +++ b/data/src/HexBug/data/special_handlers.py @@ -659,60 +659,58 @@ def parse_tail_depth(value: str): return len(value) raise ValueError(f"Invalid tail depth (expected an integer or dashes): {value}") -class HextrapatsSwizzlingSpecialHandler(PrefixSpecialHandler[int, Any]): + +class HextrapatsSwizzlingSpecialHandler(SpecialHandler[str]): prefix = "eeeeqaawddea" - @property - @override - def prefix_map(self): - return {self.prefix: None} - @override def try_match(self, registry: HexBugRegistry, pattern: HexPattern) -> str | None: - for prefix, value in self.prefix_map.items(): - if pattern.signature.startswith(prefix): - flat_dir = pattern.direction.rotated_by(HexAngle.BACK) - - dirs = pattern.iter_directions() - result = "" - side = 0 - - # Skip the first 13 directions in the worst way possible :hexxy: - dirs.next() for _ in range(13) - - for direction in dirs: - match direction.angle_from(flat_dir): - case HexAngle.FORWARD if side == 0: - result += "Y" - case HexAngle.RIGHT if side == 0: - side = 1 - elif side == -1: - side = 0 - result += "X" - case HexAngle.LEFT if side == 0: - side = -1 - elif side == 1: - side = 0 - result += "Z" - case _: - return None - - if side != 0 or result.length != 3: - return None - - return result - + if pattern.signature.startswith(self.prefix): + flat_dir = pattern.direction.rotated_by(HexAngle.BACK) + + dirs = pattern.iter_directions() + result = "" + side = 0 + + dirs = itertools.islice(dirs, 13, None) + + for direction in dirs: + match direction.angle_from(flat_dir): + case HexAngle.FORWARD if side == 0: + result += "Y" + case HexAngle.RIGHT: + if side == 0: + side = 1 + elif side == -1: + side = 0 + result += "X" + case HexAngle.LEFT: + if side == 0: + side = -1 + elif side == 1: + side = 0 + result += "Z" + case _: + return None + + if side != 0 or len(result) != 3: + return None + + return result + def generate_pattern( self, registry: HexBugRegistry, value: str, - ) -> tuple[int, HexPattern]: + ) -> tuple[str, HexPattern]: value = value.lower().strip() if not VALID_SWIZZLE_PATTERN.fullmatch(value): - raise ValueError(f"Invalid swizzle (expected exactly 3 of x, y, or z): {value}") - + raise ValueError( + f"Invalid swizzle (expected exactly 3 of x, y, or z): {value}" + ) + signature = self.prefix - current = HexDir.SOUTH_EAST + current = HexDir.NORTH_EAST # Surely there's a better way to do this for c in value: From a9769ef2b0ab44f0809c3e6c60fa424cefe171a2 Mon Sep 17 00:00:00 2001 From: MEEPofFaith <54301439+MEEPofFaith@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:12:31 -0700 Subject: [PATCH 5/5] Clean that up --- data/src/HexBug/data/special_handlers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/data/src/HexBug/data/special_handlers.py b/data/src/HexBug/data/special_handlers.py index 0ae6343368..20b6883cf7 100644 --- a/data/src/HexBug/data/special_handlers.py +++ b/data/src/HexBug/data/special_handlers.py @@ -668,13 +668,10 @@ def try_match(self, registry: HexBugRegistry, pattern: HexPattern) -> str | None if pattern.signature.startswith(self.prefix): flat_dir = pattern.direction.rotated_by(HexAngle.BACK) - dirs = pattern.iter_directions() result = "" side = 0 - dirs = itertools.islice(dirs, 13, None) - - for direction in dirs: + for direction in itertools.islice(pattern.iter_directions(), 13, None): match direction.angle_from(flat_dir): case HexAngle.FORWARD if side == 0: result += "Y"