diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9c7a6d0..c057859 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,8 +16,20 @@ jobs:
with:
python-version: '3.11'
cache: pip
- - run: python -m pip install -e '.[test]' build
- - run: python -m pytest -q tests
+ - run: python -m pip install -e '.[test]' build
+ - name: Download recommendation CLI
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ mkdir -p "$RUNNER_TEMP/deck/bin"
+ gh release download v0.0.3 --repo empty-sekai/ournotes-deck \
+ --pattern ournotes-deck-v0.0.3-x86_64-unknown-linux-musl.tar.gz \
+ --pattern SHA256SUMS --dir "$RUNNER_TEMP/deck"
+ cd "$RUNNER_TEMP/deck"
+ sha256sum --check --ignore-missing SHA256SUMS
+ tar -xzf ournotes-deck-v0.0.3-x86_64-unknown-linux-musl.tar.gz --strip-components=1 -C bin
+ echo "OURNOTES_DECK_BIN=$RUNNER_TEMP/deck/bin/ournotes-deck" >> "$GITHUB_ENV"
+ - run: python -m pytest -q -rs tests
- run: python scripts/validate_contract.py scan-request schemas/examples/scan-request.json
- run: python scripts/validate_contract.py scan-response schemas/examples/scan-response.json
- run: python -m build
diff --git a/MANIFEST.in b/MANIFEST.in
index 60506f1..90fc20e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,3 @@
include docs/DECK_PIPELINE.md
include CHANGELOG.md
+recursive-include tests/fixtures *.json
diff --git a/README.md b/README.md
index 418dd2f..d011736 100644
--- a/README.md
+++ b/README.md
@@ -48,10 +48,10 @@ python boxvision.py scan --data DATA --threads 2 --output ./result screenshot-a.
可选的正式 `deck-ui` 入口将截图识别、显式人工补齐和 [OurNotes Deck](https://github.com/empty-sekai/ournotes-deck) 的真实搜索连在一起:
```powershell
-python boxvision.py deck-ui --data DATA --deck-data deck-data.json --solver-bin ournotes-recommend.exe --workspace ./local/deck-ui --port 18790 --box-port 18793
+python boxvision.py deck-ui --data DATA --deck-data deck-data.json --solver-bin ournotes-deck.exe --workspace ./local/deck-ui --port 18790 --box-port 18793
```
-打开 `http://127.0.0.1:18790/`。需要与图库匹配的 DeckData 和支持 `ournotes-deck.recommendation-request/1` 的推荐程序;源码不附带游戏资源。未知字段不会默认填为等级/Rank 1,完整持有集合须由使用者声明。详细补齐契约、mock 复现、版本校验和目标语义见 [组卡输入管线](docs/DECK_PIPELINE.md)。
+打开 `http://127.0.0.1:18790/`。需要与图库匹配的 DeckData 和 ournotes-deck v0.0.3(`recommend` 子命令、`ournotes-deck.search-request/1` 请求);源码不附带游戏资源。未知字段不会默认填为等级/Rank 1,完整持有集合须由使用者声明。详细补齐契约、mock 复现、版本校验和目标语义见 [组卡输入管线](docs/DECK_PIPELINE.md)。
## 数据和模型
diff --git a/bdon_vision/deck/recommend.py b/bdon_vision/deck/recommend.py
index 88c8d3d..035db24 100644
--- a/bdon_vision/deck/recommend.py
+++ b/bdon_vision/deck/recommend.py
@@ -21,14 +21,12 @@ def scene_matrix(deck_path):
shortest=min(d['charts'],key=lambda c:len(c['notes']['id']))
sid=shortest['scoreId']
base=next(r for r in rows(d,'MasterLiveMusic') if sid in [r.get('_easyID'),r.get('_normalID'),r.get('_hardID'),r.get('_expertID')])
- seed={'atoms':[[1,1],[-1,1],[42,1]],'provenance':'Declared illustrative mock finite native root law; not the real TickCount/server distribution'}
specs=[]
def add(id_, execution, scenario=None, metric=None, expected='supported'):
- req={'format':'ournotes-deck.recommendation-request/1','execution':execution,'metric':metric or {'kind':'score'},
+ req={'format':'ournotes-deck.search-request/1','execution':execution,'metric':metric or {'kind':'score'},
'constraints':{'noSnaps':False},'k':3,'strategy':{'kind':'exhaustive'},
'limits':{'timeLimitMs':30000,'maxCandidates':20000,'cacheEntries':2048}}
if scenario: req['scenario']=scenario
- if execution['kind']=='live': req['seedLaw']=seed
specs.append({'id':id_,'request':req,'expected':expected})
add('songless-power',{'kind':'power'},metric={'kind':'power'})
for mode in ['free','mission','battle']:
@@ -114,7 +112,7 @@ def ticks(value):
entry['request']['strategy']={'kind':'candidate','powerSeeds':2,'proposals':600,'proposalSeed':20261001}
entry['request']['limits']={'timeLimitMs':10000,'maxCandidates':1000,'cacheEntries':1024}
return {'schema':'ournotes.integration-scene-matrix/1','owner':'BoxLens optional Deck recommendation workflow',
- 'deckDataSha256':digest(deck_path),'entries':specs,'liveIsSeparate':True,'capabilitySource':'ournotes-deck.recommendation-request/1','randomScope':'Explicit finite native root law and theoretical play; unknown real native TickCount distribution','unsupportedScope':'Battle/Arena require explicitly provided aggregate peer confirmations. Conditional mock Battle cases are separate; current JP has no Arena master rows'}
+ 'deckDataSha256':digest(deck_path),'entries':specs,'liveIsSeparate':True,'capabilitySource':'ournotes-deck.search-request/1','randomScope':'Solver-reported probability law and theoretical-best play','unsupportedScope':'Battle/Arena require explicitly provided aggregate peer confirmations; available scenes depend on the bound regional master'}
def recommend(binary, deck_path, roster_path, matrix, out):
out=Path(out);out.mkdir(parents=True,exist_ok=True)
@@ -141,7 +139,7 @@ def recommend(binary, deck_path, roster_path, matrix, out):
if 'request' not in entry:
result.append({'id':id_,'state':'Unavailable','reason':entry.get('reason'),'complete':False});continue
request=entry['request'];reqfile=out/(id_+'-request.json');write_request(entry,reqfile)
- command=[str(binary),'--data',str(frozen_deck),'--roster',str(frozen_roster),'--request',str(reqfile)]
+ command=[str(binary),'recommend','--data',str(frozen_deck),'--roster',str(frozen_roster),'--request',str(reqfile)]
t=time.perf_counter()
try:
p=subprocess.run(command,capture_output=True,encoding='utf-8',errors='replace',timeout=min(600,max(180,(request.get('limits',{}).get('timeLimitMs') or 165000)/1000+15)))
diff --git a/bdon_vision/deck/web.html b/bdon_vision/deck/web.html
index 1efebbf..4c86cdb 100644
--- a/bdon_vision/deck/web.html
+++ b/bdon_vision/deck/web.html
@@ -9,7 +9,7 @@
复用原始原生卡片 UI 生成器,含跨页重复、隐藏字段、降质与等级冲突。 图片送到本机现有 BoxLens。识别器只接收图片、图库与模型。 以下培养值来自识别或明确补充;技能与玩家状态的来源单独记录。 声明有限根种子分布下的 native Live;技能顺序由原生五槽洗牌决定。 Live、Skip 与综合力分别验收。不可用、未支持、超时和候选搜索状态按真实返回展示。 活动指标、外部排名时间线与打法可由请求文件明确提供;搜索会校验实际 master 与支持范围。 演出结果采用求解器返回的概率模型与请求中的打法。 Live、Skip 与综合力分别验收。不可用、未支持、超时和候选搜索状态按真实返回展示。 活动指标、外部排名时间线与打法可由请求文件明确提供;搜索会校验实际 master 与支持范围。截图输入与可选 mock 样本
运行图片识别
来源与版本
截图实际读到的字段
观察集合 · 保留 unknown查看原始 BoxLens JSON
已明确补齐的 Roster
完整现有 Roster JSON
推荐队伍
各根种子的技能顺序、分数与精确目标值
场景矩阵与状态
查看完整搜索结果与请求
推荐队伍
队伍分数与精确目标值
场景矩阵与状态
查看完整搜索结果与请求