Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ AI_IMAGE_MODEL=gemini-2.5-flash-image
AI_VIDEO_MODEL=kling-v2-5-turbo

# ── 积分定价 ──
QUOTA_REGISTER_GIFT_AMOUNT=100
QUOTA_REGISTER_GIFT_AMOUNT=300
QUOTA_INVITE_REWARD_AMOUNT=200
QUOTA_INVITE_REWARD_DAILY_LIMIT=3
QUOTA_INVITE_CODE_TTL_DAYS=30
QUOTA_GENERATE_IMAGE_COST=10
QUOTA_GENERATE_ACTION_COST=50

Expand Down
Binary file not shown.
Binary file not shown.
Binary file added .github/assets/readme/character-journey.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 69 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,106 +8,144 @@
面向国产小游戏开发者的 2D 角色动态素材生成与资产工作台
</p>

<p align="center"><strong>交付的是资产,而不是图片。</strong></p>
<p align="center"><strong>让你的角色,真正登场。</strong></p>

Windup 面向缺少美术产能的个人开发者和小型团队,把角色构思、动作生成、逐帧质检、试玩与引擎导出收进同一条生产链。用户从文字描述或参考图出发,最终得到可以持续补充动作、修正缺陷和重新导出的角色资产。
<p align="center">
<a href="https://windup.xin"><strong>在线使用</strong></a>
·
<a href="https://github.com/1024XEngineer/Windup/issues">问题与建议</a>
·
<a href="openapi.json">OpenAPI</a>
</p>

<p align="center">
<a href="https://windup.xin"><img src="https://img.shields.io/website?url=https%3A%2F%2Fwindup.xin&amp;up_message=online&amp;down_message=offline&amp;label=windup.xin" alt="windup.xin status"></a>
<a href="https://github.com/1024XEngineer/Windup/actions/workflows/frontend-ci.yml"><img src="https://github.com/1024XEngineer/Windup/actions/workflows/frontend-ci.yml/badge.svg?branch=main" alt="Frontend CI"></a>
<a href="https://github.com/1024XEngineer/Windup/actions/workflows/backend.yml"><img src="https://github.com/1024XEngineer/Windup/actions/workflows/backend.yml/badge.svg?branch=main" alt="Backend CI"></a>
<a href="https://codecov.io/gh/1024XEngineer/Windup"><img src="https://codecov.io/gh/1024XEngineer/Windup/graph/badge.svg?branch=main" alt="Test coverage"></a>
</p>

<p align="center"><strong>Windup 已上线,现已开放注册。</strong></p>

<p align="center">
<img src=".github/assets/readme/character-journey.webp" width="100%" alt="Windup 角色从线稿、母版到游戏资产的生成旅程">
</p>

Windup 面向缺少美术产能的个人开发者和小型团队,把角色构思、动作生成、逐帧审核、试玩与引擎导出收进同一条生产链。用户从文字描述或参考图出发,最终得到可以持续补充动作、修正缺陷和重新导出的角色资产。

## 当前能力 / What You Can Do

| 能力 | 当前可用内容 |
| --- | --- |
| 项目与资产库 | 管理项目约束、角色、造型、动作与帧,继续扩展已有角色资产 |
| Quick Start | 用自然语言描述角色和动作,由系统建立标准制作流程 |
| Workflow Editor | 在真实节点画布中确认角色母版、动作首帧、生成方式、完整动画与审核状态 |
| 角色与动作生成 | 接入真实生成任务,保存任务状态与产物,支持失败恢复与结果追溯 |
| 审核与局部返工 | 对候选图和动作结果进行确认,在具体节点重试而不必重做整条流程 |
| Playtest 与导出 | 在浏览器中预览动作,并导出透明 PNG、Sprite Sheet、动画 JSON 与 ZIP 资源包 |

