# scoring-tiebreak（子模块规范）

## 子模块名称

scoring-tiebreak（评分模型与确定性裁决）

## 目标描述

定义并实现可解释的评分模型（Scoring Model）与固定 tie-break 规则，用于在“合法断点组合”中选出最优布局（layout），并保证跨端完全一致。

本模块不负责搜索（DP/Beam），但负责：

- 每行/每断点的评分项计算顺序（必须固定）
- 情绪短语/受保护短语的匹配口径（连续 token 完全匹配）
- Debug breakdown 的结构与 Top-3 输出排序（按优先级而非 |delta|）
- tieKey 的构造与比较规则（含 breaks 字典序定义）

## 输入/输出定义

### 输入

- `layoutCandidate: { breaks: number[]; lines: Array<{ start: number; end: number; text: string; width: number; tokenCount: number; charCount: number }> }`
- `lang: 'TC' | 'EN'`
- `context: 'APP' | 'WIDGET'`
- `config: { weights: Record<string, number>; idealWidthRatio: { APP: number; WIDGET: number }; ellipsisToken: string; particleWhitelistTC: string[]; eps?: number }`
- `lexicons: { emotionPhrasesTC: string[]; emotionPhrasesEN: string[]; protectedPhrases?: string[]; shiftWordsTC: string[]; shiftWordsEN: string[]; accumWordsTC: string[]; accumWordsEN: string[]; selfWordsTC: string[]; selfWordsEN: string[]; emotionWordsTC?: string[]; emotionWordsEN?: string[] }`

### 输出

- `scoredLayout: { score: number; flags: { emotionSplit?: boolean; overflowed?: boolean; fallback?: boolean }; tieKey: Array<number | string>; scoreBreakdown?: { total: number; terms: Array<{ key: string; delta: number; detail?: any }> } }`

## 验收标准（可验证）

- **评分项顺序固定**：评分必须严格按算法文档 10.1 的顺序计算并记录（不可重排）
- **短语匹配口径正确**：
  - emotionPhrase/protectedPhrases：连续 token 完全匹配（不允许跳 token、不允许模糊）
  - EN 关键词命中遵循 `core-contract` 的“全词等值匹配”
- **冲突裁决一致**：
  - emotionPhrases 与 protectedPhrases 同级强保护
  - 同等条件下更长短语优先（需在惩罚或 tieKey 中体现，且确定性）
- **奖励折减规则一致**：
  - EmotionWord 与 Accum 同时命中时：Accum 奖励按 0.5 倍（先算 EmotionWord 再折减）
- **tie-break 一致**：
  - 规则顺序固定（emotionSplit/overflowed/lastLineWidth/均衡度/理想距离/breaks 字典序）
  - breaks 字典序定义：逐项比较；公共前缀相同则更短数组更小
- **debug Top-3 输出排序一致**：
  - debug 模式关键项按优先级输出（非 |delta|）
- **整数分优先**：尽量使用整数评分；若使用浮点必须定义 EPS，并用 `abs(a-b)<=EPS` 判断近似相等

## 依赖与关联

- **依赖**：`core-contract`（索引、文本重组、EN 命中工具）
- **被依赖**：`search-engine-app`、`search-engine-widget`、`overflow-fallback`、`golden-tests`

