Skip to content
Merged
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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@
| 用了哪些 0G 技術 | 為什麼要用它 | 在哪一行用到 |
|---|---|---|
| **0G Compute Network**<br>TEE 可驗證推論 | 戰報要永久存檔,寫它的那個 agent 就不能是黑箱。金鑰在 pc.0g.ai 選 **Private(TEE enclave)** 開的,推論實際跑在 enclave 裡,不是只呼叫一個 OpenAI 相容端點。 | [`functions/api/narrate.js#L55-L134`](https://github.com/ConsssLab/web/blob/main/functions/api/narrate.js#L55-L134) |
| **0G Compute Network**<br>Router 設定 | 敵方 agent 與旁白 agent 共用同一份供應商工廠,把 `AI_PROVIDER` 改成 `0g` 就能整支切過去,不用改程式碼。 | [`functions/api/og/_shared.js#L22-L23`](https://github.com/ConsssLab/web/blob/main/functions/api/og/_shared.js#L22-L23) · [`functions/api/og/_shared.js#L82-L90`](https://github.com/ConsssLab/web/blob/main/functions/api/og/_shared.js#L82-L90) |
| **0G Compute Network**<br>Router 設定 | 敵方 agent 與旁白 agent 共用同一份供應商工廠,**兩者預設都指向 0G Compute**。要主張「整場都是同一位 agent」,對打的那個 agent 就必須在 enclave 裡 —— 所以預設不是 OpenAI。 | [`functions/api/og/_shared.js#L22-L23`](https://github.com/ConsssLab/web/blob/main/functions/api/og/_shared.js#L22-L23) · [`functions/api/og/_shared.js#L68-L87`](https://github.com/ConsssLab/web/blob/main/functions/api/og/_shared.js#L68-L87) · [`functions/api/og/_shared.js#L89-L97`](https://github.com/ConsssLab/web/blob/main/functions/api/og/_shared.js#L89-L97) |
| **0G Storage**<br>真實寫入 | 記憶碎片要「永久保存」就必須真的落地。用官方 SDK 走完整協議:切 256-byte chunk 算 merkle root → 對 Flow 合約送 submit(付儲存費)→ 把 segment 傳給 storage node。**全程用玩家自己的錢包簽,伺服器不持有私鑰。** | [`public/js/storage.js#L72-L116`](https://github.com/ConsssLab/web/blob/main/public/js/storage.js#L72-L116)<br>節點代理(Node):[`proxy/api/index.js`](https://github.com/ConsssLab/web/blob/main/proxy/api/index.js) —— 節點是裸 IP + 非標準埠,Cloudflare Workers 打不到(error 1003 / 521),這一段必須跑在 Node 上 |
| **0G Storage**<br>indexer 唯讀查詢 | 「上傳沒報錯」不等於存進去了。拿 root hash 回頭問 indexer,確認 storage node 真的收下並 finalized 才敢標成已存檔。唯讀、不需金鑰,評審可自行查證。 | [`functions/api/og/storage.js#L117-L159`](https://github.com/ConsssLab/web/blob/main/functions/api/og/storage.js#L117-L159) |
| **0G Storage**<br>節點活性探測 | 結果畫面的燈號要照實反映網路狀態,不能寫死成綠燈。 | [`functions/api/og/status.js#L34-L57`](https://github.com/ConsssLab/web/blob/main/functions/api/og/status.js#L34-L57) |
| **0G Chain**<br>錨定寫入 | 戰報需要一個不可竄改、有時間戳的存在證明。40 bytes 結構化 calldata(魔術字 `CSSW` + 版本 + 戰績 + SHA-256),用合約建立交易送出,chainscan 上一眼認得出來。 | [`functions/api/og/shard.js#L62-L84`](https://github.com/ConsssLab/web/blob/main/functions/api/og/shard.js#L62-L84) · [`public/js/og.js#L235-L260`](https://github.com/ConsssLab/web/blob/main/public/js/og.js#L235-L260) |
| **0G Chain**<br>錨定寫入 | 戰報需要一個不可竄改、有時間戳的存在證明。40 bytes 結構化 calldata(魔術字 `CSSW` + 版本 + 戰績 + SHA-256),用合約建立交易送出,chainscan 上一眼認得出來。 | [`functions/api/og/shard.js#L85-L107`](https://github.com/ConsssLab/web/blob/main/functions/api/og/shard.js#L85-L107) · [`public/js/og.js#L235-L260`](https://github.com/ConsssLab/web/blob/main/public/js/og.js#L235-L260) |
| **0G Chain**<br>鏈上回驗 | **這是整個專案的重點**:按了按鈕、錢包沒報錯,不代表資料真的在鏈上。所以再用 `eth_getTransactionByHash` 把交易讀回來、反解 calldata、跟本地碎片逐欄比對,四項全對才打勾。 | [`functions/api/og/verify.js#L19-L47`](https://github.com/ConsssLab/web/blob/main/functions/api/og/verify.js#L19-L47) · [`functions/api/og/verify.js#L49-L104`](https://github.com/ConsssLab/web/blob/main/functions/api/og/verify.js#L49-L104) |
| **0G Chain**<br>鏈況與網路切換 | 標題頁即時顯示 Galileo 區塊高度;chainId **從鏈上實際讀回來**校準而不是寫死(0G 換過 chain ID 16601→16602,寫死會讓切鏈整個失敗)。 | [`functions/api/og/status.js#L59-L157`](https://github.com/ConsssLab/web/blob/main/functions/api/og/status.js#L59-L157) · [`public/js/og.js#L167-L220`](https://github.com/ConsssLab/web/blob/main/public/js/og.js#L167-L220) |
| — **敵方 AI agent**<br>(OpenAI,非 0G) | 對戰時每回合都要叫一次,需要低延遲,所以另外走 OpenAI。模型輸出一律當不可信資料重新過濾。 | [`functions/api/agent.js#L173-L256`](https://github.com/ConsssLab/web/blob/main/functions/api/agent.js#L173-L256) · [`public/js/ai.js#L16-L65`](https://github.com/ConsssLab/web/blob/main/public/js/ai.js#L16-L65) |
| — **敵方 AI agent**<br>(OpenAI,非 0G) | 對戰時每回合都要叫一次,需要低延遲,所以另外走 OpenAI。模型輸出一律當不可信資料重新過濾。 | [`functions/api/agent.js#L188-L291`](https://github.com/ConsssLab/web/blob/main/functions/api/agent.js#L188-L291) · [`public/js/ai.js#L16-L68`](https://github.com/ConsssLab/web/blob/main/public/js/ai.js#L16-L68) |

### 評審可以自己打的端點

Expand Down Expand Up @@ -128,7 +128,8 @@ inline SVG:戰場、卡牌、遺忘者的遮罩、結果畫面都沒有圖檔
[`public/js/rules.js`](public/js/rules.js) 是純規則層(無 DOM、無網路),可以直接 import 跑對戰模擬:

```bash
npm run sim # 跑 300 場隨機對戰,印出勝負分布與平均回合數
npm run sim # 跑 300 場隨機對戰,印出勝負分布與平均回合數
npm run check:links # 檢查這份 README 的行號連結有沒有因為改碼而跑掉
```

> ⚠️ **改任何數值前請先重跑模擬。** `npm run sim` 用隨機出牌,每次會抖動;連跑三輪的
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"scripts": {
"dev": "wrangler pages dev public --compatibility-date=2024-09-01 --port 8788",
"deploy": "wrangler pages deploy public --project-name conssswars-web --branch main",
"sim": "node scripts/simulate.mjs"
"sim": "node scripts/simulate.mjs",
"check:links": "node scripts/check-links.mjs"
},
"devDependencies": {
"wrangler": "^3.78.0"
Expand Down
62 changes: 62 additions & 0 deletions scripts/check-links.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* 檢查 README 裡的行號連結有沒有跑掉。
*
* README 的賣點是「精確到行號」—— 評審會點進去。但只要改到被指到的檔案,行號就位移,
* 連結會安靜地指向不相干的程式碼:範圍還在檔案內,所以肉眼看不出壞掉。
* 實際發生過兩次,其中一次「賽道二 indexer 唯讀查詢」點下去落在兩個無關的小工具上。
*
* 這支不驗「行號是不是原本想指的東西」(那需要人判斷),只驗一件機械可查的事:
* 起始行要是函式或註解區塊的開頭,結束行要是收尾。位移之後這兩個條件幾乎必然被破壞。
*
* npm run check:links
*/

import { readFileSync } from 'node:fs';

const README = 'README.md';
const LINK = /`([^`]+?\.js)#L(\d+)-L(\d+)`/g;

const START_OK = /^(\/\*\*|\/\*|export\s|async\s|function\s|const\s|class\s)/;
const END_OK = /^(\}|\}\);|\};|export const .+;)$/;

const readme = readFileSync(README, 'utf8');
const seen = new Set();
let bad = 0;
let total = 0;

for (const [, path, aStr, bStr] of readme.matchAll(LINK)) {
const key = `${path}#${aStr}-${bStr}`;
if (seen.has(key)) continue;
seen.add(key);
total += 1;

let lines;
try {
lines = readFileSync(path, 'utf8').split('\n');
} catch {
console.log(`❌ ${key} 檔案不存在`);
bad += 1;
continue;
}

const a = Number(aStr);
const b = Number(bStr);
if (b > lines.length) {
console.log(`❌ ${key} 檔案只有 ${lines.length} 行`);
bad += 1;
continue;
}

const head = lines[a - 1].trim();
const tail = lines[b - 1].trim();
const ok = START_OK.test(head) && END_OK.test(tail);
Comment on lines +42 to +52
if (!ok) bad += 1;
console.log(`${ok ? '✅' : '❌'} ${key}\n 起 ${head.slice(0, 60)}\n 訖 ${tail.slice(0, 60)}`);
}

console.log(
bad === 0
? `\n${total} 個行號連結都落在函式起訖上。`
: `\n${bad} / ${total} 個連結跑掉了 —— 被指到的檔案改過,行號要重算。`,
);
process.exit(bad === 0 ? 0 : 1);