feat(m76-d): four listenable mixdowns for the owner audio gate
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
| 2026-09-13 | M60 可玩性真相(W1) | `e1cad068`/`dac5de40` | `m60-playthrough` `20260913T145904Z-38281` | runtime_verified | 34 步 0 阻断;视频与逐步截图 |
|
||||
| 2026-09-13 | M61 设计/密度(W2) | `310d2620` | `m61-five-profile` `20260913T162625Z-2172` | simulation_verified | decisions=330, max_gap=31.3s, 反支配/存档/可达通过 |
|
||||
| 2026-09-13 | M62 组件统一(W3) | `2e5540ed` | `m62-ink-baselines` `20260913T164335Z-38711` | runtime_verified | 组件库+矩阵+探针接入;基线 Guild 0 / Contract 1 / Readiness+Result 6 / Market 严格 0 |
|
||||
| 2026-09-14 | M76-D 整机试听带(4 段合成试听) | `feat(m76-d)` | runtime_pending(Owner 听感)| guild/market/risk/result 四段;音乐床 + 真实音效时序 |
|
||||
| 2026-09-14 | M76-C 音频接线(服务/总线/事件/音乐族)+ 卡内模块点击缺陷 | `feat(m76-c)`/`fix(m76-c)` `b0db3b46` | `m76-audio` `20260914T131826Z-19299` | runtime_verified(待整机听感)| 144 事件、16 总线;全流程 34 步 0 阻断且音频门通过;修复市场卡无法点击 |
|
||||
| 2026-09-14 | M76-B SFX 全切片(144 事件 / 158 条)+ 音乐 7 床 | `feat(m76-b)` | machine_verified(待接线与听感)| 校验 0 错误;8 条静音失败件重生成通过;花费累计 ≈ $1.7 |
|
||||
| 2026-09-14 | M76-A 音频试听批(ElevenLabs;查明旧音频为本地合成占位) | `feat(m76-a)` `4efe4d2c`/`7f7ec58c` | runtime_pending(待人工听感)| 音乐 7 床 + SFX 23 候选;机器校验 0 错误;花费 ≈ $1.35(音乐 $0.15/min、音效 $0.12/min)|
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"guild_page": {
|
||||
"title": "公会页(音乐 + 检视/焦点/整队/整队完成)",
|
||||
"music": "MUS-G01-GUILD-base.mp3",
|
||||
"length": 26,
|
||||
"cues": [
|
||||
[
|
||||
1.5,
|
||||
"SFX-UI-005-page_open.v01.mp3"
|
||||
],
|
||||
[
|
||||
3.4,
|
||||
"SFX-UI-001-focus_move.v01.mp3"
|
||||
],
|
||||
[
|
||||
5.0,
|
||||
"SFX-UI-007-inspect_open.v01.mp3"
|
||||
],
|
||||
[
|
||||
6.6,
|
||||
"SFX-UI-008-inspect_close.v01.mp3"
|
||||
],
|
||||
[
|
||||
8.4,
|
||||
"SFX-RUN-015-save_begin.v01.mp3"
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
"SFX-RUN-016-save_commit.v01.mp3"
|
||||
],
|
||||
[
|
||||
12.5,
|
||||
"SFX-UI-001-focus_move.v01.mp3"
|
||||
],
|
||||
[
|
||||
14.0,
|
||||
"SFX-ORD-006-reorder_commit.v01.mp3"
|
||||
],
|
||||
[
|
||||
16.5,
|
||||
"SFX-ORD-017-ready_check_pass.v01.mp3"
|
||||
],
|
||||
[
|
||||
19.0,
|
||||
"SFX-UI-018-confirm_commit.v01.mp3"
|
||||
]
|
||||
]
|
||||
},
|
||||
"market_transaction": {
|
||||
"title": "市场成交(音乐 + 选卡/检视/成交/扣币)",
|
||||
"music": "MUS-G02-SHOP-base.mp3",
|
||||
"length": 24,
|
||||
"cues": [
|
||||
[
|
||||
1.2,
|
||||
"SFX-UI-005-page_open.v01.mp3"
|
||||
],
|
||||
[
|
||||
2.6,
|
||||
"SFX-SHP-002-offer_reveal.v01.mp3"
|
||||
],
|
||||
[
|
||||
4.6,
|
||||
"SFX-SHP-012-recruit_card_reveal.v01.mp3"
|
||||
],
|
||||
[
|
||||
6.6,
|
||||
"SFX-SHP-013-recruit_inspect.v01.mp3"
|
||||
],
|
||||
[
|
||||
8.8,
|
||||
"SFX-SHP-014-recruit_hire_review.v01.mp3"
|
||||
],
|
||||
[
|
||||
11.0,
|
||||
"SFX-SHP-015-recruit_hire_commit.v01.mp3"
|
||||
],
|
||||
[
|
||||
13.4,
|
||||
"SFX-SHP-011-currency_spend.v01.mp3"
|
||||
],
|
||||
[
|
||||
16.0,
|
||||
"SFX-SHP-021-trait_benefit_reveal.v01.mp3"
|
||||
],
|
||||
[
|
||||
18.4,
|
||||
"SFX-EQP-007-equip_commit.v01.mp3"
|
||||
],
|
||||
[
|
||||
20.6,
|
||||
"SFX-SHP-024-shop_exit.v01.mp3"
|
||||
]
|
||||
]
|
||||
},
|
||||
"risk_and_departure": {
|
||||
"title": "风险与开战(音乐 + 选风险/落章/确认/出发)",
|
||||
"music": "MUS-G04-RUN-base.mp3",
|
||||
"length": 24,
|
||||
"cues": [
|
||||
[
|
||||
1.2,
|
||||
"SFX-UI-005-page_open.v01.mp3"
|
||||
],
|
||||
[
|
||||
3.0,
|
||||
"SFX-RUN-001-risk_option_focus.v01.mp3"
|
||||
],
|
||||
[
|
||||
5.0,
|
||||
"SFX-RUN-002-risk_compare.v01.mp3"
|
||||
],
|
||||
[
|
||||
7.2,
|
||||
"SFX-RUN-003-risk_commit.v01.mp3"
|
||||
],
|
||||
[
|
||||
9.8,
|
||||
"SFX-UI-017-confirm_arm.v01.mp3"
|
||||
],
|
||||
[
|
||||
12.0,
|
||||
"SFX-UI-018-confirm_commit.v01.mp3"
|
||||
],
|
||||
[
|
||||
14.6,
|
||||
"SFX-ORD-019-battle_start_arm.v01.mp3"
|
||||
],
|
||||
[
|
||||
17.0,
|
||||
"SFX-ORD-020-battle_start_commit.v01.mp3"
|
||||
],
|
||||
[
|
||||
19.4,
|
||||
"SFX-RUN-004-encounter_enter.v01.mp3"
|
||||
]
|
||||
]
|
||||
},
|
||||
"result_win": {
|
||||
"title": "结算·胜(音乐 + 胜利/奖励/升级)",
|
||||
"music": "MUS-G05-RESULT-WIN-base.mp3",
|
||||
"length": 24,
|
||||
"cues": [
|
||||
[
|
||||
1.2,
|
||||
"SFX-UI-005-page_open.v01.mp3"
|
||||
],
|
||||
[
|
||||
2.8,
|
||||
"SFX-RES-012-battle_victory.v01.mp3"
|
||||
],
|
||||
[
|
||||
5.4,
|
||||
"SFX-RES-016-reward_reveal.v01.mp3"
|
||||
],
|
||||
[
|
||||
7.8,
|
||||
"SFX-RES-017-reward_option_focus.v01.mp3"
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
"SFX-RES-019-reward_accept.v01.mp3"
|
||||
],
|
||||
[
|
||||
12.4,
|
||||
"SFX-RES-022-currency_reward.v01.mp3"
|
||||
],
|
||||
[
|
||||
14.8,
|
||||
"SFX-RES-025-experience_gain.v01.mp3"
|
||||
],
|
||||
[
|
||||
17.2,
|
||||
"SFX-RES-026-level_gain.v01.mp3"
|
||||
],
|
||||
[
|
||||
20.0,
|
||||
"SFX-UI-018-confirm_commit.v01.mp3"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -58,6 +58,16 @@
|
||||
- **接线暴露并修复真缺陷**:市场招募卡的 `IdentityModule` 等模块根节点默认 `MOUSE_FILTER_STOP`,**吃掉卡片点击 → 新开局无法选卡/招募**(全流程驱动此前在 hire 步骤阻断)。已修并写入共享 UI 规则。
|
||||
- **BAT 60 条与区域/BOSS 音乐**仍随战斗里程碑(清单里显式列为 deferred)。
|
||||
|
||||
## M76-D 门(2026-09-14,待 Owner 听感)
|
||||
|
||||
- 机器检查:`validate_audio.py`(158 SFX + 7 音乐)0 错误;严格运行时诊断 0 泄漏(修复了
|
||||
音乐交叉淡化 Tween 与 Dummy 驱动下的流播放泄漏;playthrough 关掉遗留音调适配器)。
|
||||
- 运行时证据:`m76-audio` attempt(13 markers 各一次)——音频服务单测、DEV-2 入口音频断言、
|
||||
34 步全流程 0 阻断 + 音频门(families=10 / page_open=35 / events=55 / switches=10)、八组墨迹探针。
|
||||
- 整机试听带:`docs/audio/mixdowns/{guild_page,market_transaction,risk_and_departure,result_win}.mp3`
|
||||
(音乐床 + 该页真实派发的音效时序;合成试听,不等价于设备播放证据)。
|
||||
- **人工听感 = Owner**:音箱与耳机分别听;结论记录后本里程碑收口。
|
||||
|
||||
## Acceptance checks
|
||||
|
||||
- 试听批:Owner 听过并认可方向(或给出反例修订)
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compose listenable mixdowns of the runtime audio (music bed + page cue timing).
|
||||
|
||||
The mixdown approximates what a page sounds like in the running game: the music
|
||||
family bed under the semantic cues that page actually dispatches (see
|
||||
`runtime/entry/entry_shell.gd` wiring). It is a listening aid for the Owner gate,
|
||||
not runtime evidence of device playback.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
AUDIO = ROOT / "runtime/assets/audio"
|
||||
SFX = AUDIO / "sfx"
|
||||
MUSIC = AUDIO / "music"
|
||||
OUT = ROOT / "docs/audio/mixdowns"
|
||||
|
||||
MUSIC_GAIN_DB = -13.0
|
||||
SFX_GAIN_DB = -1.0
|
||||
|
||||
MIXDOWNS: dict[str, dict] = {
|
||||
"guild_page": {
|
||||
"title": "公会页(音乐 + 检视/焦点/整队/整队完成)",
|
||||
"music": "MUS-G01-GUILD-base.mp3",
|
||||
"length": 26,
|
||||
"cues": [
|
||||
(1.5, "SFX-UI-005-page_open.v01.mp3"),
|
||||
(3.4, "SFX-UI-001-focus_move.v01.mp3"),
|
||||
(5.0, "SFX-UI-007-inspect_open.v01.mp3"),
|
||||
(6.6, "SFX-UI-008-inspect_close.v01.mp3"),
|
||||
(8.4, "SFX-RUN-015-save_begin.v01.mp3"),
|
||||
(10.0, "SFX-RUN-016-save_commit.v01.mp3"),
|
||||
(12.5, "SFX-UI-001-focus_move.v01.mp3"),
|
||||
(14.0, "SFX-ORD-006-reorder_commit.v01.mp3"),
|
||||
(16.5, "SFX-ORD-017-ready_check_pass.v01.mp3"),
|
||||
(19.0, "SFX-UI-018-confirm_commit.v01.mp3"),
|
||||
],
|
||||
},
|
||||
"market_transaction": {
|
||||
"title": "市场成交(音乐 + 选卡/检视/成交/扣币)",
|
||||
"music": "MUS-G02-SHOP-base.mp3",
|
||||
"length": 24,
|
||||
"cues": [
|
||||
(1.2, "SFX-UI-005-page_open.v01.mp3"),
|
||||
(2.6, "SFX-SHP-002-offer_reveal.v01.mp3"),
|
||||
(4.6, "SFX-SHP-012-recruit_card_reveal.v01.mp3"),
|
||||
(6.6, "SFX-SHP-013-recruit_inspect.v01.mp3"),
|
||||
(8.8, "SFX-SHP-014-recruit_hire_review.v01.mp3"),
|
||||
(11.0, "SFX-SHP-015-recruit_hire_commit.v01.mp3"),
|
||||
(13.4, "SFX-SHP-011-currency_spend.v01.mp3"),
|
||||
(16.0, "SFX-SHP-021-trait_benefit_reveal.v01.mp3"),
|
||||
(18.4, "SFX-EQP-007-equip_commit.v01.mp3"),
|
||||
(20.6, "SFX-SHP-024-shop_exit.v01.mp3"),
|
||||
],
|
||||
},
|
||||
"risk_and_departure": {
|
||||
"title": "风险与开战(音乐 + 选风险/落章/确认/出发)",
|
||||
"music": "MUS-G04-RUN-base.mp3",
|
||||
"length": 24,
|
||||
"cues": [
|
||||
(1.2, "SFX-UI-005-page_open.v01.mp3"),
|
||||
(3.0, "SFX-RUN-001-risk_option_focus.v01.mp3"),
|
||||
(5.0, "SFX-RUN-002-risk_compare.v01.mp3"),
|
||||
(7.2, "SFX-RUN-003-risk_commit.v01.mp3"),
|
||||
(9.8, "SFX-UI-017-confirm_arm.v01.mp3"),
|
||||
(12.0, "SFX-UI-018-confirm_commit.v01.mp3"),
|
||||
(14.6, "SFX-ORD-019-battle_start_arm.v01.mp3"),
|
||||
(17.0, "SFX-ORD-020-battle_start_commit.v01.mp3"),
|
||||
(19.4, "SFX-RUN-004-encounter_enter.v01.mp3"),
|
||||
],
|
||||
},
|
||||
"result_win": {
|
||||
"title": "结算·胜(音乐 + 胜利/奖励/升级)",
|
||||
"music": "MUS-G05-RESULT-WIN-base.mp3",
|
||||
"length": 24,
|
||||
"cues": [
|
||||
(1.2, "SFX-UI-005-page_open.v01.mp3"),
|
||||
(2.8, "SFX-RES-012-battle_victory.v01.mp3"),
|
||||
(5.4, "SFX-RES-016-reward_reveal.v01.mp3"),
|
||||
(7.8, "SFX-RES-017-reward_option_focus.v01.mp3"),
|
||||
(10.0, "SFX-RES-019-reward_accept.v01.mp3"),
|
||||
(12.4, "SFX-RES-022-currency_reward.v01.mp3"),
|
||||
(14.8, "SFX-RES-025-experience_gain.v01.mp3"),
|
||||
(17.2, "SFX-RES-026-level_gain.v01.mp3"),
|
||||
(20.0, "SFX-UI-018-confirm_commit.v01.mp3"),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def find_sfx(prefix: str) -> Path | None:
|
||||
matches = sorted(SFX.glob(prefix))
|
||||
return matches[0] if matches else None
|
||||
|
||||
|
||||
def build(slug: str, spec: dict) -> bool:
|
||||
music = MUSIC / str(spec["music"])
|
||||
if not music.exists():
|
||||
print(f"SKIP {slug}: music missing {music.name}")
|
||||
return False
|
||||
inputs: list[str] = ["-i", str(music)]
|
||||
filters: list[str] = [
|
||||
f"[0:a]volume={MUSIC_GAIN_DB}dB,atrim=0:{spec['length']},afade=t=out:st={max(0, spec['length'] - 2)}:d=2[music]"
|
||||
]
|
||||
labels = ["[music]"]
|
||||
index = 1
|
||||
for at, filename in spec["cues"]:
|
||||
path = find_sfx(filename)
|
||||
if path is None:
|
||||
print(f" cue missing: {filename}")
|
||||
continue
|
||||
inputs += ["-i", str(path)]
|
||||
delay = int(float(at) * 1000)
|
||||
filters.append(f"[{index}:a]volume={SFX_GAIN_DB}dB,adelay={delay}:all=1[cue{index}]")
|
||||
labels.append(f"[cue{index}]")
|
||||
index += 1
|
||||
filters.append(
|
||||
"".join(labels) + f"amix=inputs={len(labels)}:normalize=0:dropout_transition=0,alimiter=limit=0.95[out]"
|
||||
)
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
target = OUT / f"{slug}.mp3"
|
||||
subprocess.run(
|
||||
["ffmpeg", "-y", "-v", "error", *inputs, "-filter_complex", ";".join(filters),
|
||||
"-map", "[out]", "-c:a", "libmp3lame", "-q:a", "2", str(target)],
|
||||
check=True,
|
||||
)
|
||||
print(f"OK {slug} -> {target.relative_to(ROOT)} ({target.stat().st_size} bytes)")
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
built = 0
|
||||
for slug, spec in MIXDOWNS.items():
|
||||
if build(slug, spec):
|
||||
built += 1
|
||||
print(f"mixdowns built: {built}/{len(MIXDOWNS)}")
|
||||
(OUT / "mixdown_manifest.json").write_text(json.dumps(MIXDOWNS, ensure_ascii=False, indent=1))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user