Compare commits

..

2 Commits

Author SHA1 Message Date
DaZuo0122
e5417b6799 Add: backend testing script for new states 2026-02-14 22:49:28 +08:00
DaZuo0122
c0efb3915b Add: states to use all 7 rows 2026-02-14 22:15:58 +08:00
10 changed files with 312 additions and 23 deletions

View File

@@ -7,25 +7,80 @@
"animations": [
{
"name": "idle",
"fps": 6,
"frames": [0, 1]
"fps": 8,
"frames": [0, 1, 2, 3, 4, 5, 6, 7]
},
{
"name": "active",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "happy",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "love",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "excited",
"fps": 8,
"frames": [16, 17, 18, 19, 20, 21, 22, 23]
},
{
"name": "celebrate",
"fps": 10,
"frames": [1, 0]
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
"one_shot": true
},
{
"name": "success",
"fps": 10,
"frames": [0, 1, 0],
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
"one_shot": true
},
{
"name": "sleepy",
"fps": 8,
"frames": [24, 25, 26, 27, 28, 29, 30, 31]
},
{
"name": "snoring",
"fps": 8,
"frames": [24, 25, 26, 27, 28, 29, 30, 31]
},
{
"name": "working",
"fps": 8,
"frames": [32, 33, 34, 35, 36, 37, 38, 39]
},
{
"name": "angry",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "surprised",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "shy",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "error",
"fps": 8,
"frames": [1, 0, 1],
"one_shot": true
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "dragging",
"fps": 8,
"frames": [48, 49, 50, 51, 52, 53, 54, 55]
}
],
"anchor": {

View File

@@ -7,25 +7,80 @@
"animations": [
{
"name": "idle",
"fps": 6,
"frames": [0, 1]
"fps": 8,
"frames": [0, 1, 2, 3, 4, 5, 6, 7]
},
{
"name": "active",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "happy",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "love",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "excited",
"fps": 8,
"frames": [16, 17, 18, 19, 20, 21, 22, 23]
},
{
"name": "celebrate",
"fps": 10,
"frames": [1, 0]
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
"one_shot": true
},
{
"name": "success",
"fps": 10,
"frames": [0, 1, 0],
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
"one_shot": true
},
{
"name": "sleepy",
"fps": 8,
"frames": [24, 25, 26, 27, 28, 29, 30, 31]
},
{
"name": "snoring",
"fps": 8,
"frames": [24, 25, 26, 27, 28, 29, 30, 31]
},
{
"name": "working",
"fps": 8,
"frames": [32, 33, 34, 35, 36, 37, 38, 39]
},
{
"name": "angry",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "surprised",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "shy",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "error",
"fps": 8,
"frames": [1, 0, 1],
"one_shot": true
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "dragging",
"fps": 8,
"frames": [48, 49, 50, 51, 52, 53, 54, 55]
}
],
"anchor": {

View File

@@ -7,25 +7,80 @@
"animations": [
{
"name": "idle",
"fps": 6,
"frames": [0, 1]
"fps": 8,
"frames": [0, 1, 2, 3, 4, 5, 6, 7]
},
{
"name": "active",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "happy",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "love",
"fps": 8,
"frames": [8, 9, 10, 11, 12, 13, 14, 15]
},
{
"name": "excited",
"fps": 8,
"frames": [16, 17, 18, 19, 20, 21, 22, 23]
},
{
"name": "celebrate",
"fps": 10,
"frames": [1, 0]
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
"one_shot": true
},
{
"name": "success",
"fps": 10,
"frames": [0, 1, 0],
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
"one_shot": true
},
{
"name": "sleepy",
"fps": 8,
"frames": [24, 25, 26, 27, 28, 29, 30, 31]
},
{
"name": "snoring",
"fps": 8,
"frames": [24, 25, 26, 27, 28, 29, 30, 31]
},
{
"name": "working",
"fps": 8,
"frames": [32, 33, 34, 35, 36, 37, 38, 39]
},
{
"name": "angry",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "surprised",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "shy",
"fps": 8,
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "error",
"fps": 8,
"frames": [1, 0, 1],
"one_shot": true
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
},
{
"name": "dragging",
"fps": 8,
"frames": [48, 49, 50, 51, 52, 53, 54, 55]
}
],
"anchor": {

View File

@@ -1034,7 +1034,7 @@ fn default_animation_for_state(state: FrontendState) -> &'static str {
FrontendState::Active => "active",
FrontendState::Success => "success",
FrontendState::Error => "error",
FrontendState::Dragging => "idle",
FrontendState::Dragging => "dragging",
FrontendState::Hidden => "idle",
}
}

View File

@@ -252,7 +252,7 @@ fn default_animation_for_state(state: sprimo_protocol::v1::FrontendState) -> &'s
sprimo_protocol::v1::FrontendState::Active => "active",
sprimo_protocol::v1::FrontendState::Success => "success",
sprimo_protocol::v1::FrontendState::Error => "error",
sprimo_protocol::v1::FrontendState::Dragging => "idle",
sprimo_protocol::v1::FrontendState::Dragging => "dragging",
sprimo_protocol::v1::FrontendState::Hidden => "idle",
}
}
@@ -284,6 +284,22 @@ mod tests {
assert_eq!(snapshot.current_animation, "active");
}
#[test]
fn dragging_state_maps_to_dragging_animation() {
let temp = TempDir::new().expect("tempdir");
let path = temp.path().join("config.toml");
let core = RuntimeCore::new_with_config(path, AppConfig::default(), CapabilityFlags::default())
.expect("core init");
core.apply_command(&FrontendCommand::SetState {
state: FrontendState::Dragging,
ttl_ms: None,
})
.expect("apply");
let snapshot = core.snapshot().read().expect("snapshot lock").clone();
assert_eq!(snapshot.state, FrontendState::Dragging);
assert_eq!(snapshot.current_animation, "dragging");
}
#[test]
fn click_through_flag_is_ignored_and_forced_false() {
let temp = TempDir::new().expect("tempdir");

View File

@@ -18,6 +18,13 @@ Supporting checks:
- `GET /v1/health`
- `GET /v1/state` (periodic sampling)
Animation traffic now targets row-based sprite names plus compatibility aliases:
- semantic names: `idle`, `happy`, `love`, `excited`, `celebrate`, `sleepy`, `snoring`,
`working`, `angry`, `surprised`, `shy`, `dragging`
- compatibility aliases: `active`, `success`, `error`
- one intentional unknown name is still included to keep invalid animation-path coverage
## Prerequisites
- Frontend runtime is already running (`sprimo-app` or `sprimo-tauri`).

View File

@@ -75,3 +75,20 @@ For `sprimo-tauri`, when manifest `image` is exactly `sprite.png`:
- `frame_height = image_height / 7`
- manifest `frame_width` and `frame_height` are ignored for this case.
- animation frame indices are validated against the fixed grid frame count (`56`).
## Recommended 7x8 Row Semantics
For `sprite.png` packs using the fixed `8x7` topology, the project convention is:
- row 1 (`0..7`): `idle`
- row 2 (`8..15`): `happy`, `love`, compatibility alias `active`
- row 3 (`16..23`): `excited`, `celebrate`, compatibility alias `success`
- row 4 (`24..31`): `sleepy`, `snoring`
- row 5 (`32..39`): `working`
- row 6 (`40..47`): `angry`, `surprised`, `shy`, compatibility alias `error`
- row 7 (`48..55`): `dragging`
Default one-shot policy:
- `celebrate` and `success` are one-shot.
- other row animations loop by default.

View File

@@ -60,6 +60,14 @@ Frontend:
from runtime snapshot events.
- For `sprite.png` packs in tauri runtime, frame size is now derived from atlas dimensions with a
fixed `8x7` grid topology to keep splitting stable across packaged asset resolutions.
- `sprite.png` animation naming now follows row semantics with backward-compatible aliases:
- row1: `idle`
- row2: `happy`/`love` + alias `active`
- row3: `excited`/`celebrate` + alias `success`
- row4: `sleepy`/`snoring`
- row5: `working`
- row6: `angry`/`surprised`/`shy` + alias `error`
- row7: `dragging`
- React/Vite frontend now supports two window modes:
- `main`: transparent overlay sprite renderer
- `settings`: pop-out settings window for character and window controls

57
issues/issue6.md Normal file
View File

@@ -0,0 +1,57 @@
## Title
Adopt row-based 7x8 sprite animation semantics with backward-compatible state aliases.
## Severity
P2
## Summary
Standardize `sprite.png` packs so each of the 7 rows maps to semantic animation groups, while
keeping runtime compatibility with existing state names (`active`, `success`, `error`).
## Scope
- `assets/sprite-packs/{default,ferris,demogorgon}/manifest.json`
- `crates/sprimo-runtime-core/src/lib.rs`
- `crates/sprimo-app/src/main.rs`
- docs:
- `docs/SPRITE_PACK_SCHEMA.md`
- `docs/TAURI_FRONTEND_DESIGN.md`
## Row Mapping Contract
- row 1 (`0..7`): `idle`
- row 2 (`8..15`): `happy`, `love`, alias `active`
- row 3 (`16..23`): `excited`, `celebrate`, alias `success`
- row 4 (`24..31`): `sleepy`, `snoring`
- row 5 (`32..39`): `working`
- row 6 (`40..47`): `angry`, `surprised`, `shy`, alias `error`
- row 7 (`48..55`): `dragging`
One-shot defaults:
- `celebrate` and `success`: one-shot
- all others: loop
## Implementation Notes
1. Runtime state mapping updated:
- `Dragging` now maps to `"dragging"` instead of `"idle"` in runtime-core and Bevy frontend.
2. All bundled sprite-pack manifests now expose row-based names and compatibility aliases.
3. Added runtime-core unit test to confirm `SetState::Dragging` selects `"dragging"`.
4. Updated schema/design docs to formalize the row convention.
## Verification
### Commands Run
- [x] `cargo test -p sprimo-runtime-core`
- [x] `cargo check -p sprimo-tauri`
- [x] `cargo check -p sprimo-app`
### Result
- Status: `Fix Implemented`
- Notes: packaged runtime visual verification pending.

View File

@@ -18,6 +18,27 @@ from typing import Any
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
ANIMATION_NAMES = (
"idle",
"happy",
"love",
"excited",
"celebrate",
"sleepy",
"snoring",
"working",
"angry",
"surprised",
"shy",
"dragging",
# Backward-compatible aliases mapped in runtime/manifests.
"active",
"success",
"error",
# Intentionally invalid to keep unknown-animation traffic coverage.
"unknown_anim",
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
@@ -205,9 +226,7 @@ def random_valid_command(rng: random.Random) -> dict[str, Any]:
if pick == "play_animation":
payload = {
"name": rng.choice(
["idle", "dance", "typing", "celebrate", "error", "unknown_anim"]
),
"name": rng.choice(ANIMATION_NAMES),
"priority": rng.randint(0, 10),
"duration_ms": rng.choice([None, 250, 500, 1000, 3000]),
"interrupt": rng.choice([None, True, False]),