Compare commits
2 Commits
eddf4b9481
...
e5417b6799
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5417b6799 | ||
|
|
c0efb3915b |
@@ -7,25 +7,80 @@
|
|||||||
"animations": [
|
"animations": [
|
||||||
{
|
{
|
||||||
"name": "idle",
|
"name": "idle",
|
||||||
"fps": 6,
|
"fps": 8,
|
||||||
"frames": [0, 1]
|
"frames": [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "active",
|
"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,
|
"fps": 10,
|
||||||
"frames": [1, 0]
|
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
"one_shot": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "success",
|
"name": "success",
|
||||||
"fps": 10,
|
"fps": 10,
|
||||||
"frames": [0, 1, 0],
|
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
"one_shot": true
|
"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",
|
"name": "error",
|
||||||
"fps": 8,
|
"fps": 8,
|
||||||
"frames": [1, 0, 1],
|
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
|
||||||
"one_shot": true
|
},
|
||||||
|
{
|
||||||
|
"name": "dragging",
|
||||||
|
"fps": 8,
|
||||||
|
"frames": [48, 49, 50, 51, 52, 53, 54, 55]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"anchor": {
|
"anchor": {
|
||||||
|
|||||||
@@ -7,25 +7,80 @@
|
|||||||
"animations": [
|
"animations": [
|
||||||
{
|
{
|
||||||
"name": "idle",
|
"name": "idle",
|
||||||
"fps": 6,
|
"fps": 8,
|
||||||
"frames": [0, 1]
|
"frames": [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "active",
|
"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,
|
"fps": 10,
|
||||||
"frames": [1, 0]
|
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
"one_shot": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "success",
|
"name": "success",
|
||||||
"fps": 10,
|
"fps": 10,
|
||||||
"frames": [0, 1, 0],
|
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
"one_shot": true
|
"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",
|
"name": "error",
|
||||||
"fps": 8,
|
"fps": 8,
|
||||||
"frames": [1, 0, 1],
|
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
|
||||||
"one_shot": true
|
},
|
||||||
|
{
|
||||||
|
"name": "dragging",
|
||||||
|
"fps": 8,
|
||||||
|
"frames": [48, 49, 50, 51, 52, 53, 54, 55]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"anchor": {
|
"anchor": {
|
||||||
|
|||||||
@@ -7,25 +7,80 @@
|
|||||||
"animations": [
|
"animations": [
|
||||||
{
|
{
|
||||||
"name": "idle",
|
"name": "idle",
|
||||||
"fps": 6,
|
"fps": 8,
|
||||||
"frames": [0, 1]
|
"frames": [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "active",
|
"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,
|
"fps": 10,
|
||||||
"frames": [1, 0]
|
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
"one_shot": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "success",
|
"name": "success",
|
||||||
"fps": 10,
|
"fps": 10,
|
||||||
"frames": [0, 1, 0],
|
"frames": [16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
"one_shot": true
|
"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",
|
"name": "error",
|
||||||
"fps": 8,
|
"fps": 8,
|
||||||
"frames": [1, 0, 1],
|
"frames": [40, 41, 42, 43, 44, 45, 46, 47]
|
||||||
"one_shot": true
|
},
|
||||||
|
{
|
||||||
|
"name": "dragging",
|
||||||
|
"fps": 8,
|
||||||
|
"frames": [48, 49, 50, 51, 52, 53, 54, 55]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"anchor": {
|
"anchor": {
|
||||||
|
|||||||
@@ -1034,7 +1034,7 @@ fn default_animation_for_state(state: FrontendState) -> &'static str {
|
|||||||
FrontendState::Active => "active",
|
FrontendState::Active => "active",
|
||||||
FrontendState::Success => "success",
|
FrontendState::Success => "success",
|
||||||
FrontendState::Error => "error",
|
FrontendState::Error => "error",
|
||||||
FrontendState::Dragging => "idle",
|
FrontendState::Dragging => "dragging",
|
||||||
FrontendState::Hidden => "idle",
|
FrontendState::Hidden => "idle",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::Active => "active",
|
||||||
sprimo_protocol::v1::FrontendState::Success => "success",
|
sprimo_protocol::v1::FrontendState::Success => "success",
|
||||||
sprimo_protocol::v1::FrontendState::Error => "error",
|
sprimo_protocol::v1::FrontendState::Error => "error",
|
||||||
sprimo_protocol::v1::FrontendState::Dragging => "idle",
|
sprimo_protocol::v1::FrontendState::Dragging => "dragging",
|
||||||
sprimo_protocol::v1::FrontendState::Hidden => "idle",
|
sprimo_protocol::v1::FrontendState::Hidden => "idle",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,6 +284,22 @@ mod tests {
|
|||||||
assert_eq!(snapshot.current_animation, "active");
|
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]
|
#[test]
|
||||||
fn click_through_flag_is_ignored_and_forced_false() {
|
fn click_through_flag_is_ignored_and_forced_false() {
|
||||||
let temp = TempDir::new().expect("tempdir");
|
let temp = TempDir::new().expect("tempdir");
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ Supporting checks:
|
|||||||
- `GET /v1/health`
|
- `GET /v1/health`
|
||||||
- `GET /v1/state` (periodic sampling)
|
- `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
|
## Prerequisites
|
||||||
|
|
||||||
- Frontend runtime is already running (`sprimo-app` or `sprimo-tauri`).
|
- Frontend runtime is already running (`sprimo-app` or `sprimo-tauri`).
|
||||||
|
|||||||
@@ -75,3 +75,20 @@ For `sprimo-tauri`, when manifest `image` is exactly `sprite.png`:
|
|||||||
- `frame_height = image_height / 7`
|
- `frame_height = image_height / 7`
|
||||||
- manifest `frame_width` and `frame_height` are ignored for this case.
|
- manifest `frame_width` and `frame_height` are ignored for this case.
|
||||||
- animation frame indices are validated against the fixed grid frame count (`56`).
|
- 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.
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ Frontend:
|
|||||||
from runtime snapshot events.
|
from runtime snapshot events.
|
||||||
- For `sprite.png` packs in tauri runtime, frame size is now derived from atlas dimensions with a
|
- 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.
|
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:
|
- React/Vite frontend now supports two window modes:
|
||||||
- `main`: transparent overlay sprite renderer
|
- `main`: transparent overlay sprite renderer
|
||||||
- `settings`: pop-out settings window for character and window controls
|
- `settings`: pop-out settings window for character and window controls
|
||||||
|
|||||||
57
issues/issue6.md
Normal file
57
issues/issue6.md
Normal 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.
|
||||||
@@ -18,6 +18,27 @@ from typing import Any
|
|||||||
from urllib.error import HTTPError, URLError
|
from urllib.error import HTTPError, URLError
|
||||||
from urllib.request import Request, urlopen
|
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:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@@ -205,9 +226,7 @@ def random_valid_command(rng: random.Random) -> dict[str, Any]:
|
|||||||
|
|
||||||
if pick == "play_animation":
|
if pick == "play_animation":
|
||||||
payload = {
|
payload = {
|
||||||
"name": rng.choice(
|
"name": rng.choice(ANIMATION_NAMES),
|
||||||
["idle", "dance", "typing", "celebrate", "error", "unknown_anim"]
|
|
||||||
),
|
|
||||||
"priority": rng.randint(0, 10),
|
"priority": rng.randint(0, 10),
|
||||||
"duration_ms": rng.choice([None, 250, 500, 1000, 3000]),
|
"duration_ms": rng.choice([None, 250, 500, 1000, 3000]),
|
||||||
"interrupt": rng.choice([None, True, False]),
|
"interrupt": rng.choice([None, True, False]),
|
||||||
|
|||||||
Reference in New Issue
Block a user