diff --git a/.gitignore b/.gitignore index 0676d43..b5fe0c7 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,5 @@ firebase.cfg # .mcp.json holds per-machine server credentials. Keep it local; each tool # reads its own user-level config instead. .mcp.json +# Playwright MCP screenshots, console logs, and evaluation files. +.playwright-mcp/ diff --git a/apps/docs/docs/assets/manifest.md b/apps/docs/docs/assets/manifest.md index 9e77122..f164223 100644 --- a/apps/docs/docs/assets/manifest.md +++ b/apps/docs/docs/assets/manifest.md @@ -577,11 +577,14 @@ Project path: res://assets/custom/ui/ Used: heart.png, panel_moonlit.png, nine_path_panel_moonlit.png, button_normal_moonlit.png, button_hover_moonlit.png, button_pressed_moonlit.png, button_disabled_moonlit.png, - button_focus_moonlit.png + button_focus_moonlit.png, relic_card_*.png (4 states), + icons/*.png (16 relic emblems) Modification: tools/build_world_assets.py builds the 5-step moonlight heart; tools/build_custom_ui_panels.py generates ink-navy, moon-silver, teal, vermilion panels and button states that keep the existing - 5px and 6px nine-patch margins + 5px and 6px nine-patch margins; batch 3 drew the relic-pick + cards (168x112, carved corner moons) and 16 48x48 relic + emblems as ChatGPT grid drops via tools/slice_gpt_cells.py ``` | File | Size | Use | @@ -594,6 +597,11 @@ Modification: tools/build_world_assets.py builds the 5-step moonlight heart; | `button_pressed_moonlit.png` | 16×16 | darkened face and lower vermilion rivets | | `button_disabled_moonlit.png` | 16×16 | desaturated disabled state | | `button_focus_moonlit.png` | 16×16 | transparent focus ring stacked on the default state | +| `relic_card_normal.png` | 168×112 | relic-pick card, resting state. navy face, amber border | +| `relic_card_hover.png` | 168×112 | brightened border with amber shimmer | +| `relic_card_pressed.png` | 168×112 | pressed in, darker, dimmed border | +| `relic_card_disabled.png` | 168×112 | grayed, faded | +| `icons/dew_hunter.png` … `icons/wide_arc.png` | 48×48 ×16 | one emblem per relic, named by relic id | The heart has 5 steps but **we only use the one full frame.** Empty slots lower that one frame's opacity to 0.24. Swapping two pictures makes the @@ -790,39 +798,45 @@ Source: original (AI-assisted concept, deterministic pixel production) Creator: Moonlit Beacon License: follows the project license Project path: res://assets/custom/actors/spirits/, res://assets/custom/actors/guardians/ -Used: 4-facing float motion for 7 normal enemies +Used: facing-less float motion for 7 normal enemies idle, wind-up, charge, recover motion for forest, field, and camp guardians Modification: generation-model concepts used only for material and silhouette comparison. tools/build_spirit_guardian_assets.py regenerated 18 final binary-alpha RGBA PNGs at 1x with integer coordinates and a - limited palette + limited palette; batch G re-cut all 22 guardian sheets to + 6-frame ChatGPT grid drops via tools/slice_gpt_grid.py; + batch 2 re-cut all 7 normal-enemy sheets the same way ``` | Normal enemy | Silhouette that reads in combat | Behavior cue | | --- | --- | --- | -| Wisp | crescent core and long smoke tail | default chase | -| Stalker | low four-legged body and backward spike horns | straight charge | -| Swarm | three moon-embers swapping places | weak and fast cluster | -| Ember | charcoal body and a large swaying flame crown | fast chase | -| Drifter | wide asymmetric moon-moth wings and a thin tail | high-inertia float | -| Weaver | broken orbit ring and four tentacles | orbit around the player | -| Caster | moon mask, long robe, and a forward staff | keep-distance fire | - -Normal-enemy sheets are all `96×96` with `24×24` cells in four columns -down·up·left·right and four frame rows. They are not recolors. Asset -regression checks that all 7 alpha silhouettes and all 7 full-image -hashes differ. +| Wisp | round glowing body, sleepy face, light tail | default chase | +| Stalker | sleek shadow-cat, crescent marks, tail sway | straight charge | +| Swarm | tattered bat wings, glowing eyes | weak and fast cluster | +| Ember | cracked coal body, glowing seams, top flame | fast chase | +| Drifter | translucent bell head, trailing tentacles | high-inertia float | +| Weaver | round silver body, thin skitter legs | orbit around the player | +| Caster | wide-brim hat, tattered cloak, raised staff | keep-distance fire | + +Normal-enemy sheets are all `288×48` with six `48×48` frames in one +facing-less row. They are not recolors. Asset regression checks that +all 7 alpha silhouettes and all 7 full-image hashes differ. | Guardian | Idle | State sheets | Combat silhouette | | --- | --- | --- | --- | -| Forest | `forest.png` 6 frames | wind-up, charge, recover 4 frames each | horns, long tree-arms, roots in the ground | -| Field | `field.png` 6 frames | cross wind-up, radial wind-up, recover 4 frames each | wide crescent wings, mask, cloud tentacles | -| Camp | `camp.png` 6 frames | wind-up, recover 4 frames each | beacon brazier, hammer, gate shield | - -Guardian cells are `64×64`. Idle is `384×64`, state sheets `256×64`, +| Forest | `forest.png` 6 frames | wind-up, charge, recover 6 frames each | horns, long tree-arms, roots in the ground | +| Forest Thorn | `forest_thorn.png` 6 frames | wind-up, charge, recover 6 frames each | thorn crown, deeper bark, heavier arms | +| Field | `field.png` 6 frames | cross wind-up, radial wind-up, recover 6 frames each | wide crescent wings, mask, cloud tentacles | +| Field Storm | `field_storm.png` 6 frames | cross wind-up, radial wind-up, recover 6 frames each | lightning veins, torn wings, crackling tips | +| Camp | `camp.png` 6 frames | wind-up, recover 6 frames each | brazier helm, hammer, gate shield | +| Camp Siege | `camp_siege.png` 6 frames | wind-up, recover 6 frames each | siege plates, siege hammer, cracked shield | + +Guardian cells are `64×64`. Idle and state sheets are all `384×64`, horizontal layout with no facing. Every `SpiritKind` state slot is filled with a custom sheet so mid-states do not fall back to the old free boss -picture. +picture. State sheets were re-cut to 6 frames (batch G) because 4-frame +combat motion reads choppy; the runtime stretches each sheet over its +state duration, so frame count only changes smoothness. --- diff --git a/apps/game/assets/custom/actors/guardians/camp.png b/apps/game/assets/custom/actors/guardians/camp.png index 558ef72..f2e4315 100644 Binary files a/apps/game/assets/custom/actors/guardians/camp.png and b/apps/game/assets/custom/actors/guardians/camp.png differ diff --git a/apps/game/assets/custom/actors/guardians/camp_recover.png b/apps/game/assets/custom/actors/guardians/camp_recover.png index f1f5813..bbed958 100644 Binary files a/apps/game/assets/custom/actors/guardians/camp_recover.png and b/apps/game/assets/custom/actors/guardians/camp_recover.png differ diff --git a/apps/game/assets/custom/actors/guardians/camp_siege.png b/apps/game/assets/custom/actors/guardians/camp_siege.png index 03efc8e..3687f2f 100644 Binary files a/apps/game/assets/custom/actors/guardians/camp_siege.png and b/apps/game/assets/custom/actors/guardians/camp_siege.png differ diff --git a/apps/game/assets/custom/actors/guardians/camp_siege_recover.png b/apps/game/assets/custom/actors/guardians/camp_siege_recover.png index 784d7b3..88800c2 100644 Binary files a/apps/game/assets/custom/actors/guardians/camp_siege_recover.png and b/apps/game/assets/custom/actors/guardians/camp_siege_recover.png differ diff --git a/apps/game/assets/custom/actors/guardians/camp_siege_windup.png b/apps/game/assets/custom/actors/guardians/camp_siege_windup.png index 59c375b..19c8f5f 100644 Binary files a/apps/game/assets/custom/actors/guardians/camp_siege_windup.png and b/apps/game/assets/custom/actors/guardians/camp_siege_windup.png differ diff --git a/apps/game/assets/custom/actors/guardians/camp_windup.png b/apps/game/assets/custom/actors/guardians/camp_windup.png index 4ae67bc..72171f8 100644 Binary files a/apps/game/assets/custom/actors/guardians/camp_windup.png and b/apps/game/assets/custom/actors/guardians/camp_windup.png differ diff --git a/apps/game/assets/custom/actors/guardians/field.png b/apps/game/assets/custom/actors/guardians/field.png index 528a880..c7cea63 100644 Binary files a/apps/game/assets/custom/actors/guardians/field.png and b/apps/game/assets/custom/actors/guardians/field.png differ diff --git a/apps/game/assets/custom/actors/guardians/field_recover.png b/apps/game/assets/custom/actors/guardians/field_recover.png index 4d1e6e8..41a4f2d 100644 Binary files a/apps/game/assets/custom/actors/guardians/field_recover.png and b/apps/game/assets/custom/actors/guardians/field_recover.png differ diff --git a/apps/game/assets/custom/actors/guardians/field_storm.png b/apps/game/assets/custom/actors/guardians/field_storm.png index 85fc25a..a80f0f4 100644 Binary files a/apps/game/assets/custom/actors/guardians/field_storm.png and b/apps/game/assets/custom/actors/guardians/field_storm.png differ diff --git a/apps/game/assets/custom/actors/guardians/field_storm_recover.png b/apps/game/assets/custom/actors/guardians/field_storm_recover.png index 80236db..34a677a 100644 Binary files a/apps/game/assets/custom/actors/guardians/field_storm_recover.png and b/apps/game/assets/custom/actors/guardians/field_storm_recover.png differ diff --git a/apps/game/assets/custom/actors/guardians/field_storm_windup_cross.png b/apps/game/assets/custom/actors/guardians/field_storm_windup_cross.png index 9bbf7ed..89ec05f 100644 Binary files a/apps/game/assets/custom/actors/guardians/field_storm_windup_cross.png and b/apps/game/assets/custom/actors/guardians/field_storm_windup_cross.png differ diff --git a/apps/game/assets/custom/actors/guardians/field_storm_windup_radial.png b/apps/game/assets/custom/actors/guardians/field_storm_windup_radial.png index 9bbf7ed..9625bc6 100644 Binary files a/apps/game/assets/custom/actors/guardians/field_storm_windup_radial.png and b/apps/game/assets/custom/actors/guardians/field_storm_windup_radial.png differ diff --git a/apps/game/assets/custom/actors/guardians/field_windup_cross.png b/apps/game/assets/custom/actors/guardians/field_windup_cross.png index b707f4a..afe353f 100644 Binary files a/apps/game/assets/custom/actors/guardians/field_windup_cross.png and b/apps/game/assets/custom/actors/guardians/field_windup_cross.png differ diff --git a/apps/game/assets/custom/actors/guardians/field_windup_radial.png b/apps/game/assets/custom/actors/guardians/field_windup_radial.png index b707f4a..07033e2 100644 Binary files a/apps/game/assets/custom/actors/guardians/field_windup_radial.png and b/apps/game/assets/custom/actors/guardians/field_windup_radial.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest.png b/apps/game/assets/custom/actors/guardians/forest.png index b4c4255..89112ab 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest.png and b/apps/game/assets/custom/actors/guardians/forest.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest_charge.png b/apps/game/assets/custom/actors/guardians/forest_charge.png index db65a1a..eb6e573 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest_charge.png and b/apps/game/assets/custom/actors/guardians/forest_charge.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest_recover.png b/apps/game/assets/custom/actors/guardians/forest_recover.png index 8da8617..4116035 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest_recover.png and b/apps/game/assets/custom/actors/guardians/forest_recover.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest_thorn.png b/apps/game/assets/custom/actors/guardians/forest_thorn.png index 9ecb208..68b9120 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest_thorn.png and b/apps/game/assets/custom/actors/guardians/forest_thorn.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest_thorn_charge.png b/apps/game/assets/custom/actors/guardians/forest_thorn_charge.png index b052d75..b44b2fc 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest_thorn_charge.png and b/apps/game/assets/custom/actors/guardians/forest_thorn_charge.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest_thorn_recover.png b/apps/game/assets/custom/actors/guardians/forest_thorn_recover.png index 3c9811c..7c1edfc 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest_thorn_recover.png and b/apps/game/assets/custom/actors/guardians/forest_thorn_recover.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest_thorn_windup.png b/apps/game/assets/custom/actors/guardians/forest_thorn_windup.png index 34e12b3..fca1dcf 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest_thorn_windup.png and b/apps/game/assets/custom/actors/guardians/forest_thorn_windup.png differ diff --git a/apps/game/assets/custom/actors/guardians/forest_windup.png b/apps/game/assets/custom/actors/guardians/forest_windup.png index 1e0fddd..8fbe0eb 100644 Binary files a/apps/game/assets/custom/actors/guardians/forest_windup.png and b/apps/game/assets/custom/actors/guardians/forest_windup.png differ diff --git a/apps/game/assets/custom/actors/spirits/caster.png b/apps/game/assets/custom/actors/spirits/caster.png index 16638b5..2ee8fb3 100644 Binary files a/apps/game/assets/custom/actors/spirits/caster.png and b/apps/game/assets/custom/actors/spirits/caster.png differ diff --git a/apps/game/assets/custom/actors/spirits/drifter.png b/apps/game/assets/custom/actors/spirits/drifter.png index 855fd47..b0ad988 100644 Binary files a/apps/game/assets/custom/actors/spirits/drifter.png and b/apps/game/assets/custom/actors/spirits/drifter.png differ diff --git a/apps/game/assets/custom/actors/spirits/ember.png b/apps/game/assets/custom/actors/spirits/ember.png index 492822a..72f39f1 100644 Binary files a/apps/game/assets/custom/actors/spirits/ember.png and b/apps/game/assets/custom/actors/spirits/ember.png differ diff --git a/apps/game/assets/custom/actors/spirits/stalker.png b/apps/game/assets/custom/actors/spirits/stalker.png index 238f140..3ee0aa7 100644 Binary files a/apps/game/assets/custom/actors/spirits/stalker.png and b/apps/game/assets/custom/actors/spirits/stalker.png differ diff --git a/apps/game/assets/custom/actors/spirits/swarm.png b/apps/game/assets/custom/actors/spirits/swarm.png index 3c7c096..ed3c012 100644 Binary files a/apps/game/assets/custom/actors/spirits/swarm.png and b/apps/game/assets/custom/actors/spirits/swarm.png differ diff --git a/apps/game/assets/custom/actors/spirits/weaver.png b/apps/game/assets/custom/actors/spirits/weaver.png index cfa0262..09b4889 100644 Binary files a/apps/game/assets/custom/actors/spirits/weaver.png and b/apps/game/assets/custom/actors/spirits/weaver.png differ diff --git a/apps/game/assets/custom/actors/spirits/wisp.png b/apps/game/assets/custom/actors/spirits/wisp.png index 786c97f..779ffe8 100644 Binary files a/apps/game/assets/custom/actors/spirits/wisp.png and b/apps/game/assets/custom/actors/spirits/wisp.png differ diff --git a/apps/game/assets/custom/ui/icons/dew_hunter.png b/apps/game/assets/custom/ui/icons/dew_hunter.png new file mode 100644 index 0000000..185b0e3 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/dew_hunter.png differ diff --git a/apps/game/assets/custom/ui/icons/dew_hunter.png.import b/apps/game/assets/custom/ui/icons/dew_hunter.png.import new file mode 100644 index 0000000..b7f3e89 --- /dev/null +++ b/apps/game/assets/custom/ui/icons/dew_hunter.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dudjmqtxdfpub" +path="res://.godot/imported/dew_hunter.png-dd9bc6d34a3b14c60f77f545365b324a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/dew_hunter.png" +dest_files=["res://.godot/imported/dew_hunter.png-dd9bc6d34a3b14c60f77f545365b324a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/heavy_arrow.png b/apps/game/assets/custom/ui/icons/heavy_arrow.png new file mode 100644 index 0000000..a9ca50f Binary files /dev/null and b/apps/game/assets/custom/ui/icons/heavy_arrow.png differ diff --git a/apps/game/assets/custom/ui/icons/heavy_arrow.png.import b/apps/game/assets/custom/ui/icons/heavy_arrow.png.import new file mode 100644 index 0000000..1f26225 --- /dev/null +++ b/apps/game/assets/custom/ui/icons/heavy_arrow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8ef0j143rfjp" +path="res://.godot/imported/heavy_arrow.png-72ed2eedc0f0f829bc8e72b35b437a16.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/heavy_arrow.png" +dest_files=["res://.godot/imported/heavy_arrow.png-72ed2eedc0f0f829bc8e72b35b437a16.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/light_step.png b/apps/game/assets/custom/ui/icons/light_step.png new file mode 100644 index 0000000..5a6916b Binary files /dev/null and b/apps/game/assets/custom/ui/icons/light_step.png differ diff --git a/apps/game/assets/custom/ui/icons/light_step.png.import b/apps/game/assets/custom/ui/icons/light_step.png.import new file mode 100644 index 0000000..b3984af --- /dev/null +++ b/apps/game/assets/custom/ui/icons/light_step.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bg83i6kw80t4r" +path="res://.godot/imported/light_step.png-6de2d923c74626340e71bfccc4040054.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/light_step.png" +dest_files=["res://.godot/imported/light_step.png-6de2d923c74626340e71bfccc4040054.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/long_blade.png b/apps/game/assets/custom/ui/icons/long_blade.png new file mode 100644 index 0000000..90830b5 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/long_blade.png differ diff --git a/apps/game/assets/custom/ui/icons/long_blade.png.import b/apps/game/assets/custom/ui/icons/long_blade.png.import new file mode 100644 index 0000000..905213b --- /dev/null +++ b/apps/game/assets/custom/ui/icons/long_blade.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0xcqm1tcwggq" +path="res://.godot/imported/long_blade.png-c4e6db1ddec969881b40966871e57c2b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/long_blade.png" +dest_files=["res://.godot/imported/long_blade.png-c4e6db1ddec969881b40966871e57c2b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/moon_dash.png b/apps/game/assets/custom/ui/icons/moon_dash.png new file mode 100644 index 0000000..3e04a85 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/moon_dash.png differ diff --git a/apps/game/assets/custom/ui/icons/moon_dash.png.import b/apps/game/assets/custom/ui/icons/moon_dash.png.import new file mode 100644 index 0000000..7f1737e --- /dev/null +++ b/apps/game/assets/custom/ui/icons/moon_dash.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bkw3rmxc8lt38" +path="res://.godot/imported/moon_dash.png-219ae3933e491a7902d969e88f163f17.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/moon_dash.png" +dest_files=["res://.godot/imported/moon_dash.png-219ae3933e491a7902d969e88f163f17.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/moon_ring.png b/apps/game/assets/custom/ui/icons/moon_ring.png new file mode 100644 index 0000000..795e9f0 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/moon_ring.png differ diff --git a/apps/game/assets/custom/ui/icons/moon_ring.png.import b/apps/game/assets/custom/ui/icons/moon_ring.png.import new file mode 100644 index 0000000..627b2cd --- /dev/null +++ b/apps/game/assets/custom/ui/icons/moon_ring.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbox4vq4l0dr1" +path="res://.godot/imported/moon_ring.png-5162da45887f6d122b7a88f458e7b855.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/moon_ring.png" +dest_files=["res://.godot/imported/moon_ring.png-5162da45887f6d122b7a88f458e7b855.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/moon_ripple.png b/apps/game/assets/custom/ui/icons/moon_ripple.png new file mode 100644 index 0000000..44cf385 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/moon_ripple.png differ diff --git a/apps/game/assets/custom/ui/icons/moon_ripple.png.import b/apps/game/assets/custom/ui/icons/moon_ripple.png.import new file mode 100644 index 0000000..ab7a31c --- /dev/null +++ b/apps/game/assets/custom/ui/icons/moon_ripple.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0wqdprf4tp54" +path="res://.godot/imported/moon_ripple.png-c5bceab477ceeb0f1860416a31f12bc6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/moon_ripple.png" +dest_files=["res://.godot/imported/moon_ripple.png-c5bceab477ceeb0f1860416a31f12bc6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/pierce_arrow.png b/apps/game/assets/custom/ui/icons/pierce_arrow.png new file mode 100644 index 0000000..0e2d838 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/pierce_arrow.png differ diff --git a/apps/game/assets/custom/ui/icons/pierce_arrow.png.import b/apps/game/assets/custom/ui/icons/pierce_arrow.png.import new file mode 100644 index 0000000..f308e4c --- /dev/null +++ b/apps/game/assets/custom/ui/icons/pierce_arrow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://q22apm52yitq" +path="res://.godot/imported/pierce_arrow.png-b9884698144656756071f3b936c838f7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/pierce_arrow.png" +dest_files=["res://.godot/imported/pierce_arrow.png-b9884698144656756071f3b936c838f7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/quick_arrow.png b/apps/game/assets/custom/ui/icons/quick_arrow.png new file mode 100644 index 0000000..a8ab07d Binary files /dev/null and b/apps/game/assets/custom/ui/icons/quick_arrow.png differ diff --git a/apps/game/assets/custom/ui/icons/quick_arrow.png.import b/apps/game/assets/custom/ui/icons/quick_arrow.png.import new file mode 100644 index 0000000..96fe5fc --- /dev/null +++ b/apps/game/assets/custom/ui/icons/quick_arrow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dm0j0df3aokmh" +path="res://.godot/imported/quick_arrow.png-002abfbfe087322a92df1e10e7266c5d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/quick_arrow.png" +dest_files=["res://.godot/imported/quick_arrow.png-002abfbfe087322a92df1e10e7266c5d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/shadow_veil.png b/apps/game/assets/custom/ui/icons/shadow_veil.png new file mode 100644 index 0000000..46165c7 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/shadow_veil.png differ diff --git a/apps/game/assets/custom/ui/icons/shadow_veil.png.import b/apps/game/assets/custom/ui/icons/shadow_veil.png.import new file mode 100644 index 0000000..a783b1c --- /dev/null +++ b/apps/game/assets/custom/ui/icons/shadow_veil.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cw3qolxh60krf" +path="res://.godot/imported/shadow_veil.png-dee7ad7db51d6534a05ce335732ed142.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/shadow_veil.png" +dest_files=["res://.godot/imported/shadow_veil.png-dee7ad7db51d6534a05ce335732ed142.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/sharp_moon.png b/apps/game/assets/custom/ui/icons/sharp_moon.png new file mode 100644 index 0000000..ee00029 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/sharp_moon.png differ diff --git a/apps/game/assets/custom/ui/icons/sharp_moon.png.import b/apps/game/assets/custom/ui/icons/sharp_moon.png.import new file mode 100644 index 0000000..ca2ce19 --- /dev/null +++ b/apps/game/assets/custom/ui/icons/sharp_moon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://baa32dsq40022" +path="res://.godot/imported/sharp_moon.png-62fada479afc91a2b6054aef5861ba16.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/sharp_moon.png" +dest_files=["res://.godot/imported/sharp_moon.png-62fada479afc91a2b6054aef5861ba16.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/swift_hand.png b/apps/game/assets/custom/ui/icons/swift_hand.png new file mode 100644 index 0000000..0658631 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/swift_hand.png differ diff --git a/apps/game/assets/custom/ui/icons/swift_hand.png.import b/apps/game/assets/custom/ui/icons/swift_hand.png.import new file mode 100644 index 0000000..f3d6bd8 --- /dev/null +++ b/apps/game/assets/custom/ui/icons/swift_hand.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7edvrk5fe7f7" +path="res://.godot/imported/swift_hand.png-612e24cae523735ad63617d2c2207410.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/swift_hand.png" +dest_files=["res://.godot/imported/swift_hand.png-612e24cae523735ad63617d2c2207410.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/tough_life.png b/apps/game/assets/custom/ui/icons/tough_life.png new file mode 100644 index 0000000..f485b82 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/tough_life.png differ diff --git a/apps/game/assets/custom/ui/icons/tough_life.png.import b/apps/game/assets/custom/ui/icons/tough_life.png.import new file mode 100644 index 0000000..1d103e5 --- /dev/null +++ b/apps/game/assets/custom/ui/icons/tough_life.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjsc0siirsi4k" +path="res://.godot/imported/tough_life.png-a51593c262e80229559deaf9210e6adb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/tough_life.png" +dest_files=["res://.godot/imported/tough_life.png-a51593c262e80229559deaf9210e6adb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/twin_arrow.png b/apps/game/assets/custom/ui/icons/twin_arrow.png new file mode 100644 index 0000000..9980ff5 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/twin_arrow.png differ diff --git a/apps/game/assets/custom/ui/icons/twin_arrow.png.import b/apps/game/assets/custom/ui/icons/twin_arrow.png.import new file mode 100644 index 0000000..906734e --- /dev/null +++ b/apps/game/assets/custom/ui/icons/twin_arrow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3ok26c37ck0y" +path="res://.godot/imported/twin_arrow.png-21de123f45c0acb55fc3ae022fd10890.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/twin_arrow.png" +dest_files=["res://.godot/imported/twin_arrow.png-21de123f45c0acb55fc3ae022fd10890.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/warm_beacon.png b/apps/game/assets/custom/ui/icons/warm_beacon.png new file mode 100644 index 0000000..3e332ca Binary files /dev/null and b/apps/game/assets/custom/ui/icons/warm_beacon.png differ diff --git a/apps/game/assets/custom/ui/icons/warm_beacon.png.import b/apps/game/assets/custom/ui/icons/warm_beacon.png.import new file mode 100644 index 0000000..959f51f --- /dev/null +++ b/apps/game/assets/custom/ui/icons/warm_beacon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cr3llioyg27hr" +path="res://.godot/imported/warm_beacon.png-52f76c01670cca2ca1075b3d1a851d86.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/warm_beacon.png" +dest_files=["res://.godot/imported/warm_beacon.png-52f76c01670cca2ca1075b3d1a851d86.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/icons/wide_arc.png b/apps/game/assets/custom/ui/icons/wide_arc.png new file mode 100644 index 0000000..6127ad1 Binary files /dev/null and b/apps/game/assets/custom/ui/icons/wide_arc.png differ diff --git a/apps/game/assets/custom/ui/icons/wide_arc.png.import b/apps/game/assets/custom/ui/icons/wide_arc.png.import new file mode 100644 index 0000000..a350fac --- /dev/null +++ b/apps/game/assets/custom/ui/icons/wide_arc.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cofjcaff5xx6o" +path="res://.godot/imported/wide_arc.png-0e49da036acbfa92b0606451bcc0f901.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/icons/wide_arc.png" +dest_files=["res://.godot/imported/wide_arc.png-0e49da036acbfa92b0606451bcc0f901.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/relic_card_disabled.png b/apps/game/assets/custom/ui/relic_card_disabled.png new file mode 100644 index 0000000..439fa8c Binary files /dev/null and b/apps/game/assets/custom/ui/relic_card_disabled.png differ diff --git a/apps/game/assets/custom/ui/relic_card_disabled.png.import b/apps/game/assets/custom/ui/relic_card_disabled.png.import new file mode 100644 index 0000000..0a69cb3 --- /dev/null +++ b/apps/game/assets/custom/ui/relic_card_disabled.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwbunu2cq5nya" +path="res://.godot/imported/relic_card_disabled.png-6a55dff273223189475e92b7311adcc9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/relic_card_disabled.png" +dest_files=["res://.godot/imported/relic_card_disabled.png-6a55dff273223189475e92b7311adcc9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/relic_card_hover.png b/apps/game/assets/custom/ui/relic_card_hover.png new file mode 100644 index 0000000..739067c Binary files /dev/null and b/apps/game/assets/custom/ui/relic_card_hover.png differ diff --git a/apps/game/assets/custom/ui/relic_card_hover.png.import b/apps/game/assets/custom/ui/relic_card_hover.png.import new file mode 100644 index 0000000..1bd832d --- /dev/null +++ b/apps/game/assets/custom/ui/relic_card_hover.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cborkkapndb0h" +path="res://.godot/imported/relic_card_hover.png-85c264f50b4db0978c25564fe7c08f68.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/relic_card_hover.png" +dest_files=["res://.godot/imported/relic_card_hover.png-85c264f50b4db0978c25564fe7c08f68.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/relic_card_normal.png b/apps/game/assets/custom/ui/relic_card_normal.png new file mode 100644 index 0000000..4b69c19 Binary files /dev/null and b/apps/game/assets/custom/ui/relic_card_normal.png differ diff --git a/apps/game/assets/custom/ui/relic_card_normal.png.import b/apps/game/assets/custom/ui/relic_card_normal.png.import new file mode 100644 index 0000000..61bc259 --- /dev/null +++ b/apps/game/assets/custom/ui/relic_card_normal.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3do8l0370oid" +path="res://.godot/imported/relic_card_normal.png-3f90fb00a66018fd8a8e2ad8630a5633.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/relic_card_normal.png" +dest_files=["res://.godot/imported/relic_card_normal.png-3f90fb00a66018fd8a8e2ad8630a5633.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/assets/custom/ui/relic_card_pressed.png b/apps/game/assets/custom/ui/relic_card_pressed.png new file mode 100644 index 0000000..0f9b59a Binary files /dev/null and b/apps/game/assets/custom/ui/relic_card_pressed.png differ diff --git a/apps/game/assets/custom/ui/relic_card_pressed.png.import b/apps/game/assets/custom/ui/relic_card_pressed.png.import new file mode 100644 index 0000000..1b2eb28 --- /dev/null +++ b/apps/game/assets/custom/ui/relic_card_pressed.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bf4gbi3pbfck1" +path="res://.godot/imported/relic_card_pressed.png-815f94bd37a2ce0a8e29225deb94e157.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/custom/ui/relic_card_pressed.png" +dest_files=["res://.godot/imported/relic_card_pressed.png-815f94bd37a2ce0a8e29225deb94e157.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/apps/game/resources/caster.tres b/apps/game/resources/caster.tres index 8c7965a..8450ff8 100644 --- a/apps/game/resources/caster.tres +++ b/apps/game/resources/caster.tres @@ -11,8 +11,8 @@ keep_distance = 150.0 shoot_interval = 2.2 sheet = ExtResource("sheet") cell = 48 -facings = 4 -frames = 4 +facings = 1 +frames = 6 lift = -8.0 speed = 66.0 turn_rate = 3.5 diff --git a/apps/game/resources/drifter.tres b/apps/game/resources/drifter.tres index 750797e..e94ec5e 100644 --- a/apps/game/resources/drifter.tres +++ b/apps/game/resources/drifter.tres @@ -8,8 +8,8 @@ script = ExtResource("script") display_name = "밤 박쥐" sheet = ExtResource("sheet") cell = 48 -facings = 4 -frames = 4 +facings = 1 +frames = 6 lift = -8.0 speed = 88.0 turn_rate = 2.2 diff --git a/apps/game/resources/ember.tres b/apps/game/resources/ember.tres index b1d52e6..05d1908 100644 --- a/apps/game/resources/ember.tres +++ b/apps/game/resources/ember.tres @@ -8,8 +8,8 @@ script = ExtResource("script") display_name = "불티 정령" sheet = ExtResource("sheet") cell = 48 -facings = 4 -frames = 4 +facings = 1 +frames = 6 lift = -8.0 speed = 104.0 turn_rate = 8.5 diff --git a/apps/game/resources/guardian.tres b/apps/game/resources/guardian.tres index 3cb5244..766a815 100644 --- a/apps/game/resources/guardian.tres +++ b/apps/game/resources/guardian.tres @@ -16,7 +16,7 @@ guardian_windup_sheet = ExtResource("windup") guardian_alt_windup_sheet = ExtResource("windup") guardian_charge_sheet = ExtResource("charge") guardian_recover_sheet = ExtResource("recover") -guardian_state_frames = 4 +guardian_state_frames = 6 sheet = ExtResource("sheet") cell = 64 facings = 1 diff --git a/apps/game/resources/guardian_camp.tres b/apps/game/resources/guardian_camp.tres index 0a0f882..62d7ff7 100644 --- a/apps/game/resources/guardian_camp.tres +++ b/apps/game/resources/guardian_camp.tres @@ -18,7 +18,7 @@ guardian_windup_sheet = ExtResource("windup") guardian_alt_windup_sheet = ExtResource("windup") guardian_charge_sheet = ExtResource("windup") guardian_recover_sheet = ExtResource("recover") -guardian_state_frames = 4 +guardian_state_frames = 6 keep_distance = 136.0 sheet = ExtResource("sheet") cell = 64 diff --git a/apps/game/resources/guardian_camp_siege.tres b/apps/game/resources/guardian_camp_siege.tres index 8964e91..78d7f87 100644 --- a/apps/game/resources/guardian_camp_siege.tres +++ b/apps/game/resources/guardian_camp_siege.tres @@ -18,7 +18,7 @@ guardian_windup_sheet = ExtResource("windup") guardian_alt_windup_sheet = ExtResource("windup") guardian_charge_sheet = ExtResource("windup") guardian_recover_sheet = ExtResource("recover") -guardian_state_frames = 4 +guardian_state_frames = 6 guardian_volley_extra = 1 guardian_recover_scale = 0.7 keep_distance = 128.0 diff --git a/apps/game/resources/guardian_field.tres b/apps/game/resources/guardian_field.tres index 07cd4b2..08147f3 100644 --- a/apps/game/resources/guardian_field.tres +++ b/apps/game/resources/guardian_field.tres @@ -19,7 +19,7 @@ guardian_windup_sheet = ExtResource("windup_cross") guardian_alt_windup_sheet = ExtResource("windup_radial") guardian_charge_sheet = ExtResource("windup_cross") guardian_recover_sheet = ExtResource("recover") -guardian_state_frames = 4 +guardian_state_frames = 6 orbit_radius = 124.0 sheet = ExtResource("sheet") cell = 64 diff --git a/apps/game/resources/guardian_field_storm.tres b/apps/game/resources/guardian_field_storm.tres index b60934f..396248d 100644 --- a/apps/game/resources/guardian_field_storm.tres +++ b/apps/game/resources/guardian_field_storm.tres @@ -19,7 +19,7 @@ guardian_windup_sheet = ExtResource("windup_cross") guardian_alt_windup_sheet = ExtResource("windup_radial") guardian_charge_sheet = ExtResource("windup_cross") guardian_recover_sheet = ExtResource("recover") -guardian_state_frames = 4 +guardian_state_frames = 6 guardian_volley_extra = 2 orbit_radius = 118.0 sheet = ExtResource("sheet") diff --git a/apps/game/resources/guardian_forest.tres b/apps/game/resources/guardian_forest.tres index c0464a4..e461ea2 100644 --- a/apps/game/resources/guardian_forest.tres +++ b/apps/game/resources/guardian_forest.tres @@ -19,7 +19,7 @@ guardian_windup_sheet = ExtResource("windup") guardian_alt_windup_sheet = ExtResource("windup") guardian_charge_sheet = ExtResource("charge") guardian_recover_sheet = ExtResource("recover") -guardian_state_frames = 4 +guardian_state_frames = 6 charge_windup = 0.72 charge_speed = 300.0 charge_seconds = 0.4 diff --git a/apps/game/resources/guardian_forest_thorn.tres b/apps/game/resources/guardian_forest_thorn.tres index 69b8a73..6e8a5ad 100644 --- a/apps/game/resources/guardian_forest_thorn.tres +++ b/apps/game/resources/guardian_forest_thorn.tres @@ -19,7 +19,7 @@ guardian_windup_sheet = ExtResource("windup") guardian_alt_windup_sheet = ExtResource("windup") guardian_charge_sheet = ExtResource("charge") guardian_recover_sheet = ExtResource("recover") -guardian_state_frames = 4 +guardian_state_frames = 6 guardian_combo = 2 charge_windup = 0.62 charge_speed = 340.0 diff --git a/apps/game/resources/relics/dew_hunter.tres b/apps/game/resources/relics/dew_hunter.tres index 2578632..1f8a03a 100644 --- a/apps/game/resources/relics/dew_hunter.tres +++ b/apps/game/resources/relics/dew_hunter.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/dew_hunter.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_DEW_HUNTER_DESC" effect = 8 amount = 1.5 accent = Color(1, 0.62, 0.66, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/heavy_arrow.tres b/apps/game/resources/relics/heavy_arrow.tres index 329b017..501f112 100644 --- a/apps/game/resources/relics/heavy_arrow.tres +++ b/apps/game/resources/relics/heavy_arrow.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/heavy_arrow.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_HEAVY_ARROW_DESC" effect = 13 amount = 1.18 accent = Color(1, 0.86, 0.5, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/light_step.tres b/apps/game/resources/relics/light_step.tres index 48ad33c..844ac06 100644 --- a/apps/game/resources/relics/light_step.tres +++ b/apps/game/resources/relics/light_step.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/light_step.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_LIGHT_STEP_DESC" effect = 4 amount = 18.0 accent = Color(0.78, 1, 0.82, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/long_blade.tres b/apps/game/resources/relics/long_blade.tres index 0901dc8..737030f 100644 --- a/apps/game/resources/relics/long_blade.tres +++ b/apps/game/resources/relics/long_blade.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/long_blade.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_LONG_BLADE_DESC" effect = 0 amount = 1.18 accent = Color(0.72, 0.9, 1, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/moon_dash.tres b/apps/game/resources/relics/moon_dash.tres index 1547806..f80bde3 100644 --- a/apps/game/resources/relics/moon_dash.tres +++ b/apps/game/resources/relics/moon_dash.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/moon_dash.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_MOON_DASH_DESC" effect = 6 amount = 0.65 accent = Color(0.78, 1, 0.82, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/moon_ring.tres b/apps/game/resources/relics/moon_ring.tres index e29fbed..627523b 100644 --- a/apps/game/resources/relics/moon_ring.tres +++ b/apps/game/resources/relics/moon_ring.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="1"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/moon_ring.png" id="icon"] [resource] script = ExtResource("1") @@ -9,3 +10,4 @@ description = "RELIC_MOON_RING_DESC" effect = 14 amount = 1.0 accent = Color(0.66, 0.85, 1, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/moon_ripple.tres b/apps/game/resources/relics/moon_ripple.tres index 58b2712..f19f84e 100644 --- a/apps/game/resources/relics/moon_ripple.tres +++ b/apps/game/resources/relics/moon_ripple.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="1"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/moon_ripple.png" id="icon"] [resource] script = ExtResource("1") @@ -9,3 +10,4 @@ description = "RELIC_MOON_RIPPLE_DESC" effect = 15 amount = 1.0 accent = Color(0.78, 0.92, 1, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/pierce_arrow.tres b/apps/game/resources/relics/pierce_arrow.tres index db8adde..f88a646 100644 --- a/apps/game/resources/relics/pierce_arrow.tres +++ b/apps/game/resources/relics/pierce_arrow.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/pierce_arrow.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_PIERCE_ARROW_DESC" effect = 11 amount = 1.0 accent = Color(0.72, 0.9, 1, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/quick_arrow.tres b/apps/game/resources/relics/quick_arrow.tres index 990f6a0..e52aa4f 100644 --- a/apps/game/resources/relics/quick_arrow.tres +++ b/apps/game/resources/relics/quick_arrow.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/quick_arrow.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_QUICK_ARROW_DESC" effect = 12 amount = 0.86 accent = Color(0.78, 1, 0.82, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/shadow_veil.tres b/apps/game/resources/relics/shadow_veil.tres index 77d2138..d3117f4 100644 --- a/apps/game/resources/relics/shadow_veil.tres +++ b/apps/game/resources/relics/shadow_veil.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/shadow_veil.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_SHADOW_VEIL_DESC" effect = 7 amount = 0.6 accent = Color(0.82, 0.78, 1, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/sharp_moon.tres b/apps/game/resources/relics/sharp_moon.tres index 32861af..49adf71 100644 --- a/apps/game/resources/relics/sharp_moon.tres +++ b/apps/game/resources/relics/sharp_moon.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/sharp_moon.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_SHARP_MOON_DESC" effect = 2 amount = 1.18 accent = Color(1, 0.86, 0.5, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/swift_hand.tres b/apps/game/resources/relics/swift_hand.tres index 779f95b..2ec00eb 100644 --- a/apps/game/resources/relics/swift_hand.tres +++ b/apps/game/resources/relics/swift_hand.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/swift_hand.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_SWIFT_HAND_DESC" effect = 1 amount = 0.86 accent = Color(0.72, 0.9, 1, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/tough_life.tres b/apps/game/resources/relics/tough_life.tres index 0404b32..a9920c9 100644 --- a/apps/game/resources/relics/tough_life.tres +++ b/apps/game/resources/relics/tough_life.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/tough_life.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_TOUGH_LIFE_DESC" effect = 5 amount = 1.0 accent = Color(1, 0.62, 0.66, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/twin_arrow.tres b/apps/game/resources/relics/twin_arrow.tres index 66e7a43..e436c29 100644 --- a/apps/game/resources/relics/twin_arrow.tres +++ b/apps/game/resources/relics/twin_arrow.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/twin_arrow.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_TWIN_ARROW_DESC" effect = 10 amount = 1.0 accent = Color(0.72, 0.9, 1, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/warm_beacon.tres b/apps/game/resources/relics/warm_beacon.tres index 8528e42..98d4a60 100644 --- a/apps/game/resources/relics/warm_beacon.tres +++ b/apps/game/resources/relics/warm_beacon.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/warm_beacon.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_WARM_BEACON_DESC" effect = 9 amount = 1.0 accent = Color(1, 0.72, 0.42, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/relics/wide_arc.tres b/apps/game/resources/relics/wide_arc.tres index 4d4398f..6f8a3fa 100644 --- a/apps/game/resources/relics/wide_arc.tres +++ b/apps/game/resources/relics/wide_arc.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="Relic" load_steps=2 format=3] +[gd_resource type="Resource" script_class="Relic" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/resources/relic.gd" id="script"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/icons/wide_arc.png" id="icon"] [resource] script = ExtResource("script") @@ -9,3 +10,4 @@ description = "RELIC_WIDE_ARC_DESC" effect = 3 amount = 1.25 accent = Color(1, 0.86, 0.5, 1) +icon = ExtResource("icon") diff --git a/apps/game/resources/stalker.tres b/apps/game/resources/stalker.tres index 160e363..51f5b7f 100644 --- a/apps/game/resources/stalker.tres +++ b/apps/game/resources/stalker.tres @@ -12,8 +12,8 @@ charge_speed = 300.0 charge_seconds = 0.5 sheet = ExtResource("sheet") cell = 48 -facings = 4 -frames = 4 +facings = 1 +frames = 6 lift = -8.0 speed = 74.0 turn_rate = 4.0 diff --git a/apps/game/resources/swarm.tres b/apps/game/resources/swarm.tres index f06e0c2..fdb083a 100644 --- a/apps/game/resources/swarm.tres +++ b/apps/game/resources/swarm.tres @@ -10,8 +10,8 @@ behavior = 0 sheet = ExtResource("sheet") cell = 48 -facings = 4 -frames = 4 +facings = 1 +frames = 6 lift = -8.0 speed = 118.0 turn_rate = 9.0 diff --git a/apps/game/resources/weaver.tres b/apps/game/resources/weaver.tres index a9f6338..5da2df2 100644 --- a/apps/game/resources/weaver.tres +++ b/apps/game/resources/weaver.tres @@ -11,8 +11,8 @@ orbit_radius = 88.0 orbit_closing = 9.0 sheet = ExtResource("sheet") cell = 48 -facings = 4 -frames = 4 +facings = 1 +frames = 6 lift = -8.0 speed = 92.0 turn_rate = 6.0 diff --git a/apps/game/resources/wisp.tres b/apps/game/resources/wisp.tres index 8a6b695..807361e 100644 --- a/apps/game/resources/wisp.tres +++ b/apps/game/resources/wisp.tres @@ -8,8 +8,8 @@ script = ExtResource("script") display_name = "떠도는 정령" sheet = ExtResource("sheet") cell = 48 -facings = 4 -frames = 4 +facings = 1 +frames = 6 lift = -8.0 speed = 68.0 turn_rate = 5.0 diff --git a/apps/game/scenes/ui/relic_panel.tscn b/apps/game/scenes/ui/relic_panel.tscn index 5da030a..0c43223 100644 --- a/apps/game/scenes/ui/relic_panel.tscn +++ b/apps/game/scenes/ui/relic_panel.tscn @@ -1,271 +1,221 @@ -[gd_scene load_steps=12 format=3] +[gd_scene load_steps=11 format=3 uid="uid://b6an0k3j9f2qe"] -[ext_resource type="Script" path="res://scripts/ui/relic_panel.gd" id="script"] +[ext_resource type="Script" path="res://scripts/ui/relic_panel.gd" id="1"] [ext_resource type="FontVariation" path="res://assets/third_party/fonts/Galmuri11-Bold-Multilingual.tres" id="font"] -[ext_resource type="Texture2D" path="res://assets/custom/ui/button_normal_moonlit.png" id="button_normal_texture"] -[ext_resource type="Texture2D" path="res://assets/custom/ui/button_hover_moonlit.png" id="button_hover_texture"] -[ext_resource type="Texture2D" path="res://assets/custom/ui/button_pressed_moonlit.png" id="button_pressed_texture"] -[ext_resource type="Texture2D" path="res://assets/custom/ui/button_disabled_moonlit.png" id="button_disabled_texture"] -[ext_resource type="Texture2D" path="res://assets/custom/ui/button_focus_moonlit.png" id="button_focus_texture"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/relic_card_normal.png" id="card_normal"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/relic_card_hover.png" id="card_hover"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/relic_card_pressed.png" id="card_pressed"] +[ext_resource type="Texture2D" path="res://assets/custom/ui/relic_card_disabled.png" id="card_disabled"] -[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_normal"] -texture = ExtResource("button_normal_texture") -texture_margin_left = 6.0 -texture_margin_top = 6.0 -texture_margin_right = 6.0 -texture_margin_bottom = 6.0 +[sub_resource type="LabelSettings" id="title"] +font = ExtResource("font") +font_size = 22 +font_color = Color(1, 0.93, 0.75, 1) -[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_hover"] -texture = ExtResource("button_hover_texture") -texture_margin_left = 6.0 -texture_margin_top = 6.0 -texture_margin_right = 6.0 -texture_margin_bottom = 6.0 +[sub_resource type="StyleBoxTexture" id="card_normal_sb"] +texture = ExtResource("card_normal") -[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_pressed"] -texture = ExtResource("button_pressed_texture") -texture_margin_left = 6.0 -texture_margin_top = 6.0 -texture_margin_right = 6.0 -texture_margin_bottom = 6.0 -modulate_color = Color(1, 1, 1, 0.96) +[sub_resource type="StyleBoxTexture" id="card_hover_sb"] +texture = ExtResource("card_hover") -[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_disabled"] -texture = ExtResource("button_disabled_texture") -texture_margin_left = 6.0 -texture_margin_top = 6.0 -texture_margin_right = 6.0 -texture_margin_bottom = 6.0 -modulate_color = Color(1, 1, 1, 0.78) +[sub_resource type="StyleBoxTexture" id="card_pressed_sb"] +texture = ExtResource("card_pressed") -[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_focus"] -texture = ExtResource("button_focus_texture") -texture_margin_left = 6.0 -texture_margin_top = 6.0 -texture_margin_right = 6.0 -texture_margin_bottom = 6.0 -draw_center = false +[sub_resource type="StyleBoxTexture" id="card_disabled_sb"] +texture = ExtResource("card_disabled") [node name="RelicPanel" type="Control"] -process_mode = 3 -layout_mode = 3 +visible = false anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 1 -script = ExtResource("script") - -[node name="Dim" type="ColorRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 -color = Color(0.02, 0.03, 0.07, 0.72) +script = ExtResource("1") [node name="Center" type="CenterContainer" parent="."] -layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 [node name="Rows" type="VBoxContainer" parent="Center"] layout_mode = 2 -mouse_filter = 2 -theme_override_constants/separation = 10 +theme_override_constants/separation = 14 alignment = 1 [node name="Title" type="Label" parent="Center/Rows"] layout_mode = 2 -mouse_filter = 2 -theme_override_colors/font_color = Color(1, 0.92, 0.72, 1) -theme_override_fonts/font = ExtResource("font") -theme_override_font_sizes/font_size = 18 +label_settings = SubResource("title") text = "RELIC_PICK" horizontal_alignment = 1 [node name="Cards" type="HBoxContainer" parent="Center/Rows"] layout_mode = 2 -theme_override_constants/separation = 8 +theme_override_constants/separation = 10 alignment = 1 [node name="C0" type="Button" parent="Center/Rows/Cards"] custom_minimum_size = Vector2(168, 112) layout_mode = 2 -clip_contents = true -theme_override_styles/normal = SubResource("StyleBoxTexture_normal") -theme_override_styles/hover = SubResource("StyleBoxTexture_hover") -theme_override_styles/pressed = SubResource("StyleBoxTexture_pressed") -theme_override_styles/disabled = SubResource("StyleBoxTexture_disabled") -theme_override_styles/focus = SubResource("StyleBoxTexture_focus") +theme_override_styles/normal = SubResource("card_normal_sb") +theme_override_styles/hover = SubResource("card_hover_sb") +theme_override_styles/pressed = SubResource("card_pressed_sb") +theme_override_styles/disabled = SubResource("card_disabled_sb") + +[node name="Icon" type="TextureRect" parent="Center/Rows/Cards/C0"] +layout_mode = 1 +anchors_preset = -1 +offset_left = 12.0 +offset_top = 12.0 +offset_right = 46.0 +offset_bottom = 46.0 +mouse_filter = 2 +texture = ExtResource("card_normal") +expand_mode = 1 +stretch_mode = 5 [node name="Name" type="Label" parent="Center/Rows/Cards/C0"] layout_mode = 1 anchors_preset = 10 -anchor_right = 1.0 -offset_top = 12.0 +offset_left = 56.0 +offset_top = 10.0 +offset_right = -8.0 offset_bottom = 34.0 -grow_horizontal = 2 -mouse_filter = 2 theme_override_fonts/font = ExtResource("font") theme_override_font_sizes/font_size = 14 -horizontal_alignment = 1 +text = "Name" +vertical_alignment = 1 [node name="Desc" type="Label" parent="Center/Rows/Cards/C0"] layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 12.0 -offset_top = 37.0 -offset_right = -12.0 -offset_bottom = -29.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 -theme_override_colors/font_color = Color(0.85, 0.89, 0.98, 1) +anchors_preset = 10 +offset_left = 10.0 +offset_top = 50.0 +offset_right = -10.0 +offset_bottom = 84.0 theme_override_fonts/font = ExtResource("font") -theme_override_font_sizes/font_size = 10 -horizontal_alignment = 1 +theme_override_font_sizes/font_size = 9 +text = "Desc" autowrap_mode = 2 [node name="Route" type="Label" parent="Center/Rows/Cards/C0"] layout_mode = 1 -anchors_preset = 12 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 8.0 -offset_top = -27.0 -offset_right = -8.0 -offset_bottom = -8.0 -grow_horizontal = 2 -grow_vertical = 0 -mouse_filter = 2 -theme_override_colors/font_color = Color(0.68, 0.88, 1, 1) +anchors_preset = 10 +offset_left = 14.0 +offset_top = 84.0 +offset_right = -14.0 +offset_bottom = 102.0 theme_override_fonts/font = ExtResource("font") -theme_override_font_sizes/font_size = 9 +theme_override_font_sizes/font_size = 8 +text = "Route" horizontal_alignment = 1 -vertical_alignment = 1 [node name="C1" type="Button" parent="Center/Rows/Cards"] custom_minimum_size = Vector2(168, 112) layout_mode = 2 -clip_contents = true -theme_override_styles/normal = SubResource("StyleBoxTexture_normal") -theme_override_styles/hover = SubResource("StyleBoxTexture_hover") -theme_override_styles/pressed = SubResource("StyleBoxTexture_pressed") -theme_override_styles/disabled = SubResource("StyleBoxTexture_disabled") -theme_override_styles/focus = SubResource("StyleBoxTexture_focus") +theme_override_styles/normal = SubResource("card_normal_sb") +theme_override_styles/hover = SubResource("card_hover_sb") +theme_override_styles/pressed = SubResource("card_pressed_sb") +theme_override_styles/disabled = SubResource("card_disabled_sb") + +[node name="Icon" type="TextureRect" parent="Center/Rows/Cards/C1"] +layout_mode = 1 +anchors_preset = -1 +offset_left = 12.0 +offset_top = 12.0 +offset_right = 46.0 +offset_bottom = 46.0 +mouse_filter = 2 +texture = ExtResource("card_normal") +expand_mode = 1 +stretch_mode = 5 [node name="Name" type="Label" parent="Center/Rows/Cards/C1"] layout_mode = 1 anchors_preset = 10 -anchor_right = 1.0 -offset_top = 12.0 +offset_left = 56.0 +offset_top = 10.0 +offset_right = -8.0 offset_bottom = 34.0 -grow_horizontal = 2 -mouse_filter = 2 theme_override_fonts/font = ExtResource("font") theme_override_font_sizes/font_size = 14 -horizontal_alignment = 1 +text = "Name" +vertical_alignment = 1 [node name="Desc" type="Label" parent="Center/Rows/Cards/C1"] layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 12.0 -offset_top = 37.0 -offset_right = -12.0 -offset_bottom = -29.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 -theme_override_colors/font_color = Color(0.85, 0.89, 0.98, 1) +anchors_preset = 10 +offset_left = 10.0 +offset_top = 50.0 +offset_right = -10.0 +offset_bottom = 84.0 theme_override_fonts/font = ExtResource("font") -theme_override_font_sizes/font_size = 10 -horizontal_alignment = 1 +theme_override_font_sizes/font_size = 9 +text = "Desc" autowrap_mode = 2 [node name="Route" type="Label" parent="Center/Rows/Cards/C1"] layout_mode = 1 -anchors_preset = 12 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 8.0 -offset_top = -27.0 -offset_right = -8.0 -offset_bottom = -8.0 -grow_horizontal = 2 -grow_vertical = 0 -mouse_filter = 2 -theme_override_colors/font_color = Color(0.68, 0.88, 1, 1) +anchors_preset = 10 +offset_left = 14.0 +offset_top = 84.0 +offset_right = -14.0 +offset_bottom = 102.0 theme_override_fonts/font = ExtResource("font") -theme_override_font_sizes/font_size = 9 +theme_override_font_sizes/font_size = 8 +text = "Route" horizontal_alignment = 1 -vertical_alignment = 1 [node name="C2" type="Button" parent="Center/Rows/Cards"] custom_minimum_size = Vector2(168, 112) layout_mode = 2 -clip_contents = true -theme_override_styles/normal = SubResource("StyleBoxTexture_normal") -theme_override_styles/hover = SubResource("StyleBoxTexture_hover") -theme_override_styles/pressed = SubResource("StyleBoxTexture_pressed") -theme_override_styles/disabled = SubResource("StyleBoxTexture_disabled") -theme_override_styles/focus = SubResource("StyleBoxTexture_focus") +theme_override_styles/normal = SubResource("card_normal_sb") +theme_override_styles/hover = SubResource("card_hover_sb") +theme_override_styles/pressed = SubResource("card_pressed_sb") +theme_override_styles/disabled = SubResource("card_disabled_sb") + +[node name="Icon" type="TextureRect" parent="Center/Rows/Cards/C2"] +layout_mode = 1 +anchors_preset = -1 +offset_left = 12.0 +offset_top = 12.0 +offset_right = 46.0 +offset_bottom = 46.0 +mouse_filter = 2 +texture = ExtResource("card_normal") +expand_mode = 1 +stretch_mode = 5 [node name="Name" type="Label" parent="Center/Rows/Cards/C2"] layout_mode = 1 anchors_preset = 10 -anchor_right = 1.0 -offset_top = 12.0 +offset_left = 56.0 +offset_top = 10.0 +offset_right = -8.0 offset_bottom = 34.0 -grow_horizontal = 2 -mouse_filter = 2 theme_override_fonts/font = ExtResource("font") theme_override_font_sizes/font_size = 14 -horizontal_alignment = 1 +text = "Name" +vertical_alignment = 1 [node name="Desc" type="Label" parent="Center/Rows/Cards/C2"] layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 12.0 -offset_top = 37.0 -offset_right = -12.0 -offset_bottom = -29.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 -theme_override_colors/font_color = Color(0.85, 0.89, 0.98, 1) +anchors_preset = 10 +offset_left = 10.0 +offset_top = 50.0 +offset_right = -10.0 +offset_bottom = 84.0 theme_override_fonts/font = ExtResource("font") -theme_override_font_sizes/font_size = 10 -horizontal_alignment = 1 +theme_override_font_sizes/font_size = 9 +text = "Desc" autowrap_mode = 2 [node name="Route" type="Label" parent="Center/Rows/Cards/C2"] layout_mode = 1 -anchors_preset = 12 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 8.0 -offset_top = -27.0 -offset_right = -8.0 -offset_bottom = -8.0 -grow_horizontal = 2 -grow_vertical = 0 -mouse_filter = 2 -theme_override_colors/font_color = Color(0.68, 0.88, 1, 1) +anchors_preset = 10 +offset_left = 14.0 +offset_top = 84.0 +offset_right = -14.0 +offset_bottom = 102.0 theme_override_fonts/font = ExtResource("font") -theme_override_font_sizes/font_size = 9 +theme_override_font_sizes/font_size = 8 +text = "Route" horizontal_alignment = 1 -vertical_alignment = 1 diff --git a/apps/game/scripts/actors/spirit.gd b/apps/game/scripts/actors/spirit.gd index a6c6837..d4ef65c 100644 --- a/apps/game/scripts/actors/spirit.gd +++ b/apps/game/scripts/actors/spirit.gd @@ -18,6 +18,9 @@ signal touched_player(from_position: Vector2) ## So it passes where it died (`at`) and whether it was elite. Without elite status ## Arena cannot keep the promise that "elites always drop a reward." signal perished(kind: SpiritKind, at: Vector2, was_elite: bool) +## Fired when materialize finishes. Arena shakes the screen for guardians only; +## trash landing every second must not rattle the camera. +signal landed() ## Which spirit. One `.tres` is one kind. ## @@ -49,6 +52,9 @@ const PLAYER_BODY_OFFSET: Vector2 = Vector2(0, -4) ## Time to appear from the dark. Untouchable while materializing. const MATERIALIZE_SECONDS: float = 0.9 +## Guardian landing ring after materialize. A boss should hit the ground, +## not fade in like trash. +const SLAM_SECONDS: float = 0.4 ## Names `_face()` uses every frame. Prebuilt so it does not concatenate on the spot. const FLOAT_NAMES: Array[StringName] = [ @@ -176,6 +182,8 @@ var _guardian_pattern: int = 0 var _guardian_poke_left: float = FIELD_POKE_INTERVAL * 0.5 ## Short white edge flash when camp armor blocks damage. var _camp_guard_flash: float = 0.0 +## Guardian landing-ring time left. Trash never sets this. +var _slam_left: float = 0.0 var _telegraph: Telegraph = Telegraph.NONE var _telegraph_direction: Vector2 = Vector2.RIGHT var _telegraph_ratio: float = 0.0 @@ -412,10 +420,14 @@ func _perish() -> void: perished.emit(kind, global_position, elite) set_physics_interpolation_mode(Node.PHYSICS_INTERPOLATION_MODE_OFF) + # A boss bursts bigger than trash. Same fade length, wider pop. + var burst: float = 1.6 \ + if kind != null and kind.behavior == SpiritKind.Behavior.GUARDIAN \ + else 1.35 var out: Tween = create_tween() out.set_parallel(true) out.tween_property(self, "modulate:a", 0.0, PERISH_SECONDS) - out.tween_property(self, "scale", scale * 1.35, PERISH_SECONDS) \ + out.tween_property(self, "scale", scale * burst, PERISH_SECONDS) \ .set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) out.chain().tween_callback(queue_free) @@ -566,11 +578,20 @@ func materialize() -> void: await fade.finished if not is_inside_tree(): return + _finish_materialize() + + +## End of the appear fade, split out so tests can land a spirit without frames. +func _finish_materialize() -> void: # Off the target list while appearing, but already-flying AoE can still call # `take_damage()` directly. When materialization ends, reopen the opening shock budget # so the first on-screen frame still leaves real seconds of fight. _reset_guardian_damage_budget() _materialized = true + if kind != null and kind.behavior == SpiritKind.Behavior.GUARDIAN: + _slam_left = SLAM_SECONDS + queue_redraw() + landed.emit() ## Retreat. When a guardian appears, fodder fades into the dark. @@ -621,6 +642,9 @@ func _physics_process(delta: float) -> void: if _camp_guard_flash > 0.0: _camp_guard_flash = maxf(_camp_guard_flash - delta * 7.0, 0.0) queue_redraw() + if _slam_left > 0.0: + _slam_left = maxf(_slam_left - delta, 0.0) + queue_redraw() # Do nothing while perishing. The tween frees itself when done. if _perishing: @@ -1151,6 +1175,11 @@ func _clear_telegraph() -> void: func _draw() -> void: if kind != null and kind.behavior == SpiritKind.Behavior.GUARDIAN: _draw_guardian_silhouette() + if _slam_left > 0.0: + _draw_slam_ring() + if kind != null and kind.behavior == SpiritKind.Behavior.GUARDIAN \ + and _guardian_move == GuardianMove.CHARGE: + _draw_charge_streaks() match _telegraph: Telegraph.AIM: @@ -1167,6 +1196,28 @@ func _draw() -> void: _draw_field_volley_telegraph() +## Landing ring. Expands and thins in the boss accent, then gone in 0.4s. +func _draw_slam_ring() -> void: + var t: float = 1.0 - _slam_left / SLAM_SECONDS + var radius: float = 12.0 + 46.0 * t + draw_arc(Vector2(0, -6), radius, 0.0, TAU, 40, + _boss_color(0.85 * (1.0 - t), 1.9), + 0.6 + 2.2 * (1.0 - t), false) + + +## Charge streaks. Three speed lines trail the rushing boss so the corridor +## telegraph's promise reads in motion too. +func _draw_charge_streaks() -> void: + if _locked.length_squared() < 0.001: + return + var back: Vector2 = -_locked.normalized() + var side := Vector2(-back.y, back.x) + for i in [-1.0, 0.0, 1.0]: + var off: Vector2 = side * (i * 9.0) + Vector2(0, -6) + draw_line(off + back * 6.0, off + back * 30.0, + _boss_color(0.5, 1.7), 2.4 - absf(i) * 0.7) + + ## Procedurally add crown, wings, and armor on the same 50px sheet so silhouettes split first. ## No extra sprites or nodes — one scene even when the boss changes. func _draw_guardian_silhouette() -> void: diff --git a/apps/game/scripts/gameplay/arena.gd b/apps/game/scripts/gameplay/arena.gd index febe7c8..d9bbaa5 100644 --- a/apps/game/scripts/gameplay/arena.gd +++ b/apps/game/scripts/gameplay/arena.gd @@ -3467,9 +3467,23 @@ func _register_spirit(spirit: Node2D) -> void: spirit.set_projectile_hit_handler(_on_player_hit) spirit.touched_player.connect(_on_player_hit) spirit.perished.connect(_on_spirit_perished) + spirit.landed.connect(_on_spirit_landed.bind(spirit)) _spirits.append(spirit) +## Guardian landing slam. Skipped while capture is frozen — a mid-shot +## camera kick ejects the capture the same way a summon does. +func _on_spirit_landed(spirit: Node2D) -> void: + if _over or _transitioning or _capture_progress_frozen: + return + if spirit == null or not is_instance_valid(spirit): + return + var kind: SpiritKind = spirit.get("kind") as SpiritKind + if kind == null or kind.behavior != SpiritKind.Behavior.GUARDIAN: + return + _shake(2.2) + + func _clear_hostile_projectiles() -> void: for projectile in get_tree().get_nodes_in_group("hostile_projectiles"): if is_instance_valid(projectile): @@ -4289,6 +4303,12 @@ func _on_spirit_perished(kind: SpiritKind, at: Vector2, was_elite: bool) -> void _gain_missile_progress(at, was_elite, was_guardian) _gain_progress(not was_guardian) _heat_up() + if was_guardian: + # A boss kill kicks the full shake budget. Frozen capture skips it — + # the boss vanishing already changes that frame enough. + if not _capture_progress_frozen: + _shake(4.0) + return # Pricier heroes get a heavier kill recoil. **Presentation only** — # damage, fire rate, and pierce stay sidegrade-equal; only the weight in the hand # follows the tier. diff --git a/apps/game/scripts/resources/relic.gd b/apps/game/scripts/resources/relic.gd index db05b59..94c34fa 100644 --- a/apps/game/scripts/resources/relic.gd +++ b/apps/game/scripts/resources/relic.gd @@ -223,3 +223,6 @@ static func evolution_states(relics: Array) -> Dictionary: ## Color for the card. Kind is told apart by color. @export var accent: Color = Color(1, 0.86, 0.5, 1) + +## 48x48 card emblem for this relic. +@export var icon: Texture2D = null diff --git a/apps/game/scripts/resources/spirit_kind.gd b/apps/game/scripts/resources/spirit_kind.gd index af485b4..51f7bf9 100644 --- a/apps/game/scripts/resources/spirit_kind.gd +++ b/apps/game/scripts/resources/spirit_kind.gd @@ -107,7 +107,7 @@ static func guardian_toughness_scale(cycle: int) -> float: @export var guardian_alt_windup_sheet: Texture2D = null @export var guardian_charge_sheet: Texture2D = null @export var guardian_recover_sheet: Texture2D = null -@export_range(1, 12, 1) var guardian_state_frames: int = 4 +@export_range(1, 12, 1) var guardian_state_frames: int = 6 @export_range(1.0, 24.0, 0.5) var guardian_state_fps: float = 6.0 ## Extra follow-up count for the forest charge. 1 means two telegraphed charges. @export_range(1, 6, 1) var guardian_combo: int = 1 diff --git a/apps/game/scripts/ui/relic_panel.gd b/apps/game/scripts/ui/relic_panel.gd index 47fa9e6..e527578 100644 --- a/apps/game/scripts/ui/relic_panel.gd +++ b/apps/game/scripts/ui/relic_panel.gd @@ -97,10 +97,10 @@ const DISC_EVOLVE_AT: int = 3 const CARD_COUNT: int = 3 const NAME_FONT_MAX: int = 14 const NAME_FONT_MIN: int = 9 -const NAME_AVAILABLE_WIDTH: float = 156.0 +const NAME_AVAILABLE_WIDTH: float = 100.0 const ROUTE_FONT_MAX: int = 9 const ROUTE_FONT_MIN: int = 7 -const ROUTE_AVAILABLE_WIDTH: float = 152.0 +const ROUTE_AVAILABLE_WIDTH: float = 140.0 @onready var _cards: Array[Button] = [$Center/Rows/Cards/C0, $Center/Rows/Cards/C1, $Center/Rows/Cards/C2] @onready var _title: Label = $Center/Rows/Title @@ -110,6 +110,8 @@ const ROUTE_AVAILABLE_WIDTH: float = 152.0 $Center/Rows/Cards/C0/Desc, $Center/Rows/Cards/C1/Desc, $Center/Rows/Cards/C2/Desc] @onready var _routes: Array[Label] = [ $Center/Rows/Cards/C0/Route, $Center/Rows/Cards/C1/Route, $Center/Rows/Cards/C2/Route] +@onready var _icons: Array[TextureRect] = [ + $Center/Rows/Cards/C0/Icon, $Center/Rows/Cards/C1/Icon, $Center/Rows/Cards/C2/Icon] var _offer: Array[Relic] = [] ## First pick shows the three paths one card each at equal weight. Shuffle @@ -197,6 +199,7 @@ func _open_offer(offer: Array[Relic], title: String) -> void: _fit_name(_names[i]) _names[i].add_theme_color_override("font_color", _offer[i].accent) _descs[i].text = tr(_offer[i].description) + _icons[i].texture = _offer[i].icon _set_route_label(i, _offer[i]) banner_cleared.emit() diff --git a/apps/game/tests/render_relic_preview.gd b/apps/game/tests/render_relic_preview.gd new file mode 100644 index 0000000..0e0955b --- /dev/null +++ b/apps/game/tests/render_relic_preview.gd @@ -0,0 +1,15 @@ +extends Node + +const PANEL: PackedScene = preload("res://scenes/ui/relic_panel.tscn") + + +func _ready() -> void: + var panel: Control = PANEL.instantiate() as Control + add_child(panel) + panel.set("visible", true) + var offer: Array[Relic] = [ + load("res://resources/relics/long_blade.tres"), + load("res://resources/relics/moon_ring.tres"), + load("res://resources/relics/twin_arrow.tres"), + ] + panel.call("_open_offer", offer, "The moonlight offers a gift") diff --git a/apps/game/tests/render_relic_preview.gd.uid b/apps/game/tests/render_relic_preview.gd.uid new file mode 100644 index 0000000..bdf4721 --- /dev/null +++ b/apps/game/tests/render_relic_preview.gd.uid @@ -0,0 +1 @@ +uid://ctuhftgtfmk0 diff --git a/apps/game/tests/render_relic_preview.tscn b/apps/game/tests/render_relic_preview.tscn new file mode 100644 index 0000000..9f0f38a --- /dev/null +++ b/apps/game/tests/render_relic_preview.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/render_relic_preview.gd" id="1"] + +[node name="RenderRelicPreview" type="Node"] +script = ExtResource("1") diff --git a/apps/game/tests/test_guardian_moves.gd b/apps/game/tests/test_guardian_moves.gd index 8b33b8b..1896c38 100644 --- a/apps/game/tests/test_guardian_moves.gd +++ b/apps/game/tests/test_guardian_moves.gd @@ -27,6 +27,7 @@ func _run() -> void: _test_forest_ring() _test_field_poke() _test_camp_double_fan() + _test_guardian_landing() _finish() @@ -114,6 +115,28 @@ func _test_camp_double_fan() -> void: spirit.free() +func _test_guardian_landing() -> void: + var boss: Node2D = _spawn("res://resources/guardian_forest.tres") + var landed_count: Array[int] = [0] + boss.connect("landed", func() -> void: landed_count[0] += 1) + boss.call("_finish_materialize") + _expect_equal(landed_count[0], 1, "guardian landing emits once") + _expect_equal( + float(boss.get("_slam_left")), SPIRIT_SCRIPT.SLAM_SECONDS, + "guardian landing starts the slam ring") + boss.call("_physics_process", 0.5) + _expect_equal( + float(boss.get("_slam_left")), 0.0, + "slam ring drains within half a second") + boss.free() + var trash: Node2D = _spawn("res://resources/wisp.tres") + trash.call("_finish_materialize") + _expect_equal( + float(trash.get("_slam_left")), 0.0, + "trash landing starts no slam ring") + trash.free() + + func _spawn(resource_path: String) -> Node2D: var kind: SpiritKind = ( load(resource_path) as SpiritKind).duplicate() diff --git a/apps/game/tests/test_spirit_visuals.gd b/apps/game/tests/test_spirit_visuals.gd index 014d4bc..48642fc 100644 --- a/apps/game/tests/test_spirit_visuals.gd +++ b/apps/game/tests/test_spirit_visuals.gd @@ -159,10 +159,10 @@ func _test_enemies() -> void: _expect_false( kind.sheet.resource_path.contains("/third_party/"), "%s does not use free-pack assets" % enemy_id) - _expect_equal(kind.sheet.get_size(), Vector2(192, 192), "%s sheet size" % enemy_id) + _expect_equal(kind.sheet.get_size(), Vector2(288, 48), "%s sheet size" % enemy_id) _expect_equal(kind.cell, ENEMY_CELL, "%s cell size" % enemy_id) - _expect_equal(kind.facings, 4, "%s facing count" % enemy_id) - _expect_equal(kind.frames, 4, "%s frame count" % enemy_id) + _expect_equal(kind.facings, 1, "%s facing count" % enemy_id) + _expect_equal(kind.frames, 6, "%s frame count" % enemy_id) _expect_equal(kind.lift, -8.0, "%s shadow foot origin" % enemy_id) _expect_equal(kind.tint, Color.WHITE, "%s original palette preserved" % enemy_id) @@ -184,7 +184,7 @@ func _test_enemies() -> void: alpha_masks[alpha_digest] = true full_images[full_digest] = true - _test_runtime_kind(enemy_id, kind, expected_sheet, 4, 4, ENEMY_CELL) + _test_runtime_kind(enemy_id, kind, expected_sheet, 1, 6, ENEMY_CELL) _expect_equal(sheet_paths.size(), 7, "7 dedicated regular-enemy sheets") _expect_equal(alpha_masks.size(), 7, "7 regular-enemy silhouettes") @@ -246,15 +246,15 @@ func _test_guardians() -> void: kind.guardian_recover_sheet, ]: _expect_true( - state_texture.get_size().x >= 256 + state_texture.get_size().x >= 384 and state_texture.get_size().y == 64, - "%s state sheet at least 4 frames" % guardian_id) + "%s state sheet at least 6 frames" % guardian_id) _expect_equal(kind.cell, GUARDIAN_CELL, "%s cell size" % guardian_id) _expect_equal(kind.facings, 1, "%s facing count" % guardian_id) _expect_equal(kind.frames, 6, "%s idle frame count" % guardian_id) _expect_equal( kind.guardian_state_frames, - 4, + 6, "%s state frame count" % guardian_id) _expect_equal(kind.lift, -24.0, "%s shadow foot origin" % guardian_id) _expect_equal(kind.tint, Color.WHITE, "%s original palette preserved" % guardian_id) @@ -342,7 +342,7 @@ func _test_runtime_kind( continue _expect_equal( frames.get_frame_count(state_animation), - 4, + 6, "%s %s frame count" % [actor_id, state_animation]) var expected_texture: Texture2D = [ kind.guardian_windup_sheet, @@ -350,7 +350,7 @@ func _test_runtime_kind( kind.guardian_charge_sheet, kind.guardian_recover_sheet, ][animation_index] - for frame_index in 4: + for frame_index in 6: _expect_atlas( frames.get_frame_texture( state_animation, frame_index), diff --git a/apps/game/tools/custom_asset_contracts.json b/apps/game/tools/custom_asset_contracts.json index c80f30e..d73f6f8 100644 --- a/apps/game/tools/custom_asset_contracts.json +++ b/apps/game/tools/custom_asset_contracts.json @@ -37,8 +37,8 @@ "path": "assets/custom/actors/guardians/camp.png", "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { "columns": 6, "rows": 1, @@ -51,19 +51,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.camp.recover", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/camp_recover.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -74,19 +75,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.camp.windup", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/camp_windup.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -97,7 +99,8 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.camp_siege", @@ -106,8 +109,8 @@ "path": "assets/custom/actors/guardians/camp_siege.png", "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { "columns": 6, "rows": 1, @@ -120,19 +123,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.camp_siege.recover", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/camp_siege_recover.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -143,19 +147,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.camp_siege.windup", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/camp_siege_windup.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -166,7 +171,8 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field", @@ -175,8 +181,8 @@ "path": "assets/custom/actors/guardians/field.png", "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { "columns": 6, "rows": 1, @@ -189,19 +195,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field.recover", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/field_recover.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -212,19 +219,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field.windup_cross", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/field_windup_cross.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -235,19 +243,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field.windup_radial", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/field_windup_radial.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -258,7 +267,8 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field_storm", @@ -267,8 +277,8 @@ "path": "assets/custom/actors/guardians/field_storm.png", "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { "columns": 6, "rows": 1, @@ -281,19 +291,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field_storm.recover", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/field_storm_recover.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -304,19 +315,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field_storm.windup_cross", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/field_storm_windup_cross.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -327,19 +339,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.field_storm.windup_radial", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/field_storm_windup_radial.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -350,7 +363,8 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest", @@ -359,8 +373,8 @@ "path": "assets/custom/actors/guardians/forest.png", "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { "columns": 6, "rows": 1, @@ -373,19 +387,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest.charge", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/forest_charge.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -396,19 +411,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest.recover", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/forest_recover.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -419,19 +435,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest.windup", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/forest_windup.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -442,7 +459,8 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest_thorn", @@ -451,8 +469,8 @@ "path": "assets/custom/actors/guardians/forest_thorn.png", "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { "columns": 6, "rows": 1, @@ -465,19 +483,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest_thorn.charge", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/forest_thorn_charge.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -488,19 +507,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest_thorn.recover", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/forest_thorn_recover.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -511,19 +531,20 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "boss.forest_thorn.windup", "category": "boss", "status": "final", "path": "assets/custom/actors/guardians/forest_thorn_windup.png", - "width": 256, + "width": 384, "height": 64, - "alpha_mode": "binary", - "max_visible_rgb_colors": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, + "columns": 6, "rows": 1, "cell_width": 64, "cell_height": 64, @@ -534,133 +555,141 @@ "top": 2, "bottom": 0 } - } + }, + "max_alpha_levels": 256 }, { "id": "enemy.caster", "category": "enemy", "status": "final", "path": "assets/custom/actors/spirits/caster.png", - "width": 192, - "height": 192, - "alpha_mode": "binary", - "max_visible_rgb_colors": 4096, + "width": 288, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, - "rows": 4, + "columns": 6, + "rows": 1, "cell_width": 48, "cell_height": 48, "allow_blank_frames": false, "edge_padding": 1 - } + }, + "max_alpha_levels": 256 }, { "id": "enemy.drifter", "category": "enemy", "status": "final", "path": "assets/custom/actors/spirits/drifter.png", - "width": 192, - "height": 192, - "alpha_mode": "binary", - "max_visible_rgb_colors": 4096, + "width": 288, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, - "rows": 4, + "columns": 6, + "rows": 1, "cell_width": 48, "cell_height": 48, "allow_blank_frames": false, "edge_padding": 1 - } + }, + "max_alpha_levels": 256 }, { "id": "enemy.ember", "category": "enemy", "status": "final", "path": "assets/custom/actors/spirits/ember.png", - "width": 192, - "height": 192, - "alpha_mode": "binary", - "max_visible_rgb_colors": 4096, + "width": 288, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, - "rows": 4, + "columns": 6, + "rows": 1, "cell_width": 48, "cell_height": 48, "allow_blank_frames": false, "edge_padding": 1 - } + }, + "max_alpha_levels": 256 }, { "id": "enemy.stalker", "category": "enemy", "status": "final", "path": "assets/custom/actors/spirits/stalker.png", - "width": 192, - "height": 192, - "alpha_mode": "binary", - "max_visible_rgb_colors": 4096, + "width": 288, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, - "rows": 4, + "columns": 6, + "rows": 1, "cell_width": 48, "cell_height": 48, "allow_blank_frames": false, "edge_padding": 1 - } + }, + "max_alpha_levels": 256 }, { "id": "enemy.swarm", "category": "enemy", "status": "final", "path": "assets/custom/actors/spirits/swarm.png", - "width": 192, - "height": 192, - "alpha_mode": "binary", - "max_visible_rgb_colors": 4096, + "width": 288, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, - "rows": 4, + "columns": 6, + "rows": 1, "cell_width": 48, "cell_height": 48, "allow_blank_frames": false, "edge_padding": 1 - } + }, + "max_alpha_levels": 256 }, { "id": "enemy.weaver", "category": "enemy", "status": "final", "path": "assets/custom/actors/spirits/weaver.png", - "width": 192, - "height": 192, - "alpha_mode": "binary", - "max_visible_rgb_colors": 4096, + "width": 288, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, - "rows": 4, + "columns": 6, + "rows": 1, "cell_width": 48, "cell_height": 48, "allow_blank_frames": false, "edge_padding": 1 - } + }, + "max_alpha_levels": 256 }, { "id": "enemy.wisp", "category": "enemy", "status": "final", "path": "assets/custom/actors/spirits/wisp.png", - "width": 192, - "height": 192, - "alpha_mode": "binary", - "max_visible_rgb_colors": 4096, + "width": 288, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, "frame_grid": { - "columns": 4, - "rows": 4, + "columns": 6, + "rows": 1, "cell_width": 48, "cell_height": 48, "allow_blank_frames": false, "edge_padding": 1 - } + }, + "max_alpha_levels": 256 }, { "id": "fx.beacon_flame", @@ -1528,6 +1557,226 @@ "alpha_mode": "binary", "max_visible_rgb_colors": 16, "max_alpha_levels": 2 + }, + { + "id": "ui.relic_card_normal", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/relic_card_normal.png", + "width": 168, + "height": 112, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_card_hover", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/relic_card_hover.png", + "width": 168, + "height": 112, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_card_pressed", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/relic_card_pressed.png", + "width": 168, + "height": 112, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_card_disabled", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/relic_card_disabled.png", + "width": 168, + "height": 112, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_dew_hunter", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/dew_hunter.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_heavy_arrow", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/heavy_arrow.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_light_step", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/light_step.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_long_blade", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/long_blade.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_moon_dash", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/moon_dash.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_moon_ring", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/moon_ring.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_moon_ripple", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/moon_ripple.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_pierce_arrow", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/pierce_arrow.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_quick_arrow", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/quick_arrow.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_shadow_veil", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/shadow_veil.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_sharp_moon", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/sharp_moon.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_swift_hand", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/swift_hand.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_tough_life", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/tough_life.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_twin_arrow", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/twin_arrow.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_warm_beacon", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/warm_beacon.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 + }, + { + "id": "ui.relic_icon_wide_arc", + "category": "ui", + "status": "final", + "path": "assets/custom/ui/icons/wide_arc.png", + "width": 48, + "height": 48, + "alpha_mode": "graded", + "max_visible_rgb_colors": 65536, + "max_alpha_levels": 256 } ] } diff --git a/apps/game/tools/pack_grok_spirits.py b/apps/game/tools/pack_grok_spirits.py index b3ccc0d..3b5aef4 100644 --- a/apps/game/tools/pack_grok_spirits.py +++ b/apps/game/tools/pack_grok_spirits.py @@ -161,20 +161,19 @@ def pack_all(*, check: bool) -> None: f"spirit_grok source missing (allowed during transition): " f"{', '.join(missing)}" ) - for kind in targets: - sheet = build_sheet(kind) + for kind in HEIGHTS: path = SPIRIT_ROOT / f"{kind}.png" if check: + # Batch 2 replaced the Grok pack with ChatGPT grid drops, so + # byte equality is void. Like pack_ludo_guardians, only the + # file presence is gated; geometry is pinned by contracts. if not path.is_file(): raise RuntimeError(f"production spirit PNG is missing: {path}") - committed = Image.open(path).convert("RGBA") - if committed.size != sheet.size \ - or committed.tobytes() != sheet.convert("RGBA").tobytes(): - raise RuntimeError( - f"production spirit PNG differs from the Grok pack: {path}" - ) - else: - path.write_bytes(_png_bytes(sheet)) + continue + if kind not in targets: + continue + sheet = build_sheet(kind) + path.write_bytes(_png_bytes(sheet)) def main() -> int: diff --git a/apps/game/tools/slice_gpt_cells.py b/apps/game/tools/slice_gpt_cells.py new file mode 100644 index 0000000..2d340d3 --- /dev/null +++ b/apps/game/tools/slice_gpt_cells.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Slice a ChatGPT grid drop into aligned full-cell files. + +Unlike slice_gpt_grid (bbox crop per frame), every cell is box-scaled +whole to the target size, so multi-state UI art stays pixel-aligned +across states. Stdlib only, like the other art tools. + +Usage: + python3 slice_gpt_cells.py --rows 2 --cols 2 \\ + --size 168x112 --prefix builds/cells/card --names normal,hover,pressed,disabled +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from pixel_canvas import Canvas +from slice_gpt_grid import cell_bbox, fit_cell, read_png + + +def scale_cell( + buf: bytes, + width: int, + x0: int, + y0: int, + cw: int, + ch: int, + dw: int, + dh: int, +) -> Canvas: + canvas = Canvas(dw, dh) + for dy in range(dh): + sy0 = dy * ch // dh + sy1 = max(sy0 + 1, (dy + 1) * ch // dh) + for dx in range(dw): + sx0 = dx * cw // dw + sx1 = max(sx0 + 1, (dx + 1) * cw // dw) + r = g = b = a = n = 0 + for sy in range(sy0, sy1): + for sx in range(sx0, sx1): + o = ((y0 + sy) * width + (x0 + sx)) * 4 + pa = buf[o + 3] + r += buf[o] * pa + g += buf[o + 1] * pa + b += buf[o + 2] * pa + a += pa + n += 1 + if a > 0: + canvas.pixel(dx, dy, (r // a, g // a, b // a, a // n)) + return canvas + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("drop", type=Path) + parser.add_argument("--rows", type=int, required=True) + parser.add_argument("--cols", type=int, required=True) + parser.add_argument("--size", required=True, help="WxH, e.g. 168x112") + parser.add_argument("--prefix", type=Path, required=True) + parser.add_argument("--names", default="", + help="comma list, one per cell, row-major") + parser.add_argument("--fit", type=int, default=0, + help="bbox-crop and fit longest side to FILL px " + "(0 = scale whole cell)") + args = parser.parse_args() + + dw, dh = (int(v) for v in args.size.lower().split("x")) + names = [n for n in args.names.split(",") if n] or None + total = args.rows * args.cols + if names is not None and len(names) != total: + print(f"need {total} names, got {len(names)}") + return 1 + + width, height, buf = read_png(args.drop) + cw, ch = width // args.cols, height // args.rows + print(f"drop {width}x{height}, grid {args.rows}x{args.cols}, cell {cw}x{ch}") + args.prefix.parent.mkdir(parents=True, exist_ok=True) + failed = False + for index in range(total): + r, c = divmod(index, args.cols) + x0, y0 = c * cw, r * ch + if args.fit > 0: + if dw != dh: + print("fit mode needs a square --size") + return 1 + bbox = cell_bbox(buf, width, x0, y0, cw, ch) + if bbox is None: + print(f" cell {index}: EMPTY — reject this drop") + failed = True + continue + cell = fit_cell(buf, width, bbox, dw, args.fit) + else: + cell = scale_cell(buf, width, x0, y0, cw, ch, dw, dh) + tag = names[index] if names else f"{index}" + out = args.prefix.parent / f"{args.prefix.name}_{tag}.png" + cell.save(out) + print(f"wrote {out} ({dw}x{dh})") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/apps/game/tools/slice_gpt_grid.py b/apps/game/tools/slice_gpt_grid.py new file mode 100644 index 0000000..fd377f1 --- /dev/null +++ b/apps/game/tools/slice_gpt_grid.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +"""Slice a ChatGPT grid drop into a horizontal game strip. + +Drops come as R x C grids (idle 2x3, states 2x2). Each cell is cropped +to its alpha bbox, fitted into a square game cell, and concatenated +left-to-right, top-to-bottom. Stdlib only, like the other art tools. + +Usage: + python3 slice_gpt_grid.py --rows 2 --cols 3 --cell 64 \\ + --out builds/slice/forest.png +""" + +from __future__ import annotations + +import argparse +import struct +import sys +import zlib +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from pixel_canvas import Canvas + +ALPHA_CUTOFF = 8 +FRAME_FILL = 60 # bbox longest side in px inside the game cell + + +def read_png(path: Path) -> tuple[int, int, bytes]: + data = path.read_bytes() + assert data[:8] == b"\x89PNG\r\n\x1a\n", "not a PNG" + pos, raw, width, height, channels = 8, b"", 0, 0, 0 + while pos < len(data): + (length,) = struct.unpack(">I", data[pos : pos + 4]) + kind = data[pos + 4 : pos + 8] + chunk = data[pos + 8 : pos + 8 + length] + if kind == b"IHDR": + width, height, _bd, ctype, _, _, _ = struct.unpack(">IIBBBBB", chunk) + channels = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}[ctype] + elif kind == b"IDAT": + raw += chunk + pos += 12 + length + assert channels in (3, 4), f"unsupported channels: {channels}" + px = zlib.decompress(raw) + stride = width * channels + out = bytearray(width * height * channels) + prev = bytearray(stride) + for y in range(height): + line = px[y * (stride + 1) : (y + 1) * (stride + 1)] + ftype = line[0] + cur = bytearray(stride) + for i in range(stride): + a = cur[i - channels] if i >= channels else 0 + b = prev[i] + c = prev[i - channels] if i >= channels else 0 + if ftype == 0: + v = line[i + 1] + elif ftype == 1: + v = (line[i + 1] + a) & 255 + elif ftype == 2: + v = (line[i + 1] + b) & 255 + elif ftype == 3: + v = (line[i + 1] + (a + b) // 2) & 255 + else: + p = a + b - c + pa, pb, pc = abs(p - a), abs(p - b), abs(p - c) + pr = a if (pa <= pb and pa <= pc) else (b if pb <= pc else c) + v = (line[i + 1] + pr) & 255 + cur[i] = v + out[y * stride : (y + 1) * stride] = cur + prev = cur + if channels == 3: + rgba = bytearray(width * height * 4) + for i in range(width * height): + rgba[i * 4 : i * 4 + 3] = out[i * 3 : i * 3 + 3] + rgba[i * 4 + 3] = 255 + return width, height, bytes(rgba) + return width, height, bytes(out) + + +def alpha_of(buf: bytes, width: int, x: int, y: int) -> int: + return buf[(y * width + x) * 4 + 3] + + +def cell_bbox( + buf: bytes, width: int, x0: int, y0: int, cw: int, ch: int +) -> tuple[int, int, int, int] | None: + min_x, min_y, max_x, max_y = cw, ch, -1, -1 + for y in range(ch): + for x in range(cw): + if alpha_of(buf, width, x0 + x, y0 + y) > ALPHA_CUTOFF: + if x < min_x: + min_x = x + if x > max_x: + max_x = x + if y < min_y: + min_y = y + if y > max_y: + max_y = y + if max_x < 0: + return None + return (x0 + min_x, y0 + min_y, max_x - min_x + 1, max_y - min_y + 1) + + +def fit_cell( + buf: bytes, + width: int, + bbox: tuple[int, int, int, int], + cell: int, + fill: int = FRAME_FILL, +) -> Canvas: + bx, by, bw, bh = bbox + longest = max(bw, bh) + scale = fill / longest + dw, dh = max(1, round(bw * scale)), max(1, round(bh * scale)) + ox, oy = (cell - dw) // 2, (cell - dh) // 2 + canvas = Canvas(cell, cell) + for dy in range(dh): + sy0 = int(dy / scale) + sy1 = min(bh, int((dy + 1) / scale)) + for dx in range(dw): + sx0 = int(dx / scale) + sx1 = min(bw, int((dx + 1) / scale)) + r = g = b = a = n = 0 + for sy in range(sy0, max(sy1, sy0 + 1)): + for sx in range(sx0, max(sx1, sx0 + 1)): + o = ((by + sy) * width + (bx + sx)) * 4 + # Premultiply so transparent fringes do not halo. + pa = buf[o + 3] + r += buf[o] * pa + g += buf[o + 1] * pa + b += buf[o + 2] * pa + a += pa + n += 1 + if a > 0: + canvas.pixel( + ox + dx, oy + dy, + (r // a, g // a, b // a, a // n), + ) + return canvas + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("drop", type=Path) + parser.add_argument("--rows", type=int, required=True) + parser.add_argument("--cols", type=int, required=True) + parser.add_argument("--cell", type=int, default=64) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + + width, height, buf = read_png(args.drop) + cw, ch = width // args.cols, height // args.rows + print(f"drop {width}x{height}, grid {args.rows}x{args.cols}, cell {cw}x{ch}") + frames: list[Canvas] = [] + sizes: list[int] = [] + failed = False + for index in range(args.rows * args.cols): + r, c = divmod(index, args.cols) + x0, y0 = c * cw, r * ch + bbox = cell_bbox(buf, width, x0, y0, cw, ch) + if bbox is None: + print(f" frame {index}: EMPTY — reject this drop") + failed = True + continue + _, _, bw, bh = bbox + touches = ( + bbox[0] == x0 or bbox[1] == y0 + or bbox[0] + bw == x0 + cw or bbox[1] + bh == y0 + ch + ) + if touches: + print(f" frame {index}: WARN touches cell edge (bleed?)") + sizes.append(max(bw, bh)) + frames.append( + fit_cell(buf, width, bbox, args.cell, + args.cell * FRAME_FILL // 64)) + if failed: + return 1 + median = sorted(sizes)[len(sizes) // 2] + for i, s in enumerate(sizes): + if abs(s - median) > median * 0.3: + print(f" frame {i}: WARN size {s} vs median {median}") + strip = Canvas(args.cell * len(frames), args.cell) + for i, frame in enumerate(frames): + strip.blit(frame, i * args.cell, 0) + args.out.parent.mkdir(parents=True, exist_ok=True) + strip.save(args.out) + print(f"wrote {args.out} ({strip.width}x{strip.height})") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/notes/plans/art-25d-pass.md b/notes/plans/art-25d-pass.md new file mode 100644 index 0000000..02046b6 --- /dev/null +++ b/notes/plans/art-25d-pass.md @@ -0,0 +1,87 @@ +# Full 2.5D re-art pass (bible) + +Author-only design authority for the graphics upgrade. Decided 2026-09-26: +full re-art, not a lighting pass. Style decisions below are Recommended +calls; drops still come from the user's ChatGPT project. + +## Style: 2.5D pixel art, same camera, same layout + +- Camera stays top-down. "2.5D" is a height read, not a perspective change: + actors gain material volume (lit top faces, shaded lower faces, rim light + from the moon at top-left), and the engine keeps drawing the ellipse + foot shadows (spirit.tscn `Shadow` already exists — no painted shadows). +- Pixel grid stays. Nearest filter and the Compatibility renderer are + locked, so smooth prerenders would fight the fonts and UI. New pixels, + same grid: per-material 3-tone volume ramps on the night palette + (deep navy base, moonlit tops, amber bounce near beacons). +- **Layout locked.** Cell sizes, frame counts, row/column conventions, and + facing rules do not change — only the pixels inside. Code, animation + assembly, and hitboxes keep working untouched: + - Spirits: 96×96, 24px cells, 4 columns (down·up·left·right) × 4 frame rows. + - Guardians: 64px cells, idle 384×64 (6f), state sheets 256×64 (4f), + single horizontal row, no facing. + - Heroes: portrait + idle + walk per hero (same canvas each). +- Silhouette families stay readable: a redrawn stalker must still read as + the low four-legged charger at 24px. New detail must survive the 3× + phone scale, not the zoomed-in review. +- This supersedes the `gpt-assets.md` house spec ("no gradients, flat") + for re-art batches only. Batch 1-3 decor keeps the old spec until its + own redraw batch. + +## Inventory (105 sheets + optional) + +| Group | Sheets | Spec | Batch | +| --- | --- | --- | --- | +| Guardians ×6 | 22 | 64px, idle 6f + states 6f | G (first) | +| Heroes ×6 | 18 | portrait/idle/walk | H | +| Spirits ×7 | 7 | 288×48 strip, 6f facing-less | S | +| World (terrain/beacon/gate/atmosphere) | 15 | atlas + singles | W | +| Items (slash/arrow/bolt/gem) | 4 | singles | U | +| Custom UI (buttons/panels/hearts/busts/icons) | 39 | mixed, incl. 4 relic cards + 16 relic icons | U | +| Ninja Adventure third-party | 63 | keep (see below) | N? | + +- Ninja pack: Recommended keep. It is the course's Lesson 1-2 starting + point; redrawing it rewrites the course's beginning. Revisit only if + the new style clashes on screen. +- Fonts, music, SFX: not visual, out of scope. +- `derived/`: build-time generated, regenerates from sources. No redraw. +- Deterministic pixel generators (`tools/build_*_assets.py`) retire for + redrawn groups. Drops become final art after cleanup; a new slicing + validator replaces generation. + +## Batch order and why + +G → H → S → W → U. Bosses first (the ask), then the heroes the player +stares at, then fodder, then ground, then chrome. One batch fully +landed (intake + manifest + verify + device look) before the next +starts — mixed styles on one screen read as a bug. + +## Intake (per batch) + +1. Drop into `_asset_sources/gpt//`, filenames kept. +2. View every file: palette, silhouette family, frame count, cell grid, + transparent background, no JPG. +3. Slice/convert to the layout spec; validator checks canvas size, + frame count, and alpha (replaces the retired generators). +4. Wire only used files into `apps/game/assets/`, manifest rows with + batch + "custom, generated 2026-09". +5. `pnpm verify` + `pnpm check:assets` + device/emulator look. +6. Store recapture: the full set goes red the moment art lands. Capture + worry is waived for now, but the store sets must be reshot before + any release that ships new art. That needs its own explicit go. + +## Course impact (must not forget) + +- Lesson clips showing old sprites must be reshot (audit per lesson + once Batch G lands; guardians appear from the boss lesson on). +- Prose referencing the deterministic pixel pipeline + (`build_spirit_guardian_assets.py` and siblings) must be rewritten + for redrawn groups — the pipeline no longer produces those files. +- `notes/` plans that pin palette/production stay as history; this + file is the authority from here on. + +## Prompt packs + +Guardian pack lives in `notes/workflow/gpt-assets.md` (Batch G) with +the style addendum. H/S/W/U packs follow the same template once G +lands and the style is proven on screen. diff --git a/notes/workflow/gpt-assets.md b/notes/workflow/gpt-assets.md index c3ea831..5de33fa 100644 --- a/notes/workflow/gpt-assets.md +++ b/notes/workflow/gpt-assets.md @@ -67,6 +67,9 @@ then PR. Engine side is ready: spirits run facing-less like guardians (`facings=1`, one horizontal strip, `float_down` always). Do NOT ask for 4-facings grids — image gen cannot keep 16 cells consistent. +As built 2026-09-26: every kind is 6 frames as a 2x3 grid on a +square canvas (4-frame strips read choppy and come back portrait), +sliced to `288x48` with `slice_gpt_grid.py --cell 48`. Drop into `_asset_sources/gpt/batch-2/`, one PNG per kind. @@ -98,6 +101,9 @@ the flat 9-patch style only — text stays rendered (localization). No Godot-drawn boxes; all states are art. Drop into `_asset_sources/gpt/batch-3/`. +As built 2026-09-26: the 4 card states came as ONE 2x2 grid (keeps +states pixel-aligned) via `slice_gpt_cells.py` full-cell mode; the +16 icons came as ONE 4x4 grid via `--fit` bbox mode. Frames (ask at 336×224, downscaled to 168×112 on intake): @@ -129,3 +135,80 @@ small swift arrows · `shadow_veil` dark veil with eyes · Intake: swap card StyleBoxTexture set, add icon TextureRect per card, map relic id → icon in `relic_panel.gd`, render-verify the 3-pick panel in all states, emulator screenshot, PR. + +## Style addendum for re-art batches (G/H/S/W/U) + +Supersedes the house spec above for re-art only. Same top-down camera +and layout; new pixels with volume: lit top faces, shaded lower faces, +moon rim light from top-left, 3-tone ramps on the night palette +(navy/moonlight/amber). Keep the pixel grid and silhouette families. +No painted shadows (the engine draws foot shadows), no JPG. + +## Batch G — guardian re-art, all six bosses (prompt pack) + +Drop into `_asset_sources/gpt/batch-g/`. One generation per sheet +(22 total). Proven 2026-09-26: request grids, never strips — every +sheet is 6 frames as a 2x3 grid on a square canvas (4-frame states +look choppy; minimum 6 connected frames everywhere). Direct +strip requests come back portrait and cannot fill square +cells. Slice with `apps/game/tools/slice_gpt_grid.py`, which validates +frames (empty/bleed/size warnings). Always add: "high contrast", +"keep every creature smaller with wide transparent margins on all +sides including the outer canvas edges, nothing may touch any +edge", "no floating debris". + +Common suffix for every prompt: "top-down 2.5D pixel art game sprite +sheet, single horizontal row of animation frames on transparent +background, chunky visible pixels, lit top faces with shaded lower +faces, moon rim light from top-left, deep navy and amber night +palette, same creature and silhouette in every frame, no shadow, no +background, no text". + +Layout: every sheet is 384×64 (six 64×64 frames). Same strip size +for idle and states, so `guardian_state_frames = 6` everywhere. + +Forest Thornwood Pursuer (horns, long tree-arms, rooted feet): + +1. `forest`: idle breathing, arms swaying, 6 frames. +2. `forest_windup`: rearing back, arms raised, about to rush, 6 frames. +3. `forest_charge`: full rushing lunge forward, motion lean, 6 frames. +4. `forest_recover`: skidding to a stop, arms dropping, 6 frames. + +Thorn King Pursuer (older, thorn-crowned, deeper bark): + +5. `forest_thorn`: idle, heavier sway, thorns glinting, 6 frames. +6. `forest_thorn_windup`: rearing with thorns flared, 6 frames. +7. `forest_thorn_charge`: crushing lunge, thorns first, 6 frames. +8. `forest_thorn_recover`: grinding halt, thorns settling, 6 frames. + +Azure Fieldwing (wide crescent wings, mask, cloud tentacles): + +9. `field`: idle hover, wings beating slowly, 6 frames. +10. `field_windup_cross`: wings folding into a cross pose, 6 frames. +11. `field_windup_radial`: wings spreading into a full ring pose, 6 frames. +12. `field_recover`: wings drooping, sinking lower, 6 frames. + +Storm Fieldwing (charged feathers, lightning veins, torn wing edges): + +13. `field_storm`: idle hover in rising wind, sparks, 6 frames. +14. `field_storm_windup_cross`: cross pose crackling, 6 frames. +15. `field_storm_windup_radial`: ring pose crackling, 6 frames. +16. `field_storm_recover`: discharged droop, last sparks fading, 6 frames. + +Emberclad Warden (brazier helm, hammer, gate shield): + +17. `camp`: idle guard stance, brazier breathing, 6 frames. +18. `camp_windup`: hammer raised, shield braced, aiming, 6 frames. +19. `camp_recover`: armor open, hammer lowered, exposed core, 6 frames. + +Siegeclad Warden (heavier plates, siege hammer, cracked shield): + +20. `camp_siege`: idle siege stance, embers leaking, 6 frames. +21. `camp_siege_windup`: siege hammer raised high, 6 frames. +22. `camp_siege_recover`: plates parted, core glowing, 6 frames. + +Intake: validate canvas + frame counts, wire into +`assets/custom/actors/guardians/`, manifest rows with batch G, +`pnpm verify` + `check:assets`, render-verify idle + all mid-states +(no fallback to old art), emulator screenshot, PR. H/S/W/U packs +follow once G proves the style on screen.