From a87f20c66e48c842010361e1758cce36501e81e8 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 29 Sep 2026 20:19:05 +0300 Subject: [PATCH] ludic.anim: animset_choice names the playing choice's clip as the set does, animset_can says whether the rig carries any of a state's clips (a one-shot with none is not asked for) Co-Authored-By: Claude Opus 5.5 --- packages/ludic.anim/README.md | 4 +++- packages/ludic.anim/set_pick.ludic | 11 +++++++++++ packages/ludic.anim/set_player.ludic | 6 ++++++ packages/ludic.anim/set_tick.ludic | 1 + packages/ludic.anim/tests/animset_test.ludic | 11 +++++++++++ packages/ludic.anim/tests/sets.lres | 5 +++-- 6 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/ludic.anim/README.md b/packages/ludic.anim/README.md index 8d1ce047..53b7e903 100644 --- a/packages/ludic.anim/README.md +++ b/packages/ludic.anim/README.md @@ -68,7 +68,9 @@ and transitions (from a state or `"*"`, with a fade; with `@frame when: fn() -> own the frame it answers true). The code binds an `AnimPlayer` to a set and a rig (`animset_bind`), sets parameters (`animset_param`), asks for a state (`animset_request`, taken once however often it is asked) and ticks it into a skin (`animset_tick`). A one-shot (`loop: false`) -holds its last pose and hands on to `next`. Clip names are made once per rig; a tick makes nothing. +holds its last pose and hands on to `next`. `animset_choice` names the playing choice's clip as the set +does ("sit_seat", no rig), and `animset_can(key)` says whether the rig carries any of a state's clips, so a +one-shot with none yet is not asked for. Clip names are made once per rig; a tick makes nothing. ## Tests diff --git a/packages/ludic.anim/set_pick.ludic b/packages/ludic.anim/set_pick.ludic index b44efe02..23180972 100644 --- a/packages/ludic.anim/set_pick.ludic +++ b/packages/ludic.anim/set_pick.ludic @@ -15,6 +15,17 @@ export function animset_request(pl: AnimPlayer, key: string) -> void { pl.want = i } +# the state is in the set and the rig carries one of its clips: a one-shot with none is not asked for +export function animset_can(pl: AnimPlayer, key: string) -> bool { + if pl.set < 0 or pl.clips == null { return false } + let i = as_state(AnimSets[pl.set], key) + if i < 0 { return false } + let d = AnimSets[pl.set].states[i] + if d.choices == null { return false } + for c in 0 .. len(d.choices) { if pl.clips[pl.base[i] + c] != null { return true } } + return false +} + function as_param_at(pl: AnimPlayer, name: string) -> int { if pl.set < 0 { return -1 } let s = AnimSets[pl.set] diff --git a/packages/ludic.anim/set_player.ludic b/packages/ludic.anim/set_player.ludic index 5c7b0b92..fcc31877 100644 --- a/packages/ludic.anim/set_player.ludic +++ b/packages/ludic.anim/set_player.ludic @@ -20,10 +20,16 @@ export property AnimPlayer { mix: float = 1.0 fade: float = 0.2 rate: float = 1.0 + pick: int = -1 # which of the state's choices plays } export function animset_clip(pl: AnimPlayer) -> Clip { return pl.clip } export function animset_time(pl: AnimPlayer) -> float { return pl.t } export function animset_rate(pl: AnimPlayer) -> float { return pl.rate } +# the playing choice's clip as the set names it, without the rig ("sit_seat"); "" before a tick +export function animset_choice(pl: AnimPlayer) -> string { + if pl.set < 0 or pl.pick < 0 { return "" } + return AnimSets[pl.set].states[pl.state].choices[pl.pick].clip +} export function animset_ready(pl: AnimPlayer) -> bool { return pl.ready } export function animset_bound(pl: AnimPlayer, set: int, rig: string) -> bool { return pl.set == set and pl.rig == rig and pl.names != null } diff --git a/packages/ludic.anim/set_tick.ludic b/packages/ludic.anim/set_tick.ludic index 2fc64123..58cb0c68 100644 --- a/packages/ludic.anim/set_tick.ludic +++ b/packages/ludic.anim/set_tick.ludic @@ -6,6 +6,7 @@ export function animset_tick(anim_st: mut AnimState, pl: AnimPlayer, sk: Skin, d let d = AnimSets[pl.set].states[pl.state] let k = as_pick(pl, d) if k < 0 { return false } + pl.pick = k pl.rate = as_rate(pl, d, d.choices[k]) as_switch(pl, d, pl.clips[pl.base[pl.state] + k]) let adv = dt * pl.rate diff --git a/packages/ludic.anim/tests/animset_test.ludic b/packages/ludic.anim/tests/animset_test.ludic index 9e1a849c..6d3bff81 100644 --- a/packages/ludic.anim/tests/animset_test.ludic +++ b/packages/ludic.anim/tests/animset_test.ludic @@ -113,4 +113,15 @@ program AnimSetTest { } expect(playing(pl) == "rig_idle") } + test "the playing choice is named as the set names it; a state the rig has no clip for cannot be asked" (anim_st: mut AnimState, render3d_st: mut Render3dState) { + let sk = skin(render3d_st) + let pl = rig(anim_st) + expect(animset_choice(pl) == "") + animset_param(pl, "speed", 1.0) + animset_tick(anim_st, pl, sk, 0.016) + expect(animset_choice(pl) == "walk") + expect(animset_can(pl, "wave")) + expect(not animset_can(pl, "bow")) + expect(not animset_can(pl, "nope")) + } } diff --git a/packages/ludic.anim/tests/sets.lres b/packages/ludic.anim/tests/sets.lres index c4d2128e..0929fa51 100644 --- a/packages/ludic.anim/tests/sets.lres +++ b/packages/ludic.anim/tests/sets.lres @@ -1,5 +1,5 @@ # sets.lres - the test's one set: a ground state picking by speed bands, a one-shot that hands back, -# and a hop the code never asks for, taken when toy_hops says so +# a hop the code never asks for, taken when toy_hops says so, and a bow no rig carries toy { entry: "ground" params: ["speed"] @@ -14,7 +14,8 @@ toy { ] }, { key: "wave", loop: false, next: "ground", choices: [{ clip: "wave" }] }, - { key: "hop", choices: [{ clip: "hop" }] } + { key: "hop", choices: [{ clip: "hop" }] }, + { key: "bow", loop: false, next: "ground", choices: [{ clip: "bow" }] } ] transitions: [{ from: "ground", to: "hop", blend: 0.1, when: fn toy_hops }] }