From 96798c0fbb74f92de05df0d11edd11ee047cc7c3 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Wed, 30 Sep 2026 02:34:07 +0300 Subject: [PATCH] ludic.anim: a transition may name a `via` one-shot - a request from `from` (the state last asked, or "*") to `to` goes by it when the rig carries its clip, and the one-shot's next hands on. The game's hiker picked sit_down / stand_up / board_boat / leave_boat in code; the set says it now. A via edge is not an edge for a request's own fade. animset_test holds it, with a via the rig has and one it has not. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.anim/set_follow.ludic | 2 +- packages/ludic.anim/set_pick.ludic | 14 ++++++++++++++ packages/ludic.anim/set_records.ludic | 1 + packages/ludic.anim/tests/animset_test.ludic | 18 ++++++++++++++++++ packages/ludic.anim/tests/sets.lres | 14 +++++++++++--- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/ludic.anim/set_follow.ludic b/packages/ludic.anim/set_follow.ludic index 299bd6ac..32df98e3 100644 --- a/packages/ludic.anim/set_follow.ludic +++ b/packages/ludic.anim/set_follow.ludic @@ -33,7 +33,7 @@ function as_blend(s: AnimSet, from: int, to: int) -> float { if s.transitions != null { for i in 0 .. len(s.transitions) { let tr = s.transitions[i] - if tr.when == null and tr.to == s.states[to].key and as_leaves(s, tr, from) { return tr.blend } + if tr.when == null and tr.via == "" and tr.to == s.states[to].key and as_leaves(s, tr, from) { return tr.blend } } } return s.states[to].blend diff --git a/packages/ludic.anim/set_pick.ludic b/packages/ludic.anim/set_pick.ludic index 23180972..38ab9f3a 100644 --- a/packages/ludic.anim/set_pick.ludic +++ b/packages/ludic.anim/set_pick.ludic @@ -11,8 +11,22 @@ export function animset_request(pl: AnimPlayer, key: string) -> void { if pl.set < 0 or key == pl.asked { return } let i = as_state(AnimSets[pl.set], key) if i < 0 { return } + let v = as_via(pl, key) pl.asked = key pl.want = i + if v >= 0 { pl.want = v } +} + +# the one-shot a request goes by: a `via` edge from where the code last asked (or "*") to this state, +# whose clip the rig carries; -1 for none +function as_via(pl: AnimPlayer, key: string) -> int { + let s = AnimSets[pl.set] + if s.transitions == null { return -1 } + for i in 0 .. len(s.transitions) { + let tr = s.transitions[i] + if tr.via != "" and tr.to == key and (tr.from == "*" or tr.from == pl.asked) and animset_can(pl, tr.via) { return as_state(s, tr.via) } + } + return -1 } # the state is in the set and the rig carries one of its clips: a one-shot with none is not asked for diff --git a/packages/ludic.anim/set_records.ludic b/packages/ludic.anim/set_records.ludic index 357f6e3f..ff2ab62d 100644 --- a/packages/ludic.anim/set_records.ludic +++ b/packages/ludic.anim/set_records.ludic @@ -38,6 +38,7 @@ export property AnimTransition { to: string = "" blend: float = 0.2 @frame when: fn() -> bool = null + via: string = "" # a one-shot the code's request goes by when the rig carries its clip (its next hands on to `to`) } # a graph for a kind of rig: its parameters, its states (the first is where it starts unless diff --git a/packages/ludic.anim/tests/animset_test.ludic b/packages/ludic.anim/tests/animset_test.ludic index 6d3bff81..c5de698a 100644 --- a/packages/ludic.anim/tests/animset_test.ludic +++ b/packages/ludic.anim/tests/animset_test.ludic @@ -124,4 +124,22 @@ program AnimSetTest { expect(not animset_can(pl, "bow")) expect(not animset_can(pl, "nope")) } + + test "a request goes by its via one-shot when the rig carries it, which hands on; else straight there" (anim_st: mut AnimState, render3d_st: mut Render3dState) { + let sk = skin(render3d_st) + let pl = rig(anim_st) + animset_request(pl, "seat") + animset_tick(anim_st, pl, sk, 0.1) + expect(playing(pl) == "rig_wave") + animset_tick(anim_st, pl, sk, 0.3) + animset_tick(anim_st, pl, sk, 0.3) + animset_tick(anim_st, pl, sk, 0.016) + expect(playing(pl) == "rig_hop") + animset_request(pl, "seat") + animset_tick(anim_st, pl, sk, 0.016) + expect(playing(pl) == "rig_hop") + animset_request(pl, "perch") + animset_tick(anim_st, pl, sk, 0.016) + expect(playing(pl) == "rig_walk") + } } diff --git a/packages/ludic.anim/tests/sets.lres b/packages/ludic.anim/tests/sets.lres index 0929fa51..fc31113d 100644 --- a/packages/ludic.anim/tests/sets.lres +++ b/packages/ludic.anim/tests/sets.lres @@ -1,5 +1,6 @@ # sets.lres - the test's one set: a ground state picking by speed bands, a one-shot that hands back, -# a hop the code never asks for, taken when toy_hops says so, and a bow no rig carries +# a hop the code never asks for, taken when toy_hops says so, a bow no rig carries, and a seat and a +# perch the code asks for by way of a one-shot (settle, which the rig has; bow, which it has not) toy { entry: "ground" params: ["speed"] @@ -15,7 +16,14 @@ toy { }, { key: "wave", loop: false, next: "ground", choices: [{ clip: "wave" }] }, { key: "hop", choices: [{ clip: "hop" }] }, - { key: "bow", loop: false, next: "ground", choices: [{ clip: "bow" }] } + { key: "bow", loop: false, next: "ground", choices: [{ clip: "bow" }] }, + { key: "settle", loop: false, next: "seat", choices: [{ clip: "wave" }] }, + { key: "seat", choices: [{ clip: "hop" }] }, + { key: "perch", choices: [{ clip: "walk" }] } + ] + transitions: [ + { from: "ground", to: "hop", blend: 0.1, when: fn toy_hops }, + { from: "*", to: "seat", via: "settle" }, + { from: "*", to: "perch", via: "bow" } ] - transitions: [{ from: "ground", to: "hop", blend: 0.1, when: fn toy_hops }] }