docs: 添加随机逻辑解释页面
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
<div class="nav-grid">
|
||||
<a class="nav-card" href="structure.html"><strong>结构与分层</strong><span>Runtime、Demo、DemoEditor、Tests 的边界与依赖方向。</span></a>
|
||||
<a class="nav-card" href="lifecycle.html"><strong>流程与生命周期</strong><span>从 Roll 请求到奖励、Lucky Dice、目标玩法入口的完整链路。</span></a>
|
||||
<a class="nav-card" href="random-logic.html"><strong>随机逻辑解释</strong><span>权重随机、分层池漏斗、状态沉淀与可观测 Trace 的决策图。</span></a>
|
||||
<a class="nav-card" href="module-development.html"><strong>玩法与模块开发</strong><span>规则、Matcher、Action、候选、TargetMode 如何扩展。</span></a>
|
||||
<a class="nav-card" href="resources.html"><strong>资源管理</strong><span>当前资源策略、配置形态、Unity 资源接入缺口。</span></a>
|
||||
<a class="nav-card" href="ui.html"><strong>UI 管理</strong><span>Demo UI 的组织方式,以及框架层尚未拥有的 UI 抽象。</span></a>
|
||||
|
||||
151
analysis/FishDice/random-logic.html
Normal file
151
analysis/FishDice/random-logic.html
Normal file
@@ -0,0 +1,151 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>FishDice 随机逻辑解释</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<div class="topbar"><a href="index.html">← 返回总览</a><span class="brand">随机逻辑解释</span></div>
|
||||
|
||||
<section class="hero">
|
||||
<h1>FishDice 随机逻辑:从权重候选到分层池漏斗</h1>
|
||||
<p class="subtitle">这页解释当前仓库里两套随机决策:一套服务 Lucky Dice 候选选择,另一套是更通用的 Pool Funnel 分层随机池。它们共同的设计目标是:随机可注入、权重可解释、结果可回放、失败可观测。</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>一句话模型</h2>
|
||||
<div class="note good">随机并不是“从列表里随手挑一个”。框架会先筛候选、算权重、只在最高优先级集合里抽取,再把随机值、选中项、状态变化和后续决策写入结果对象或 Trace。</div>
|
||||
<div class="grid-3">
|
||||
<div class="metric"><div class="value">IRandomSource</div><div class="label">随机源由外部注入,测试可以用固定序列复现结果。</div></div>
|
||||
<div class="metric"><div class="value">Weight</div><div class="label">候选通过权重参与抽取,权重为 0 的项不会进入抽奖池。</div></div>
|
||||
<div class="metric"><div class="value">Trace</div><div class="label">Pool Funnel 会记录每层权重、随机值、命中结果、状态前后差异。</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>流程图:Pool Funnel 随机决策</h2>
|
||||
<div class="diagram-panel">
|
||||
<div class="logic-flow" aria-label="Pool Funnel 随机流程图">
|
||||
<div class="logic-node">
|
||||
<strong>1. 输入请求</strong>
|
||||
<span><code>RandomPoolDefinition</code> + <code>PoolFunnelRequest</code> 进入服务。</span>
|
||||
</div>
|
||||
<div class="logic-node">
|
||||
<strong>2. 解析入口层</strong>
|
||||
<span>检查 <code>EntryLayerIds</code>,过滤不可用层,按 priority 选入口。</span>
|
||||
</div>
|
||||
<div class="logic-node">
|
||||
<strong>3. 读取状态</strong>
|
||||
<span>从 <code>IRandomPoolStateStore</code> 取当前层 luck value 和 roll count。</span>
|
||||
</div>
|
||||
<div class="logic-node">
|
||||
<strong>4. 计算权重</strong>
|
||||
<span>默认公式:<code>BaseWeight + Factor * LuckValue</code>,小于 0 归零。</span>
|
||||
</div>
|
||||
<div class="logic-node break">
|
||||
<strong>5. 筛候选池</strong>
|
||||
<span>只保留权重大于 0 的结果,再取最高 priority 的结果组。</span>
|
||||
</div>
|
||||
<div class="logic-node alt">
|
||||
<strong>6. 权重抽取</strong>
|
||||
<span><code>Next(0, totalWeight)</code> 得到随机游标,累加权重命中结果。</span>
|
||||
</div>
|
||||
<div class="logic-node alt">
|
||||
<strong>7. 执行动作</strong>
|
||||
<span>把选中结果声明的 actions 交给 dispatcher,默认只排队成功结果。</span>
|
||||
</div>
|
||||
<div class="logic-node alt">
|
||||
<strong>8. 沉淀状态</strong>
|
||||
<span>默认将 <code>LuckValue += LuckValueDelta</code>,并让 roll count +1。</span>
|
||||
</div>
|
||||
<div class="logic-node alt">
|
||||
<strong>9. 判断去向</strong>
|
||||
<span>结果配置决定 Stop 或 NextLayer;NextLayer 会进入下一层循环。</span>
|
||||
</div>
|
||||
<div class="logic-node stop">
|
||||
<strong>10. 输出结果</strong>
|
||||
<span>返回 outcome、每层详情和 <code>PoolFunnelTrace</code>,便于复盘。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>这个流程的关键不是“随机算法多复杂”,而是把随机前后的上下文都保留下来:入口层为什么可用、候选为什么被过滤、某个结果为什么权重更高、抽中的随机游标落在哪个区间。</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>框架图:随机池组件关系</h2>
|
||||
<div class="diagram-panel">
|
||||
<div class="framework-map" aria-label="随机池框架图">
|
||||
<div class="framework-column">
|
||||
<div class="framework-title">配置与输入</div>
|
||||
<div class="framework-box"><strong>RandomPoolDefinition</strong><p>定义 pool id、入口层列表、所有随机层。</p></div>
|
||||
<div class="framework-box"><strong>RandomLayerDefinition</strong><p>定义 layer id、优先级、可用性 key、结果集合。</p></div>
|
||||
<div class="framework-box"><strong>RandomResultDefinition</strong><p>定义 result key、priority、基础权重、幸运值因子、状态增量和下一步决策。</p></div>
|
||||
</div>
|
||||
|
||||
<div class="framework-column">
|
||||
<div class="framework-title">核心编排</div>
|
||||
<div class="framework-box"><strong>PoolFunnelRandomService</strong><p>负责入口层解析、循环 RollLayer、处理空池、处理 NextLayer、生成最终结果。</p></div>
|
||||
<div class="framework-box"><strong>RollLayer</strong><p>读取状态 → 算全部权重 → 取最高优先级候选 → 权重抽取 → action → settlement → decision。</p></div>
|
||||
<div class="framework-box"><strong>PoolFunnelRunResult</strong><p>输出 session、pool、每层结果、最终 outcome 和可回放 Trace。</p></div>
|
||||
</div>
|
||||
|
||||
<div class="framework-column">
|
||||
<div class="framework-title">可替换策略</div>
|
||||
<div class="framework-box"><strong>IRandomSource</strong><p>控制随机值来源,线上用真实随机,测试用脚本随机。</p></div>
|
||||
<div class="framework-box"><strong>ILayerAvailabilityPolicy</strong><p>判断层是否开放,默认 AlwaysAvailable。</p></div>
|
||||
<div class="framework-box"><strong>IResultWeightCalculator</strong><p>计算结果权重,默认线性幸运值公式。</p></div>
|
||||
<div class="framework-box"><strong>IPoolSettlementStrategy</strong><p>处理抽中后的状态变化,默认按 delta 修改幸运值。</p></div>
|
||||
<div class="framework-box"><strong>IFunnelDecisionStrategy</strong><p>决定停在当前结果,还是进入下一层。</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>核心公式</h2>
|
||||
<p>默认权重计算来自 <code>LinearLuckValueWeightCalculator</code>:</p>
|
||||
<div class="formula">weight = max(0, BaseWeight + LuckValueWeightFactor * LuckValue)</div>
|
||||
<p>抽取时先求最高优先级候选的总权重,然后生成 <code>[0, totalWeight)</code> 的随机数。遍历候选并累加权重,随机数落入哪个区间,就选中哪个结果。</p>
|
||||
<div class="note warning">priority 比 weight 更先发生作用:低优先级结果即使权重很高,也不会和最高优先级结果同池竞争。</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Lucky Dice 候选随机</h2>
|
||||
<div class="step-list">
|
||||
<div class="step-item"><strong>先过滤。</strong>只保留 enabled、满足 player level、满足 trigger source 的候选。</div>
|
||||
<div class="step-item"><strong>再按权重抽取。</strong>权重大于 0 的候选进入池子,使用同样的累加区间方式命中结果。</div>
|
||||
<div class="step-item"><strong>最后兜底。</strong>如果没有可抽候选,优先找 default candidate;仍没有时返回 <code>NormalRewardFallback</code>。</div>
|
||||
<div class="step-item"><strong>补展示槽。</strong>选中候选后决定 result slot count,候选未配置时使用 Lucky Dice 骰子集默认槽数。</div>
|
||||
</div>
|
||||
<p>因此 Lucky Dice 的随机逻辑更轻:它没有多层状态沉淀,也不循环跳层;它主要服务“从当前可用特殊结果里选一个入口”。</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>为什么这样设计</h2>
|
||||
<table>
|
||||
<tr><th>问题</th><th>设计回答</th><th>收益</th></tr>
|
||||
<tr><td>测试里怎么复现随机?</td><td>随机源走 <code>IRandomSource</code> 注入。</td><td>固定随机序列即可断言选中项。</td></tr>
|
||||
<tr><td>概率为什么变化?</td><td>权重计算器显式依赖基础权重和幸运值。</td><td>策划参数、保底逻辑、状态变化都能解释。</td></tr>
|
||||
<tr><td>结果池为空怎么办?</td><td>Pool Funnel 返回 <code>NoWeightedResult</code>;Lucky Dice 走 default 或普通奖励兜底。</td><td>失败路径不会静默吞掉。</td></tr>
|
||||
<tr><td>以后要换概率算法怎么办?</td><td>可替换 weight calculator、settlement、decision strategy。</td><td>核心编排稳定,策略可以独立演进。</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="evidence">
|
||||
<h2>代码证据</h2>
|
||||
<ul>
|
||||
<li><span class="path">PoolFunnelRandomService.cs:52-88</span>:入口层解析、循环滚层和 NextLayer 处理。</li>
|
||||
<li><span class="path">PoolFunnelRandomService.cs:117-169</span>:权重计算、最高优先级候选、随机游标、状态沉淀和决策。</li>
|
||||
<li><span class="path">PoolFunnelStrategies.cs:35-74</span>:默认可用性、线性权重和 delta settlement。</li>
|
||||
<li><span class="path">LuckyDiceFlowService.cs:16-49</span>:Lucky Dice 候选过滤、权重选择、默认结果兜底。</li>
|
||||
<li><span class="path">LuckyDiceRollFlowService.cs:65-90</span>:只有 action 产生 <code>LuckyDiceRequestedEffect</code> 时才进入 Lucky Dice 随机选择。</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="footer">生成目录:<span class="path">analysis/FishDice/random-logic.html</span>。这是离线静态页面,可直接用浏览器打开。</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -281,6 +281,152 @@ pre {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.diagram-panel {
|
||||
border: 1px solid var(--line);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(184, 71, 42, 0.08), transparent 36%),
|
||||
linear-gradient(315deg, rgba(31, 111, 104, 0.08), transparent 34%),
|
||||
var(--panel);
|
||||
padding: 20px;
|
||||
margin: 18px 0 28px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.logic-flow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(142px, 1fr));
|
||||
gap: 14px;
|
||||
min-width: 820px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.logic-node {
|
||||
position: relative;
|
||||
border: 2px solid var(--line);
|
||||
background: rgba(255, 253, 247, 0.92);
|
||||
padding: 14px;
|
||||
min-height: 112px;
|
||||
}
|
||||
|
||||
.logic-node::after {
|
||||
content: "→";
|
||||
position: absolute;
|
||||
right: -18px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--accent);
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.logic-node:last-child::after,
|
||||
.logic-node.break::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logic-node strong {
|
||||
display: block;
|
||||
color: var(--accent);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.logic-node span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.logic-node.alt {
|
||||
border-color: rgba(31, 111, 104, 0.62);
|
||||
}
|
||||
|
||||
.logic-node.stop {
|
||||
border-color: rgba(157, 44, 47, 0.56);
|
||||
}
|
||||
|
||||
.framework-map {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.25fr 1fr;
|
||||
gap: 16px;
|
||||
min-width: 900px;
|
||||
}
|
||||
|
||||
.framework-column {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.framework-title {
|
||||
font-weight: 800;
|
||||
color: var(--accent-2);
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--line);
|
||||
}
|
||||
|
||||
.framework-box {
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255, 253, 247, 0.9);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.framework-box strong {
|
||||
display: block;
|
||||
color: var(--accent);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.framework-box p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.formula {
|
||||
display: inline-block;
|
||||
margin: 6px 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--soft);
|
||||
font-family: Consolas, "Cascadia Mono", monospace;
|
||||
color: var(--code);
|
||||
}
|
||||
|
||||
.step-list {
|
||||
counter-reset: step;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 18px 0;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
counter-increment: step;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
padding: 14px 16px 14px 54px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-item::before {
|
||||
content: counter(step);
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
top: 14px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--accent-2);
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.step-item strong {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.shell {
|
||||
width: min(100vw - 24px, 1180px);
|
||||
@@ -294,4 +440,8 @@ pre {
|
||||
.flow-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.diagram-panel {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user