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 @@

截图输入与可选 mock 样本

复用原始原生卡片 UI 生成器,含跨页重复、隐藏字段、降质与等级冲突。

运行图片识别

图片送到本机现有 BoxLens。识别器只接收图片、图库与模型。

打开 BoxLens 原始校对界面 ↗

截图只观察可见卡片;技能等级、角色 Rank、道具、VIP、回忆和完整持有集合需要单独提供。

截图实际读到的字段

观察集合 · 保留 unknown
同一卡片的等级出现两个值时,合并值保留为未知,冲突证据完整保留。隐藏字段与降质拒识也保留未知。
查看原始 BoxLens JSON
逐图观察与模型来源 ↗

已明确补齐的 Roster

以下培养值来自识别或明确补充;技能与玩家状态的来源单独记录。

完整现有 Roster JSON

补充缺失信息

演示真值与识别过程分开,点击后才用于补齐。真实账号请上传声明来源的人工补充文件。

查看补充契约样例 ↗

只有必要字段、持有集合和版本绑定全部校验通过,才导出 Roster 并进入搜索。
-

推荐队伍

声明有限根种子分布下的 native Live;技能顺序由原生五槽洗牌决定。

目标指标
Complete 的最优性限定于请求中的卡池、约束、打法、有限 seedLaw 和模型范围。真实 TickCount / 服务器根种子总体分布未知。
各根种子的技能顺序、分数与精确目标值

场景矩阵与状态

Live、Skip 与综合力分别验收。不可用、未支持、超时和候选搜索状态按真实返回展示。

活动指标、外部排名时间线与打法可由请求文件明确提供;搜索会校验实际 master 与支持范围。

查看完整搜索结果与请求
+

推荐队伍

演出结果采用求解器返回的概率模型与请求中的打法。

目标指标
只有 Complete 且 optimality=proven 表示请求范围内的排名已经证明;概率模型与计算条件见完整结果。
队伍分数与精确目标值

场景矩阵与状态

Live、Skip 与综合力分别验收。不可用、未支持、超时和候选搜索状态按真实返回展示。

活动指标、外部排名时间线与打法可由请求文件明确提供;搜索会校验实际 master 与支持范围。

查看完整搜索结果与请求