diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index 952c981..4e43acf 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -932,6 +932,20 @@ "Attributes": [], "Name": "DiscardShivPerPick" }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "DiscardPostDraw" + }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "DiscardDrawPerPick" + }, { "Type": "boolean", "DefaultValue": "false", diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 4c3ceac..b93ad87 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -152,6 +152,8 @@ function writeCodeblocks() { prop('number', 'DiscardSelectTotal', '0'), prop('number', 'DiscardPostShiv', '0'), prop('number', 'DiscardShivPerPick', '0'), + prop('number', 'DiscardPostDraw', '0'), + prop('number', 'DiscardDrawPerPick', '0'), prop('boolean', 'RetainSelectActive', 'false'), prop('boolean', 'ReserveSelectActive', 'false'), prop('number', 'NextTurnBlock', '0'), diff --git a/tools/verify/cbprops.mjs b/tools/verify/cbprops.mjs new file mode 100644 index 0000000..f9463e8 --- /dev/null +++ b/tools/verify/cbprops.mjs @@ -0,0 +1,34 @@ +// cb/*.mjs의 `self. = ` 대입 ↔ gen-slaydeck.mjs 선언 prop 대조. +// 미선언 prop에 대입하면 MSW 런타임 "cannot set X, no such field" → 그 후보를 정적 검출. +// 사용: node tools/verify/cbprops.mjs +import { readFileSync, readdirSync } from 'node:fs'; + +const orch = readFileSync('tools/deck/gen-slaydeck.mjs', 'utf8'); +const declared = new Set(); +for (const m of orch.matchAll(/prop\(\s*'[^']*'\s*,\s*'([^']+)'/g)) declared.add(m[1]); + +// MSW 빌트인/설정으로 대입 가능한 self 필드(프롭 아님) — 오탐 제외 화이트리스트. +const BUILTIN = new Set(['Entity']); + +const dir = 'tools/deck/cb'; +const files = readdirSync(dir).filter((f) => f.endsWith('.mjs')); +const assigns = new Map(); // name -> Set(files) +for (const f of files) { + const src = readFileSync(`${dir}/${f}`, 'utf8'); + // self.Name = (단, == / ~= / .Y= / [..]= 는 제외) + for (const m of src.matchAll(/self\.([A-Za-z_]\w*)\s*=(?!=)/g)) { + const name = m[1]; + if (!assigns.has(name)) assigns.set(name, new Set()); + assigns.get(name).add(f); + } +} + +const missing = [...assigns.keys()] + .filter((n) => !declared.has(n) && !BUILTIN.has(n)) + .sort(); + +console.log(`선언 prop: ${declared.size} | 대입된 self.X distinct: ${assigns.size}`); +console.log(`미선언 대입 (no such field 후보): ${missing.length}`); +for (const n of missing) console.log(` - ${n} [${[...assigns.get(n)].join(', ')}]`); +console.log(missing.length ? 'RESULT: MISSING PROPS ABOVE' : 'RESULT: 모든 self 대입이 선언됨 ✓'); +process.exit(missing.length ? 1 : 0);