{"_id":"@aemeath-projects/trpg","_rev":"4-fc9be9252484850676011b1562dfb9ab","name":"@aemeath-projects/trpg","dist-tags":{"latest":"1.0.2"},"versions":{"0.1.0":{"name":"@aemeath-projects/trpg","version":"0.1.0","keywords":["trpg","dice-roller","coc","dnd","rule-engine","typescript"],"author":"Aemeath Projects","license":"MIT","_id":"@aemeath-projects/trpg@0.1.0","maintainers":[{"name":"akaririn","email":"akaririn1028@gmail.com"}],"homepage":"https://aemeath.dev","bugs":{"url":"https://github.com/aemeath-projects/trpg/issues"},"dist":{"shasum":"1e47d719f23242cc9e6532bcf745ad13b530af35","tarball":"https://registry.npmjs.org/@aemeath-projects/trpg/-/trpg-0.1.0.tgz","fileCount":32,"integrity":"sha512-wtT6dBK5P+rSrLAOEuSUx4bYVyHc9ZRKijx1yUlfprSSuYiCp94mzUT9e10p6p0JOY8Ney5zpLF8z+JxqI8tSg==","signatures":[{"sig":"MEUCIBr2wR9Gx0/Ou7oty2uMgPhArdfqLmd0NCBuXSyXw5vgAiEAoYihuQRkekBwUckQjwmGfGOukpIm7iRGe3Qjp+WwCxk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":149980},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","config":{"commitizen":{"path":"cz-conventional-changelog"}},"engines":{"node":">=24.17.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./coc":{"types":"./dist/coc/index.d.ts","import":"./dist/coc/index.js"},"./dnd":{"types":"./dist/dnd/index.d.ts","import":"./dist/dnd/index.js"},"./log":{"types":"./dist/log/index.d.ts","import":"./dist/log/index.js"},"./dice":{"types":"./dist/dice/index.d.ts","import":"./dist/dice/index.js"},"./decks":{"types":"./dist/decks/index.d.ts","import":"./dist/decks/index.js"},"./stats":{"types":"./dist/stats/index.d.ts","import":"./dist/stats/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"}},"scripts":{"dev":"tsup --watch","bump":"commit-and-tag-version","lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\"","test":"vitest run","build":"tsup","commit":"cz","format":"prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"","lint:fix":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\" --fix","changelog":"commit-and-tag-version --skip.bump --skip.commit --skip.tag","bump:major":"commit-and-tag-version --release-as major","bump:minor":"commit-and-tag-version --release-as minor","bump:patch":"commit-and-tag-version --release-as patch","format:fix":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:watch":"vitest","type-check":"tsc --noEmit -p tsconfig.test.json","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"akaririn","email":"akaririn1028@gmail.com"},"repository":{"url":"https://github.com/aemeath-projects/trpg.git","type":"git"},"description":"TRPG 骰娘规则引擎：无状态掷骰求值、CoC/D&D 规则判定、人物生成公式、日志状态机等纯函数集合","directories":{},"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"],"tests/**/*.ts":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","husky":"^9.1.7","eslint":"^10.7.0","vitest":"^4.1.10","prettier":"^3.9.6","@eslint/js":"^10.0.1","commitizen":"^4.3.2","typescript":"^6.0.3","@types/node":"^26.1.1","lint-staged":"^17.1.1","@commitlint/cli":"^21.2.1","typescript-eslint":"^8.65.0","@vitest/coverage-v8":"^4.1.10","commit-and-tag-version":"^13.1.0","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.17.1","cz-conventional-changelog":"^3.3.0","@commitlint/config-conventional":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/trpg_0.1.0_1785007628917_0.07056772319974502","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@aemeath-projects/trpg","version":"1.0.0","keywords":["trpg","dice-roller","coc","dnd","rule-engine","typescript"],"author":"Aemeath Projects","license":"MIT","_id":"@aemeath-projects/trpg@1.0.0","maintainers":[{"name":"akaririn","email":"akaririn1028@gmail.com"}],"homepage":"https://aemeath.dev","bugs":{"url":"https://github.com/aemeath-projects/trpg/issues"},"dist":{"shasum":"72adae1cc4516ac71a8077434cfe07e606b3d5b0","tarball":"https://registry.npmjs.org/@aemeath-projects/trpg/-/trpg-1.0.0.tgz","fileCount":35,"integrity":"sha512-rCvThtrZQPq8jAm8ZqtOxeDzzy5/4UTXVsvSOwMQQZ4+hPflrayJNON6ATeP0WFsL7lY22dVpkLEqDMxjKlFeA==","signatures":[{"sig":"MEUCIQDmIEkcWV/TvlwA95vHqhAvA7grlwe3QyZR+yqZpqnh9QIgBaIEpLl1IjPhLgfdBpQEILG63z5LIc0fjMtT0udp/SQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aemeath-projects%2ftrpg@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":187578},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","config":{"commitizen":{"path":"cz-conventional-changelog"}},"engines":{"node":">=24.17.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./coc":{"types":"./dist/coc/index.d.ts","import":"./dist/coc/index.js"},"./dnd":{"types":"./dist/dnd/index.d.ts","import":"./dist/dnd/index.js"},"./log":{"types":"./dist/log/index.d.ts","import":"./dist/log/index.js"},"./dice":{"types":"./dist/dice/index.d.ts","import":"./dist/dice/index.js"},"./decks":{"types":"./dist/decks/index.d.ts","import":"./dist/decks/index.js"},"./stats":{"types":"./dist/stats/index.d.ts","import":"./dist/stats/index.js"},"./story":{"types":"./dist/story/index.d.ts","import":"./dist/story/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"}},"scripts":{"dev":"tsup --watch","bump":"commit-and-tag-version","lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\"","test":"vitest run","build":"tsup","commit":"cz","format":"prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"","lint:fix":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\" --fix","changelog":"commit-and-tag-version --skip.bump --skip.commit --skip.tag","bump:major":"commit-and-tag-version --release-as major","bump:minor":"commit-and-tag-version --release-as minor","bump:patch":"commit-and-tag-version --release-as patch","format:fix":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:watch":"vitest","type-check":"tsc --noEmit -p tsconfig.test.json","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:6e55cec1-0079-4a85-a5dc-54db582d6fb9"}},"repository":{"url":"https://github.com/aemeath-projects/trpg.git","type":"git"},"description":"TRPG 骰娘规则引擎：无状态掷骰求值、CoC/D&D 规则判定、人物生成公式、日志状态机等纯函数集合","directories":{},"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"],"tests/**/*.ts":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"fflate":"^0.8.3","msgpackr":"^2.0.4"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","husky":"^9.1.7","eslint":"^10.7.0","vitest":"^4.1.10","prettier":"^3.9.6","@eslint/js":"^10.0.1","commitizen":"^4.3.2","typescript":"^6.0.3","@types/node":"^26.1.1","lint-staged":"^17.1.1","@commitlint/cli":"^21.2.1","typescript-eslint":"^8.65.0","@vitest/coverage-v8":"^4.1.10","commit-and-tag-version":"^13.1.0","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.17.1","cz-conventional-changelog":"^3.3.0","@commitlint/config-conventional":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/trpg_1.0.0_1785131665092_0.049516933216002146","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@aemeath-projects/trpg","version":"1.0.1","keywords":["trpg","dice-roller","coc","dnd","rule-engine","typescript"],"author":"Aemeath Projects","license":"MIT","_id":"@aemeath-projects/trpg@1.0.1","maintainers":[{"name":"akaririn","email":"akaririn1028@gmail.com"}],"homepage":"https://aemeath.dev","bugs":{"url":"https://github.com/aemeath-projects/trpg/issues"},"dist":{"shasum":"f07a6c94b74fdc0dee0fbac3a146dcff3bc0dc7f","tarball":"https://registry.npmjs.org/@aemeath-projects/trpg/-/trpg-1.0.1.tgz","fileCount":35,"integrity":"sha512-Kuq41ISGyItRSc/PcjZPywG9LLJ9Jk+22ozl1DI0DDkle0gcaHtlQuEkTRasOvC7FE3o3kBX0nxgiwJ7ZdAidg==","signatures":[{"sig":"MEQCIDNX4QEmcnGk6tyOxld8GkrYYNWZpfwVrTXJQ5J6+fGcAiAdypwjc9i5cQGVxVepJYo/L73UCUrXTaRbCgwf5fWyRA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aemeath-projects%2ftrpg@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":196121},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","config":{"commitizen":{"path":"cz-conventional-changelog"}},"engines":{"node":">=24.17.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./coc":{"types":"./dist/coc/index.d.ts","import":"./dist/coc/index.js"},"./dnd":{"types":"./dist/dnd/index.d.ts","import":"./dist/dnd/index.js"},"./log":{"types":"./dist/log/index.d.ts","import":"./dist/log/index.js"},"./dice":{"types":"./dist/dice/index.d.ts","import":"./dist/dice/index.js"},"./decks":{"types":"./dist/decks/index.d.ts","import":"./dist/decks/index.js"},"./stats":{"types":"./dist/stats/index.d.ts","import":"./dist/stats/index.js"},"./story":{"types":"./dist/story/index.d.ts","import":"./dist/story/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"}},"scripts":{"dev":"tsup --watch","bump":"commit-and-tag-version","lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\"","test":"vitest run","build":"tsup","commit":"cz","format":"prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"","lint:fix":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\" --fix","changelog":"commit-and-tag-version --skip.bump --skip.commit --skip.tag","bump:major":"commit-and-tag-version --release-as major","bump:minor":"commit-and-tag-version --release-as minor","bump:patch":"commit-and-tag-version --release-as patch","format:fix":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","test:watch":"vitest","type-check":"tsc --noEmit -p tsconfig.test.json","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:6e55cec1-0079-4a85-a5dc-54db582d6fb9"}},"repository":{"url":"https://github.com/aemeath-projects/trpg.git","type":"git"},"description":"TRPG 骰娘规则引擎：无状态掷骰求值、CoC/D&D 规则判定、人物生成公式、日志状态机等纯函数集合","directories":{},"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"],"tests/**/*.ts":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"fflate":"^0.8.3","msgpackr":"^2.0.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","husky":"^9.1.7","eslint":"^10.8.0","vitest":"^4.1.10","prettier":"^3.9.6","@eslint/js":"^10.0.1","commitizen":"^4.3.2","typescript":"^6.0.3","@types/node":"^26.1.2","lint-staged":"^17.2.0","@commitlint/cli":"^21.2.1","typescript-eslint":"^8.65.0","@vitest/coverage-v8":"^4.1.10","commit-and-tag-version":"^13.1.2","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.17.1","cz-conventional-changelog":"^3.3.0","@commitlint/config-conventional":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/trpg_1.0.1_1785506745298_0.961964195460792","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@aemeath-projects/trpg","version":"1.0.2","description":"TRPG 骰娘规则引擎：无状态掷骰求值、CoC/D&D 规则判定、人物生成公式、日志状态机等纯函数集合","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"engines":{"node":">=24.17.0"},"keywords":["trpg","dice-roller","coc","dnd","rule-engine","typescript"],"author":"Aemeath Projects","license":"MIT","publishConfig":{"access":"public"},"repository":{"type":"git","url":"https://github.com/aemeath-projects/trpg.git"},"bugs":{"url":"https://github.com/aemeath-projects/trpg/issues"},"homepage":"https://aemeath.dev","config":{"commitizen":{"path":"cz-conventional-changelog"}},"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"],"tests/**/*.ts":["eslint --fix","prettier --write"]},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","import":"./dist/types/index.js"},"./dice":{"types":"./dist/dice/index.d.ts","import":"./dist/dice/index.js"},"./coc":{"types":"./dist/coc/index.d.ts","import":"./dist/coc/index.js"},"./dnd":{"types":"./dist/dnd/index.d.ts","import":"./dist/dnd/index.js"},"./decks":{"types":"./dist/decks/index.d.ts","import":"./dist/decks/index.js"},"./stats":{"types":"./dist/stats/index.d.ts","import":"./dist/stats/index.js"},"./log":{"types":"./dist/log/index.d.ts","import":"./dist/log/index.js"},"./story":{"types":"./dist/story/index.d.ts","import":"./dist/story/index.js"}},"devDependencies":{"@commitlint/cli":"^21.2.1","@commitlint/config-conventional":"^21.2.0","@eslint/js":"^10.0.1","@types/node":"^26.1.2","@vitest/coverage-v8":"^4.1.10","commit-and-tag-version":"^13.1.2","commitizen":"^4.3.2","cz-conventional-changelog":"^3.3.0","eslint":"^10.8.0","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.17.1","husky":"^9.1.7","lint-staged":"^17.3.0","prettier":"^3.9.6","tsup":"^8.5.1","typescript":"^6.0.3","typescript-eslint":"^8.65.0","vitest":"^4.1.10"},"dependencies":{"fflate":"^0.8.3","msgpackr":"^2.0.5"},"scripts":{"build":"tsup","dev":"tsup --watch","lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\"","lint:fix":"eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"*.ts\" --fix","format":"prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"","format:fix":"prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"","type-check":"tsc --noEmit -p tsconfig.test.json","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","bump":"commit-and-tag-version","bump:patch":"commit-and-tag-version --release-as patch","bump:minor":"commit-and-tag-version --release-as minor","bump:major":"commit-and-tag-version --release-as major","changelog":"commit-and-tag-version --skip.bump --skip.commit --skip.tag","commit":"cz"},"_nodeVersion":"24.18.0","_id":"@aemeath-projects/trpg@1.0.2","dist":{"integrity":"sha512-elCx5SFinQBRrpImy/kRKOGRUuMonmZu8mVHCfoLMbVLv7S5sB87rnZddxqYICgvUBQLMXpMfSqKJbIOUJ5wkQ==","shasum":"c39dfa9d7b3958fcd0f326141efbc5121eb21382","tarball":"https://registry.npmjs.org/@aemeath-projects/trpg/-/trpg-1.0.2.tgz","fileCount":35,"unpackedSize":197375,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aemeath-projects%2ftrpg@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDLbKkHffB6ZUwqjOe87VE06HdYSRXMtz9cI3lQBiMABQIgbrXXvIpyTAVKd4B2MPuHc3fiTyQu5PDK+2IhYMGHPi4="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:6e55cec1-0079-4a85-a5dc-54db582d6fb9"}},"directories":{},"maintainers":[{"name":"akaririn","email":"akaririn1028@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/trpg_1.0.2_1785661537763_0.026911481520050407"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-25T19:27:08.770Z","modified":"2026-08-02T09:05:38.244Z","0.1.0":"2026-07-25T19:27:09.071Z","1.0.0":"2026-07-27T05:54:25.223Z","1.0.1":"2026-07-31T14:05:45.425Z","1.0.2":"2026-08-02T09:05:37.914Z"},"bugs":{"url":"https://github.com/aemeath-projects/trpg/issues"},"author":"Aemeath Projects","license":"MIT","homepage":"https://aemeath.dev","keywords":["trpg","dice-roller","coc","dnd","rule-engine","typescript"],"repository":{"type":"git","url":"https://github.com/aemeath-projects/trpg.git"},"description":"TRPG 骰娘规则引擎：无状态掷骰求值、CoC/D&D 规则判定、人物生成公式、日志状态机等纯函数集合","maintainers":[{"name":"akaririn","email":"akaririn1028@gmail.com"}],"readme":"# Aemeath TRPG Engine\n\n骰娘规则引擎——掷骰、CoC/D&D 判定、人物生成、日志状态机，纯函数，双端可用。\n\n![NPM Version](https://img.shields.io/npm/v/%40aemeath-projects%2Ftrpg?style=for-the-badge)\n![CI Status](https://img.shields.io/github/actions/workflow/status/aemeath-projects/trpg/publish.yml?style=for-the-badge)\n![Codecov](https://img.shields.io/codecov/c/github/aemeath-projects/trpg?style=for-the-badge)\n![License](https://img.shields.io/github/license/aemeath-projects/trpg?style=for-the-badge)\n\n## 这是什么\n\n一个给骰娘 Bot 用的规则计算库。它不做任何 I/O——不连数据库、不发网络请求、也不绑定任何 Bot 框架。你把数据喂进来，它算完还给你，怎么存是你的事。\n\n所有模块都可以按 subpath 单独导入，打包工具会自动 tree-shake 掉不用的部分：\n\n```ts\nimport { rollExpression } from '@aemeath-projects/trpg/dice'\nimport { generateCharacter } from '@aemeath-projects/trpg/coc'\n```\n\n除了 `story`（归档编解码）依赖 `msgpackr` + `fflate` 两个纯 JS 包，其余模块零依赖。不导入 `story` 就不会把这些依赖打进去。\n\nNode 和浏览器行为一致——加密走 WebCrypto，没用 `node:crypto`。\n\n## 安装\n\n```bash\npnpm add @aemeath-projects/trpg\n```\n\n需要 Node.js ≥ 24.17。\n\n## 模块\n\n| 模块 | 导入路径 | 主要导出 |\n|---|---|---|\n| **dice** | `.../trpg/dice` | `rollExpression` · `maxOfExpression` · `rollDie` |\n| **coc** | `.../trpg/coc` | `generateCharacter` · `judgeCoCResult` · `applySanityCheck` · `applyGrowthCheck` · `rollBonusPenaltyD100` |\n| **dnd** | `.../trpg/dnd` | `computeSkillModifier` · `rollNamedAbilityScoreSets` · `applyDeathSaveRoll` · `initSlots` · `restSlots` · `generateName` |\n| **decks** | `.../trpg/decks` | `draw` |\n| **stats** | `.../trpg/stats` | `summarize` |\n| **log** | `.../trpg/log` | `start` · `pause` · `resume` · `end` · `computeDuration` |\n| **story** | `.../trpg/story` | `packStory` · `unpackStory` · `packStoryEncrypted` · `unpackStoryEncrypted` · `readStoryHeader` · `isStoryEncrypted` |\n| **types** | `.../trpg/types` | `RandomSource` · `CheckResult` |\n\n## 快速上手\n\n### 掷骰\n\n```ts\nimport { rollExpression, maxOfExpression } from '@aemeath-projects/trpg/dice'\n\nconst r = rollExpression('4d6kh3')   // D&D 属性掷骰：4d6 取最高 3 个\nr.total   // → 15\nr.detail  // → \"4D6=[5,3,6,2]=>(kh3)[5,3,6]=14\"\n\nmaxOfExpression('3d6+2')            // → 20，不掷骰，纯理论上限\n```\n\n支持 `NdM`、`d%`、四则运算、`khN` / `klN`（取高/取低）、嵌套括号。\n\n### CoC 人物生成与检定\n\n```ts\nimport { generateCharacter, judgeCoCResult } from '@aemeath-projects/trpg/coc'\n\nconst char = generateCharacter('coc7')  // 7 版人物，含八围 + 派生属性\n\nconst outcome = judgeCoCResult({ roll: 45, skill: 60, rule: 'coc7' })\noutcome.result       // → 'success'\noutcome.description  // → 中文描述\n```\n\n`judgeCoCResult` 内置 6 版和 7 版共 8 种房规策略。\n\n#### 理智检定与幕间成长\n\n```ts\nimport { applySanityCheck, applyGrowthCheck } from '@aemeath-projects/trpg/coc'\n\n// san check：传入当前 san，返回损失量和疯狂状态\nconst outcome = applySanityCheck({ currentSanity: 42, maxSanity: 99, lossExpr: '1d6/1d20' })\n\n// 幕间成长\nconst grown = applyGrowthCheck({ skillName: '图书馆使用', currentValue: 45 })\n```\n\n### D&D 5e\n\n```ts\nimport {\n  rollNamedAbilityScoreSets,\n  computeSkillModifier,\n  applyDeathSaveRoll,\n  initSlots,\n  generateName,\n} from '@aemeath-projects/trpg/dnd'\n\n// 6 属性掷骰（4d6kh3）\nconst scores = rollNamedAbilityScoreSets()\n\n// 技能调整值\nconst mod = computeSkillModifier({ attributeMod: 3, isProficient: true, proficiencyBonus: 2 })\nmod.total   // → 5\n\n// 死亡豁免状态机\nconst state = applyDeathSaveRoll(prevState, { roll: 15 })\nstate.successes  // → 1\n\n// 法术位\nconst slots = initSlots({ level: 3, slots: { 1: 4, 2: 2 } })\n\n// 随机姓名（按种族音节拼接）\nconst name = generateName('elf')\n```\n\n### 抽取 & 统计\n\n```ts\nimport { draw } from '@aemeath-projects/trpg/decks'\nimport { summarize } from '@aemeath-projects/trpg/stats'\n\ndraw(['A', 'B', 'C', 'D'], 2)  // → ['C', 'A']（无放回）\n\nconst summary = summarize([{ result: 'success' }, { result: 'great_success' }, { result: 'fail' }])\nsummary.successRate        // → 0.666...\nsummary.greatSuccessCount  // → 1\n```\n\n### 日志状态机\n\n```ts\nimport { start, pause, resume, end, computeDuration } from '@aemeath-projects/trpg/log'\n\nlet log = start(Date.now())           // → recording\nlog = pause(log, Date.now())          // → paused\nlog = resume(log, Date.now())         // → recording\nlog = end(log, Date.now())            // → ended\n\ncomputeDuration(log, Date.now())      // → 累计有效毫秒（已扣除暂停）\n```\n\n状态转换：`recording` ⇄ `paused` → `ended`。\n\n### `.story` 跑团日志归档\n\n`.story` 是跑团记录的交换格式——8 字节头 + msgpack 载荷（默认 gzip，可选口令加密）。Bot 导出它，在线查看器打开它。\n\n```ts\nimport { packStory, unpackStory, STORY_EXTENSION } from '@aemeath-projects/trpg/story'\nimport type { StoryDocument } from '@aemeath-projects/trpg/story'\n\nconst doc: StoryDocument = {\n  meta: {\n    logName: '深海余烬',\n    createdBy: '10001',\n    startedAt: 1769472000000,\n    endedAt: 1769486400000,\n    durationMs: 12600000,\n    entryCount: 2,\n    exportedAt: Date.now(),\n    source: 'My Dice Bot',\n  },\n  actors: [\n    { id: '10001', name: 'KP老王' },\n    { id: '10002', name: '调查员小李', characterName: '陈默',\n      avatar: 'https://example.com/avatar/10002.png' },\n  ],\n  characters: [\n    { name: '陈默', ruleset: 'coc7', owner: 1,\n      attributes: { 力量: 50, 侦查: 60, san: 55 } },\n  ],\n  entries: [\n    { offset: 0, actor: 0, type: 'message', text: '你们推开了灯塔的门。' },\n    { offset: 22400, actor: 1, type: 'roll', character: 0,\n      text: '陈默 进行 侦查 检定：D100=23/60 困难成功',\n      roll: { expression: '1d100', total: 23, detail: 'D100=23', rolls: [23],\n              skill: '侦查', target: 60, outcome: 'hard_success' } },\n  ],\n}\n\nconst bytes = packStory(doc)\nconst restored = unpackStory(bytes)\n```\n\n文档四段：`meta` → `actors` → `characters` → `entries`，段之间用下标引用。`offset` 是相对 `startedAt` 的毫秒偏移，`text` 只存纯文本（图片/语音压成占位符），`characters` 独立成段不挂在 actor 下。`unpackStory` 内部按版本号分派解析器，你拿到的永远是同一种 `StoryDocument`。\n\n```ts\nimport { readStoryHeader, isSupportedStoryVersion } from '@aemeath-projects/trpg/story'\n\nconst header = readStoryHeader(bytes)   // 只读 8 字节头\nif (!isSupportedStoryVersion(header.version)) { /* 格式太新或已废弃 */ }\n```\n\n#### 口令加密\n\n```ts\nimport { packStoryEncrypted, unpackStoryEncrypted, isStoryEncrypted } from '@aemeath-projects/trpg/story'\n\nconst bytes = await packStoryEncrypted(doc, 'a very long passphrase')\nisStoryEncrypted(bytes)   // → true\nconst restored = await unpackStoryEncrypted(bytes, 'a very long passphrase')\n```\n\nAES-256-GCM + PBKDF2-HMAC-SHA256，走 WebCrypto。先压缩后加密。口令丢了归档永久不可读。\n\n### 可复现随机\n\n所有接受随机数的函数都支持注入 `RandomSource`：\n\n```ts\nimport seedrandom from 'seedrandom'\nimport { rollExpression } from '@aemeath-projects/trpg/dice'\n\nconst rng = seedrandom('fixed-seed')\nrollExpression('3d6', rng)  // 每次相同输入返回相同结果\n```\n","readmeFilename":""}