三渲二、多方向资产和更多引擎适配仍在推进。相关基础能力进入仓库不等于已进入在线产品主流程;当前进度以 [`main`](https://github.com/1024XEngineer/Windup/tree/main) 与 [Issues](https://github.com/1024XEngineer/Windup/issues) 为准。

## 产品链路 / Product Workflow

```text
新角色:文字描述 / 参考图 → 项目约束 → 角色母版
已有角色:从资产库继续生产 ─────────────┘
动作序列帧 → 逐帧审核 / 局部重生成
动作序列帧 → 审核 / 局部重生成
Playtest 试玩 → PNG / Sprite Sheet / 元数据 → 游戏引擎
```

Windup 用角色母版约束跨帧、跨动作的视觉一致性,再用确定性的工程后处理完成去背景、切帧、对齐和打包。出现缺陷时,返工可以缩小到具体帧或节点,已通过的结果继续保留
Windup 用角色母版约束跨帧、跨动作的视觉一致性,再用确定性的工程后处理完成去背景、切帧、对齐和打包。出现缺陷时,返工可以缩小到具体节点,已经确认的结果继续保留

## 核心对象 / Core Concepts

| 对象 | 职责 |
| --- | --- |
| `Project` | 统一管理题材、美术风格、视角与精灵尺寸等项目级约束 |
| `Character` | 角色资产本体;造型、动作实例与帧属于它的资产树 |
| `ActionTemplate` | 可在不同角色间复用的动作规格与生产配方 |
| `Generation` | 一次生成任务及其输入、状态和结果,用于恢复与追溯 |
| `WorkflowRun` | 一次前端制作流程的运行记录,连接生成、确认、回退与导出 |

产品提供两种入口:`Quick Start` 用自然语言建立标准生产流程;`Workflow Editor` 在系统预置的成熟管线上追加动作分支、微调参数和局部返工。两者共用同一套流程状态和质量门禁,分别服务快速创建与精细控制。

## 当前阶段 / Project Status
| `WorkflowRun` | 一次制作流程的持久化运行记录,连接生成、确认、回退与导出 |

MS2 已完成 Windup 的产品 MVP,验证了角色资产生产的核心链路。MS3 的重点从“完成一次生成”转向“持续完善已有角色资产”:用户可以从资产库回到已有角色,为它补充动作、重做有问题的分支,并保留未受影响的资产。

| 状态 | 内容 |
| --- | --- |
| MS2 产出 | 完成产品 MVP,跑通并验证角色资产生产的核心体验 |
| MS3 产品主线 | 已有角色补动作;工作流采用固定成熟管线,通过卡片加号追加分支,支持参数微调与局部重跑 |
| MS3 工程重点 | 持久化 `WorkflowRun` 并关联角色,串起工作流编辑、产物审核、节点回退与 Playtest |
| 后续探索 | Quick Start Agent、3D 动作生成路线、多视角资产与项目级导出 |

项目进度见 [`main`](https://github.com/1024XEngineer/Windup/tree/main) 与 [Issues](https://github.com/1024XEngineer/Windup/issues)。
`Quick Start` 与 `Workflow Editor` 是同一套流程状态的两种入口:前者用于快速建立标准流程,后者用于查看节点依赖、调整生成方式和处理局部返工。

## 技术栈 / Tech Stack

- 前端:React 19、TypeScript 6、Vite 8、Tailwind CSS 4、Vitest
- 后端:Python 3.12、FastAPI、Pydantic、SQLAlchemy、uv workspace
- 基础设施:PostgreSQL、Redis、Docker Compose、Nginx
- 工程约束:GitHub Actions、Ruff、Pytest、Import Linter、oxlint、oxfmt

## 本地开发 / Local Development

前端支持 Node.js `^20.19.0`、`^22.12.0` 或 `>=24.0.0`;CI 使用 Node.js 24:
需要 Node.js 24、Python 3.12、[uv](https://docs.astral.sh/uv/)、PostgreSQL 与 Redis。

先准备本地配置和依赖服务:

```bash
cd frontend
npm ci
npm run dev
cp .env.example .env
# 在 .env 中配置 POSTGRES_PASSWORD、JWT_SECRET(至少 32 字符)及所需服务凭据
docker compose up -d postgres redis
```

后端使用 Python 3.12 和 [uv](https://docs.astral.sh/uv/)
启动后端

```bash
cd backend
uv sync --frozen
uv run uvicorn windup_app.bootstrap.app:create_app --factory --reload
```

另开一个终端启动前端:

```bash
cd frontend
npm ci
npm run dev
```

前端开发服务器默认访问 `http://localhost:5173`,后端健康检查为 `http://localhost:8000/health`。前端需要指向其他后端时,通过构建期变量 `VITE_API_BASE_URL` 配置。

## 质量检查 / Quality Checks

以下命令与 GitHub Actions 的主要检查保持一致:

```bash
# frontend/
npm run format:check
npm run lint
npm run typecheck
npm run test
npm run test:coverage
npm run build

# backend/
uv run ruff check .
uv run python -m scripts.export_openapi
uv run lint-imports
uv run pytest -q
uv run pytest -q --cov=packages
```

## 仓库结构 / Repository Structure

```text
Windup/
├── frontend/ # React 前端、页面与制作流程
├── backend/ # Python 工作区、领域服务与 API
└── README.md
├── frontend/ # React 前端、产品页面与制作流程
├── backend/ # FastAPI 应用、领域服务、生成引擎与基础设施
├── openapi.json # 从后端自动生成的接口契约
└── docker-compose.yml # PostgreSQL、Redis、后端与前端构建任务
```

## 相关文档 / Documentation

- [在线产品](https://windup.xin)
- [Windup 产品策划案](https://github.com/1024XEngineer/Windup/issues/37)
- [核心流程与工作流](https://github.com/1024XEngineer/Windup/issues/25)
- [OpenAPI 接口契约](openapi.json)

## 参与贡献 / Contributing

问题、需求和实验记录统一进入 [Issues](https://github.com/1024XEngineer/Windup/issues)。功能和核心改动按 `Proposal → Issue → Branch → Pull Request → Review` 推进,开发前请先查看对应 Issue 与领域契约。
Bug、需求和实验建议统一进入 [Issues](https://github.com/1024XEngineer/Windup/issues)。功能和核心改动按 `Proposal → Issue → Branch → Pull Request → Review` 推进,开发前请先查看对应 Issue 与领域契约。

项目的维护与历史贡献见 [Contributors](https://github.com/1024XEngineer/Windup/graphs/contributors)。

Expand Down
20 changes: 16 additions & 4 deletions backend/packages/ai_engine/src/windup_ai_engine/master_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
**核心规律(三次实测验证,写死为契约):母版姿态决定动作,提示词只能微调。**
- walk:母版**朝侧向**才不转身;正面母版配侧走词 → 模型靠转身调和图文矛盾。
- jump:母版**顶部留白**才不被视频画面裁掉。
- attack:必须给**极限蓄力母版**(出手那只手已拉到身后腰际)。用站立母版时,即使提示词
- attack:必须给**极限蓄力母版**(发力那一侧已拉到待发位)。用站立母版时,即使提示词
写死"不过头顶 / 不转身 / 只做一次",模型仍会抡过头顶、转到背面、劈两次 —— 强动作
先验压不住;换蓄力母版后模型只能"接着往前挥",没有再抡起的空间。
先验压不住;换蓄力母版后模型只能"接着往前发力",没有再抡起的空间。
蓄力姿态按运动拓扑分四支(见 :data:`ATTACK_MASTER_POSES`):同一张横挥蓄力母版
喂给直刺 / 远程 / 前扑,模型会先把收好的那一侧重新抡起来再做。

**姿势描述里不写装备名词(#195)。** 这几段是拿去生成母版的提示词,写"the weapon"等于
断言角色持械 —— 空手角色会被凭空塞一把武器,而母版是整条 i2v 链的身份来源,污染会一路
Expand All @@ -27,16 +29,26 @@

from PIL import Image

from windup_common.models import AttackArchetype

from windup_ai_engine._subject import bg_color as _bg_color
from windup_ai_engine.prompt._md import load_section

__all__ = ["add_headroom", "prepare_master", "MASTER_POSES"]
__all__ = ["add_headroom", "prepare_master", "MASTER_POSES", "ATTACK_MASTER_POSES"]

# 空值 = 该动作用中性站立母版即可。这是唯一允许空提示词的地方,故显式放行 ——
# 别处的空串会一路跑到付费调用。
MASTER_POSES = {
a: load_section("master_poses.md", a, allow_empty=True)
for a in ("walk", "run", "idle", "jump", "attack")
for a in ("walk", "run", "idle", "jump")
}

# attack 按运动拓扑取母版姿态:四支的起手姿态互不兼容(横挥蓄力母版跑不出直刺),
# 而"母版姿态决定动作"对 attack 最狠 —— 见本模块开头。这里不放行空值:
# 四支都必须有自己的蓄力姿态,缺一支就该炸,不能退回中性站立。
ATTACK_MASTER_POSES = {
arch: load_section("master_poses.md", f"attack.{arch.value}")
for arch in AttackArchetype
}


Expand Down
21 changes: 21 additions & 0 deletions backend/packages/ai_engine/src/windup_ai_engine/prompt/_framing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""所有动作共用的构图约束。

由代码统一追加而不是抄进每份 md:同一条约束抄 N 份会各自漂移。

只写正向计数句 —— 该 i2v 接口没有 negative_prompt,否定句里的名词会被 latch 进画面
(实测"do not add dust"反而勾出更多灰尘),所以说"恰好一个",不说"不要第二个"。
"""
from __future__ import annotations

__all__ = ["SINGLE_SUBJECT_FRAMING", "with_framing"]

# 攻击的两处留白(母版姿态要求 + 母版补边)让画面空得足以容下第二个主体。
SINGLE_SUBJECT_FRAMING = (
"Exactly one character is in the frame, alone against a plain flat solid-color background, "
"and the whole body stays inside the frame."
)


def with_framing(body: str) -> str:
"""给一段动作正文接上构图约束。"""
return f"{body} {SINGLE_SUBJECT_FRAMING}"
18 changes: 13 additions & 5 deletions backend/packages/ai_engine/src/windup_ai_engine/prompt/actions.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""待机 / 攻击 i2v 提示词。

提示词正文在 ``prompts/idle.md`` 与 ``prompts/attack.md``(#233)。
本模块只留加载与按 facing 分流。
本模块只留加载与按 facing / archetype 分流。
"""
from __future__ import annotations

from windup_common.models import Facing
from windup_common.models import AttackArchetype, Facing

from windup_ai_engine.prompt._framing import with_framing
from windup_ai_engine.prompt._md import load_section

__all__ = ["build_idle_prompt", "build_attack_prompt"]
Expand All @@ -16,11 +17,18 @@ def build_idle_prompt(facing: Facing | str = Facing.SIDE) -> str:
"""待机正文(循环类)。``facing`` 须与母版朝向一致。

"""
return load_section("idle.md", Facing(facing).value)
return with_framing(load_section("idle.md", Facing(facing).value))


def build_attack_prompt(facing: Facing | str = Facing.SIDE) -> str:
def build_attack_prompt(
facing: Facing | str = Facing.SIDE,
*,
archetype: AttackArchetype | str = AttackArchetype.THRUST,
) -> str:
"""攻击正文(一次性类)。``facing`` 须与母版朝向一致。

默认取 THRUST:四支里只有 SWEEP 要求手里有一件有宽面的长条物,拿它当默认 = 对每个未知角色断言持械(#195)。
"""
return load_section("attack.md", Facing(facing).value)
# 两个枚举都过一遍构造:非法值要炸,不能静默落到某一节。
section = f"{AttackArchetype(archetype).value}.{Facing(facing).value}"
return with_framing(load_section("attack.md", section))
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from windup_common.models import Facing

from windup_ai_engine.prompt._framing import with_framing

__all__ = ["build_custom_prompt", "MAX_ACTION_CHARS"]

# 不是接口限制,是产品判断:描述越长越容易夹带角色外观,而外观由母版承载,写两遍会打架。
Expand Down Expand Up @@ -68,4 +70,4 @@ def build_custom_prompt(
lock = _FACING_LOCK[Facing(facing)] # 非法朝向要炸,不静默落到某一支
tail = _CYCLIC_TAIL if cyclic else _ONESHOT_TAIL
# 朝向放最前:最强的约束先钉。
return f"The character {lock}: {text}, {_KEEP_WHAT_IT_HAS}. {tail}"
return with_framing(f"The character {lock}: {text}, {_KEEP_WHAT_IT_HAS}. {tail}")
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from windup_common.models import Facing

from windup_ai_engine.prompt._framing import with_framing
from windup_ai_engine.prompt._md import load_section

__all__ = ["JUMP_PHASES", "build_jump_prompt"]
Expand All @@ -23,4 +24,4 @@ def build_jump_prompt(facing: Facing | str = Facing.SIDE) -> str:
facing: :class:`Facing` 成员(或其等价字符串),**必须与母版朝向一致**。

"""
return load_section(_DOC, Facing(facing).value)
return with_framing(load_section(_DOC, Facing(facing).value))
Loading
Loading