From fa003077bddf5127c6cffe809d32a24004ca553f Mon Sep 17 00:00:00 2001 From: sp00ktober <79335616+sp00ktober@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:57:13 +0200 Subject: [PATCH 1/2] feat(ports): the Zinogre's walks play derived own moves, not em75's (1, 0) The donor's walks (2,12) to (2,25) mapped to em75's (1, 0), which parked for good live (D1). Each now derives its own move on its walk clip; the twins (2,13), (2,14) share (2,12)'s and (2,24), (2,25) share (2,23)'s. 89 own moves of 96 slots take 4642 of the 5632 turn keys. Co-Authored-By: Claude Sonnet 5.5 --- .../mhfu-port/tests/test_zinogre_brain.py | 41 +++++++++++++++-- ports/README.md | 7 +-- ports/zinogre.toml | 45 ++++--------------- 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/packages/mhfu-port/tests/test_zinogre_brain.py b/packages/mhfu-port/tests/test_zinogre_brain.py index 8ed9331c..22633755 100644 --- a/packages/mhfu-port/tests/test_zinogre_brain.py +++ b/packages/mhfu-port/tests/test_zinogre_brain.py @@ -64,7 +64,7 @@ def test_the_manifest_carries_its_brain(zinogre): assert (b.donor, b.splice, b.entry, b.fatigue, b.charge) == (40, "base_3", "base_3", True, True) assert b.when == {"if_target_set": True, "if_target_state": True} assert manifest.loads(manifest.dumps(m)) == m - assert len(b.pairs) == 104 and len(b.derived()) == len(own.pairs(b)) == 34 + assert len(b.pairs) == 104 and len(b.derived()) == len(own.pairs(b)) == 39 def test_a_donor_pair_plays_the_authored_move_where_it_is_the_donors(zinogre): @@ -87,6 +87,26 @@ def test_a_wrapper_turns_and_plays_the_move_of_the_pair_it_leads_to(zinogre): assert (7, 13) in b.shared() and b.targets((7, 84)) == [TURN, "p3_7_13"] +WALKS = { + (2, 12): "p3_2_12", + (2, 13): "p3_2_12", + (2, 14): "p3_2_12", + (2, 16): "p3_2_16", + (2, 17): "p3_2_17", + (2, 21): "p3_2_21", + (2, 23): "p3_2_23", + (2, 24): "p3_2_23", + (2, 25): "p3_2_23", +} +"""The donor pairs that walk, with the move each plays (the host's (1, 0) parked live: D1).""" + + +def test_the_walks_play_own_moves_not_the_host_pair_1_0(zinogre): + b = zinogre.m.brain + assert not [k for k in b.pairs if (1, 0) in b.targets(manifest.pair_of(k))] + assert {p: b.targets(p) for p in WALKS} == {p: [name] for p, name in WALKS.items()} + + def chain_of(m: manifest.Manifest, name: str) -> list[tuple[object, ...]]: """What a derived move plays, step by step: clip, attacks, steer, length.""" out = [] @@ -106,7 +126,7 @@ def test_the_pairs_that_share_a_move_derive_the_same_one(zinogre, data): named = {k: v for k, v in b.pairs.items() if isinstance(v, str)} twins = {k: v for k, v in named.items() if v in b.derived()} twins |= {"7,71": "p3_7_13", "7,82": "p3_7_13", "7,119": "p3_7_13"} - assert len(twins) == 17 + assert len(twins) == 21 apart = manifest.load(PORT) apart.brain.pairs |= {k: manifest.OWN for k in twins} again, _ = own.expand(apart, data) @@ -210,6 +230,20 @@ def test_zinogre_reads_tired_from_gauge_a(zinogre): assert not any(n.op.name in ("if_tired", "if_stamina_low") for n in tests) +def test_zinogre_walks_enter_their_own_moves(zinogre): + s, _ = splice_of(zinogre, zinogre.m.brain) + for pair, name in WALKS.items(): + assert s.uses[pair, name] > 0 and not [t for (p, t) in s.uses if p == pair and t != name] + nodes = [n for i in s.edits.inserts.values() for n in edit.everything(i.nodes)] + nodes += [n for r in s.edits.routines.values() for n in edit.everything(r)] + pairs = { + (n.args["main"], n.args["sub"]) + for n in nodes + if isinstance(n, ir.Insn) and n.op.name == "enter" + } + assert (1, 0) not in pairs and set(WALKS.values()) <= set(s.slots) + + def test_zinogre_enters_its_own_moves(zinogre): s, built = splice_of(zinogre, zinogre.m.brain) own_enters = { @@ -331,7 +365,7 @@ def test_the_own_moves_fit_the_framework(zinogre, module_text): _, built = module_text names = [n for n, mv in zinogre.m.moves.items() if mv.own] keys = sum(len(moves.curve(zinogre.m, n, built.layout) or "") // 4 for n in names) - assert len(names) == 83 and keys == 4367 + assert len(names) == 89 and keys == 4642 assert len(names) <= moves.SLOTS and keys <= moves.POOL assert not moves.problems(zinogre.m, built.layout, moves.records(Data.find().fu, 75)) @@ -342,4 +376,5 @@ def test_the_cli_splices_the_port(data, capsys): out = capsys.readouterr().out assert "routines added" in out and "worst run" in out assert " charge: charge_up 40/55/65/100, p3_1_15 25/35/40/55, " in out + assert " pair 2,12 -> own p3_2_12 (slot " in out and " pair 2,25 -> own p3_2_23 (slot " in out assert " pair 7,23 -> 1,2 x" in out and " pair 7,23 -> own p3_7_0 (slot " in out diff --git a/ports/README.md b/ports/README.md index 9fb6e4e2..e090ba79 100644 --- a/ports/README.md +++ b/ports/README.md @@ -204,12 +204,13 @@ port with them; `mhfu move play --own NAME [--force]` plays one of its own moves The Zinogre's moves are the donor's: `[brain]` splices MHP3rd's own script (`base_3`, its charged picker `base_6` and its normal one `base_5`) into em75's brain, so what he picks, when he charges up (the helper's gauge, `[brain] charge`) and when he is tired (`[brain] fatigue`) is the donor's. -A donor pair plays a host pair (a turn, a walk), the authored move that is the donor's +A donor pair plays a host pair (a turn), the authored move that is the donor's (`charge_up`, `dash`, `dash_stop`, `break_howl`) or an own move derived from the donor (`"own"`); a pair that only turns, then enters another, turns on em75's (1, 2) and plays the move of the pair it leads to; pairs whose derived moves come out identical (twins) share the -first one's. His 83 own moves, the 14 authored and the 69 derived, take 4367 of the registry's -5632 turn keys and 83 of its 96 slots. +first one's. The donor's walks are derived moves on their own walk clips, not em75's (1, 0), +which parked for good live. His 89 own moves, the 14 authored and the 75 derived, take 4642 of +the registry's 5632 turn keys and 89 of its 96 slots. What stays of the hand-built rules, after the brain's own picks (2 plays nothing and so runs first): diff --git a/ports/zinogre.toml b/ports/zinogre.toml index cced5303..06bd6088 100644 --- a/ports/zinogre.toml +++ b/ports/zinogre.toml @@ -1113,43 +1113,16 @@ charge = true ] "2,10" = "own" "2,11" = "p3_2_10" -"2,12" = [ - 1, - 0, -] -"2,13" = [ - 1, - 0, -] -"2,14" = [ - 1, - 0, -] -"2,16" = [ - 1, - 0, -] -"2,17" = [ - 1, - 0, -] -"2,21" = [ - 1, - 0, -] +"2,12" = "own" +"2,13" = "p3_2_12" +"2,14" = "p3_2_12" +"2,16" = "own" +"2,17" = "own" +"2,21" = "own" "2,22" = "own" -"2,23" = [ - 1, - 0, -] -"2,24" = [ - 1, - 0, -] -"2,25" = [ - 1, - 0, -] +"2,23" = "own" +"2,24" = "p3_2_23" +"2,25" = "p3_2_23" "2,26" = [ 1, 2, From 50c0ff34d0b55d222d1c5dc7bfaa44905b7c41a9 Mon Sep 17 00:00:00 2001 From: sp00ktober <79335616+sp00ktober@users.noreply.github.com> Date: Thu, 22 Oct 2026 12:10:04 +0200 Subject: [PATCH 2/2] docs: a host pair needs what the host's own script does around it em75's (1, 0) walks to a destination its script sets first; entered without one it waits for good. The owner chose this note over a splice guard, which no static rule could make precise. Co-Authored-By: Claude Opus 5.5 --- docs/formats/p3rd_ai_script.md | 6 ++++++ ports/README.md | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/formats/p3rd_ai_script.md b/docs/formats/p3rd_ai_script.md index 28ba0ae2..5e1e8ab7 100644 --- a/docs/formats/p3rd_ai_script.md +++ b/docs/formats/p3rd_ai_script.md @@ -119,6 +119,12 @@ move another pair plays), `P3_WHEN`, `P3_WHEN_IGNORED`, a `P3_HISTORY` for a call before the read) and notes (`P3_DROPPED`, `P3_WEIGHT`, `P3_SPLIT`, an `info` `P3_FOLDED`). +A host pair needs what the host's own script does around it. em75 enters (1, 0) only after a +destination is set (`set_target kind=2`, a `walk_next` step) and, mostly, a turn (1, 2) toward it; +(1, 0) then walks there. Entered without one, it waits for good, and the script with it. So map a +donor pair to a host pair only where the host enters that pair the same way (`mhfu script ` +shows each site); a donor walk is derived (`"own"`). + ### Own moves A `[brain.pairs]` value may name an own move of the port's `[moves]` (`"7,13" = "slam"`) instead of diff --git a/ports/README.md b/ports/README.md index e090ba79..b4b8a881 100644 --- a/ports/README.md +++ b/ports/README.md @@ -208,8 +208,8 @@ A donor pair plays a host pair (a turn), the authored move that is the donor's (`charge_up`, `dash`, `dash_stop`, `break_howl`) or an own move derived from the donor (`"own"`); a pair that only turns, then enters another, turns on em75's (1, 2) and plays the move of the pair it leads to; pairs whose derived moves come out identical (twins) share the -first one's. The donor's walks are derived moves on their own walk clips, not em75's (1, 0), -which parked for good live. His 89 own moves, the 14 authored and the 75 derived, take 4642 of +first one's. The donor's walks are derived moves on their own walk clips: em75's (1, 0) walks to a +destination its own script sets first (docs/formats/p3rd_ai_script.md). His 89 own moves, the 14 authored and the 75 derived, take 4642 of the registry's 5632 turn keys and 89 of its 96 slots. What stays of the hand-built rules, after the brain's own picks (2 plays nothing and so runs