diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index e12ea691..c12a5079 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -57,6 +57,7 @@ | 复杂文字与窄屏 ConPTY 尾部 | [源/控制台/布局验证](../experiments/shell/results/2026-10-26-unicode-workflow/validation.json)、[只读调查入口](../research/README.md) | `730b5cd` 分别核对源字节、真实控制台 `CHAR_INFO`、运输尾部与独立 `pyte==0.8.2` 布局;默认/候选常规两轮及窄屏完整 F 场景通过,原文分解形式保留,pyte 的 NFC 不冒充原文。完整默认 39/40、候选 41/42,#36 和候选早期输入/关闭仍未闭合;#30 的完整 Unicode/IME、真实 DPI、呈现和 WindTerm 等价均未验收 | | R2 复杂标量序列输入(#30 限定切片) | [真实键盘接收验证](../experiments/shell/results/2026-10-26-unicode-keyboard/validation.json)、[场景与复现](../experiments/shell/README.md) | `fe02400` 的固定 ZWJ/keycap 样本经 WM_CHAR、ConPTY 与真实 `ReadConsoleW` 接收端逐字节一致,默认/候选各两轮。只验收输入保真,不证明字素绘制、选择、完整 IME、应用键盘模式或 WindTerm 等价 | | R2 原文保留与 glyph 适配(#30 限定切片) | [真实控制台与绘制验证](../experiments/shell/results/2026-10-26-unicode-renderer/validation.json)、[字体/显示合同](milestones/R2-font-display.md) | `18c36ce` 区分源字节、原生控制台、transport、候选单元格与实际 DWrite glyph;过宽文字在原网格内缩小字号,32 组绘制回归和候选两轮原文/glyph 场景通过。完整 ZWJ/keycap 聚类与宽度由开放的 [#39](https://github.com/JavaZeroo/termforge/issues/39) 阻塞;没有完整 Unicode/IME、实际呈现或 WindTerm 等价结论 | +| R2 流式字素与固定 emoji(#39) | [合同](research/streaming-graphemes.md)、[红/绿及实际场景验证](../experiments/shell/results/2026-10-26-streaming-graphemes/validation.json) | 候选 Unicode 17 分段/宽度、766 个规范边界、64 标量无损分片和单列 resize;固定 ZWJ/keycap whole/byte、实际 ConPTY 80×24→100×30、原子选择与单 glyph 验证。WindTerm 运行时未知;不声明完整 IME、所有脚本/字体、真实跨屏或性能等价 | | 扩展核心能力轨迹 | [能力矩阵说明](../experiments/differential/README.md#扩展能力轨迹25)、[2026-10-25 摘要](../experiments/differential/results/2026-10-25-capabilities-v2/summary.json) | 备用屏/滚动区/查询各 5 次为自研明确未支持,Recovery 5 次与候选一致;resize 5 次为 `resize_difference`,双方实际 2×6,候选重排与自研物理裁剪不同。语义接受未覆盖 | | 真实应用核心轨迹 | [真实应用说明](../experiments/differential/README.md#真实应用核心轨迹25)、[2026-10-25 摘要](../experiments/differential/results/2026-10-25-real-applications/summary.json) | Windows cmd、more.com、Vim 真实 ConPTY 进入/操作/退出和文件结果已核验;分页器与 Vim 回放一致,shell 仅有已裁决 ED2 差异。`presented_done=null`,不是产品 GUI/TUI 验收 | | Vim CI 捕获握手(#37) | [本地三应用摘要](../experiments/differential/results/2026-10-25-real-applications-v4/summary.json)、[握手与负例验证](../experiments/differential/results/2026-10-25-real-applications-v4/validation.json) | 以 `[New]`、`-- INSERT --` 和实际文本作为状态握手,再有序 Escape/`:wq`;保留实际编辑器路径及失败阶段、原始输出和 Job 回收。真实应用 Python 4 项与 differential CTest 通过;本地默认全套 38/39 唯一失败仍为 #36,远程 CI 未通过前 #37 保持开放 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index d601b0b9..e0935914 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -20,7 +20,7 @@ | 统一文字动作 | **已验证(有限)**:共用目标/可用状态,实际终端右键/快捷键、原生 Find/rename 编辑、busy/closing/失效选区反馈及受控 DPI/zoom 保持;[最终系统审计](../experiments/shell/results/2026-10-24-r1-actions-accepted/validation.json)与[逐项对照](research/r1-action-comparison.md)覆盖 Find 实际 Ctrl+C/V、中文重选、默认 Return 和会话隔离 | 复杂 busy/closing 参考行为仍未知,采用明确项目策略;完整流程与八会话有限资源已由 #18 另验收,真实跨显示器及完整 WindTerm 等价未验收 | R1 | | 中文 IME 与键盘 | **已验证(有限)**:基础按键/修饰键/UTF-16 与实际接收字节;[固定 ZWJ/keycap 输入样本](../experiments/shell/results/2026-10-26-unicode-keyboard/validation.json)在默认/候选各两轮精确接收;固定 Microsoft 拼音[五组产品场景](../research/ime/results/2026-10-24-product-ime-contexts/validation.json)覆盖两区域组合/提交/取消、真实候选、输出/resize/zoom/受控 DPI、隐藏/关闭,以及历史阅读、Find/rename 双 Escape 与回收 | 限定 Windows Server 2025 固定拼音及测试范围;同一八会话组合已由 #18 另验收,真实跨屏、所有输入法、复杂字素绘制/选择及应用模式归 R2/后续矩阵 | R1 / R2 | | VT 核心 | **部分**:自研核心支持基础光标/擦除(含 ECH)、CR/LF/BS/TAB、有限 SGR、有界未知序列恢复;[ADR-0003](adr/0003-core-reference-and-reuse.md)已决定 R2 新能力复用候选 Term/Processor;`0.26.1-dev+termforge.3` [固定补丁](../experiments/candidates/alacritty/PROVENANCE.md)纳入上游资源修复、ED1/ED2、稳定行元数据和每缓冲区滚动区域,[共同轨迹](../experiments/differential/results/2026-10-26-candidate-common-v6/summary.json)为 95 次一致;[C++ 适配](../experiments/core/CANDIDATE.md)已在显式候选构建接入 ShellWindow,真实 Vim/resize/光标回复经 ConPTY 和有界 InputQueue 验证;备用屏切换、局部滚动和[公开源 margin 修复](../experiments/shell/results/2026-10-26-margin-state/validation.json)有独立验证 | 默认构建保留自研回退;完整 R1 候选回归与 #36/#38 未关闭;ECH 的候选 pending-wrap 差异见[限定合同](research/more-capture-ech.md),插删、完整模式和最终呈现仍未验收。#27 的 origin/wrap 和完整 resize 交叉未完成;#41 的真实 ConPTY 主屏 margin 恢复差异未解决,不能把模型切片当作产品 margin 通过;旧平台支持缺口不因路线决定消失 | R2 / R4 | -| Unicode 与文字显示 | **部分**:UTF-8、固定 Unicode 15.0 宽度策略、有限组合标记、16 色/粗体、Consolas 逐格绘制;实际 DWrite glyph run 已检查 Consolas、中文 fallback、固定 Segoe UI Emoji/Symbol 与缺字负例;[候选原文保留与 glyph 限定验证](../experiments/shell/results/2026-10-26-unicode-renderer/validation.json)覆盖真实 ConPTY 样本及超宽文字适配 | [#39](https://github.com/JavaZeroo/termforge/issues/39) 的完整 ZWJ/keycap 聚类与宽度合同、完整字素/整形、通用 fallback、真彩色/更多属性、Powerline;WindTerm 的 Unicode 13 声明与本项目策略差异需明确裁决 | R2 / R4 | +| Unicode 与文字显示 | **部分**:默认自研保留 Unicode 15;候选按[Unicode 17 流式字素合同](research/streaming-graphemes.md)实现固定 ZWJ/keycap 整格、光标、SGR/查询、原子选择复制、resize 与有界无损分片;真实 ConPTY/DWrite 核对固定样本的单 glyph、字体与网格,保留缺字/分裂负例 | 完整脚本/整形、通用 fallback、所有原生重放、真彩色/更多属性、Powerline;完整 IME/真实跨屏与 R2 父项仍独立验收,WindTerm 字素运行时和性能等价未知 | R2 / R4 | | resize 与 reflow | **已验证(有限)**:默认回退按 ADR-0005 物理行裁剪;候选在固定 ConPTY 重放、逐字节及连续同步中保持唯一行身份、Find 和选区,按 ID 与完整行键合并有界历史,真实淘汰不恢复,显式清空不复活;[合同与验证](../experiments/core/results/2026-10-26-resize-replay/validation.json)。真实文字动作、八会话及 Vim 60×12 resize 有独立场景 | 仅已识别的 resize 事务;候选重排不同于默认裁剪,不等于完整 reflow、备用屏/滚动区全语义或 WindTerm 等价。缩窄默认不可恢复;真实 DPI、呈现、性能及 #36/#38 独立回归未闭合 | R2 | | 本地 shell 选择 | **部分**:`ShellLaunch` 分离可执行文件、参数、工作目录及 shell 命令差异;cmd、Windows PowerShell 5.1、PowerShell 7、显式 Ubuntu WSL 各有[两轮真实启动/目录/尺寸/正常退出证据](../experiments/shell/results/2026-10-24-r2-shell-lifecycle/validation.json),无效程序启动错误可见且可关闭后恢复 cmd | 仅声明上述固定入口和隔离目录;任意 profile/参数、通用分屏/排序、全部运行时错误提示及管理员 shell 未完成 | R2;权限场景 R4 | | 常用 TUI | **部分**:显式候选构建已有两轮真实 Vim 进入、编辑、resize、退出写盘、主屏恢复及光标回复,见[候选产品证据](../experiments/core/CANDIDATE.md);默认构建保留自研回退,`presented_done=null` | #28 的更多应用、完整输入/输出模式和最终产品验收未完成;headless 核心比较不替代真实场景,当前自有 DIB 不代表实际上屏 | R2 | diff --git a/docs/PROVENANCE.md b/docs/PROVENANCE.md index 658eb0f7..150127a5 100644 --- a/docs/PROVENANCE.md +++ b/docs/PROVENANCE.md @@ -18,4 +18,8 @@ - `experiments/core/unicode_width.hpp`:通过原创生成器从 Python 3.12.10 `unicodedata` 所带 Unicode 15.0.0 属性数据生成,含项目自己的宽度/拒绝策略。未复制 CPython 实现源码。Unicode 数据保留[官方许可](../experiments/core/UNICODE-LICENSE.txt)与归属,来源为 [Unicode](https://www.unicode.org/);正常构建使用固定生成文件。此数据不归项目 MIT 许可覆盖。 -- 外部核心候选当前使用 `alacritty_terminal 0.26.1-dev+termforge.3`(Apache-2.0),固定上游 commit `542147067dd016fc5f49120ef0ba605a98a4e3a7` 的 manifest/changelog/许可证与 `src/`,并保留 ED、稳定行身份/淘汰、安全 swap 和每缓冲区滚动区域补丁;精确传递依赖及校验值由 `Cargo.lock` 固定。未复制上游 46 MiB 行为录制,候选不进入当前产品包;默认 CMake 不依赖 Rust,显式适配器构建使用 staticlib。原始 0.26.0 身份、当前来源、许可证和补丁清单见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 +- 外部核心候选当前使用 `alacritty_terminal 0.26.1-dev+termforge.4`(Apache-2.0),固定上游 commit `542147067dd016fc5f49120ef0ba605a98a4e3a7` 的 manifest/changelog/许可证与 `src/`,并保留 ED、稳定行身份/淘汰、安全 swap、每缓冲区滚动区域和流式字素补丁;精确传递依赖及校验值由 `Cargo.lock` 固定。未复制上游 46 MiB 行为录制,候选不进入当前产品包;默认 CMake 不依赖 Rust,显式适配器构建使用 staticlib。原始 0.26.0 身份、当前来源、许可证和补丁清单见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 + +- 候选新增精确锁定 `unicode-segmentation 1.13.3`(Unicode 17,MIT OR Apache-2.0),官方来源为 [unicode-rs](https://github.com/unicode-rs/unicode-segmentation) 与 [crates.io 固定版本](https://crates.io/crates/unicode-segmentation/1.13.3)。保留原依赖许可证;与既有 `unicode-width 0.2.2` 同使用 Unicode 17,默认自研核心仍使用 Unicode 15。版本、包哈希、MSRV、依赖成本和修改边界见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 + +- 候选规范测试数据 `GraphemeBreakTest-17.0.0.txt` 从 [Unicode 17 官方数据](https://www.unicode.org/Public/17.0.0/ucd/auxiliary/GraphemeBreakTest.txt)原样取得(766 例、126570 字节,SHA-256 `e2d134d2c52919bace503ebb6a551c1855fe1a1faec18478c78fff254a1793ec`),随附[Unicode License V3](../experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt),不由项目 MIT 许可证覆盖。它验证分段 helper 的规范边界,不代表完整 Term/VT 状态正确性;原始执行字节身份与 Git blob 换行身份分列保存在[验证记录](../experiments/shell/results/2026-10-26-streaming-graphemes/validation.json)。 diff --git a/docs/TRACKING.md b/docs/TRACKING.md index e668cea0..c0465161 100644 --- a/docs/TRACKING.md +++ b/docs/TRACKING.md @@ -57,6 +57,10 @@ R1已按[限定合同](milestones/R1-local-text.md)验收,[完整组合证据] | Windows CI 回归 | [#40](https://github.com/JavaZeroo/termforge/issues/40) | 复核 `ime_inventory` 原 10 秒超时根因,保留 AST/最终进度与 OwnedJob 回收证据 | 无;作为 #38 的子项阻塞 #38,不能用本机或单次 CI 通过冒充修复 | | ConPTY margin 差异 | [#41](https://github.com/JavaZeroo/termforge/issues/41) | 裁决宿主/传输的每缓冲区 margin,保留原场景和独立实现证据 | #26;作为 #27 的子项阻塞 #27,不能以模型切片或差异观察代替产品验收 | | 显式官方 ConPTY 实验后端 | [#47](https://github.com/JavaZeroo/termforge/issues/47) | 固定同包 DLL/EXE 来源与身份,通过同一 Backend 验证实际 Session、GUI 和回收;不改变默认系统后端 | #41 的实现子项;仍需处理原生 API 与末端语义、resize、粘贴和完整工作流差异,不代表 #34 产品决定或 R2 出口 | +| 实验后端连续 resize | [#51](https://github.com/JavaZeroo/termforge/issues/51) | 定位 stage43 的残留 OW 与 Unicode 重放差异,保持精确末端及子进程尺寸合同 | #47 的子项与阻塞;独立诊断可进行,不以裁尾通过 | +| 实验后端 Vim | [#52](https://github.com/JavaZeroo/termforge/issues/52) | 定位最终 Console-width 下的 GUI stage59 与 native new-buffer 失败;早期 flags0 单独保留 | #47 的子项与阻塞;实际 resize、写盘、退出断言保留 | +| 实验后端粘贴取消 | [#53](https://github.com/JavaZeroo/termforge/issues/53) | 原 3MB/768 checkpoint/16KiB 门槛下建立 sender 栅栏,验证未完成期取消与关闭 | #47 的子项与阻塞;不能把提前 completed 当作 cancelled | +| 流式字素性能成本 | [#54](https://github.com/JavaZeroo/termforge/issues/54) | 以原 12 个诊断样本为起点冻结受控负载/端点/预算,定位 project/interactive 吞吐成本 | #34 的子项与阻塞;现有正确性不豁免性能,未建立 WindTerm 等价 | | More 尾部 ECH 与捕获时序 | [#42](https://github.com/JavaZeroo/termforge/issues/42) | [限定合同](research/more-capture-ech.md):默认 ECH、完整 More 提示、q/退出/真实 EOF;候选 pending-wrap 差异保留给 #27 | 无;#38 的子项,与 margin 修复分开;完整原始输入和旧失败保留,不静默降级为 match | | controller 正常退出观测缺口 | [#44](https://github.com/JavaZeroo/termforge/issues/44) | 记录 wait_exit 与 close 前 cmd 状态,定位真实 child→cmd/close 转换 | 无;#38 的子项与出口阻塞;后续同合同重跑成功不覆盖原失败 | | 候选 GUI 合法空标题 | [#45](https://github.com/JavaZeroo/termforge/issues/45) | 修正空 UTF-8 转换,使用真实 console 标题事件验证 GUI 存活,并保留非法非空输入拒绝 | 无;#38 的子项;新最小复现与旧 normal-1 未定位异常分列 | diff --git a/docs/milestones/R2-font-display.md b/docs/milestones/R2-font-display.md index 74aa490f..fff49fc9 100644 --- a/docs/milestones/R2-font-display.md +++ b/docs/milestones/R2-font-display.md @@ -6,7 +6,7 @@ - 工作区 UI 使用独立获取的 Roboto Mono Regular,9pt,SIL OFL 1.1;固定上游提交、原始字节和 SHA-256 见[字体来源](../../assets/fonts/README.md)。通过 `AddFontResourceExW(..., FR_PRIVATE, ...)` 仅对进程加载,缺失、注册失败或实际 `GetTextFaceW` 不是 Roboto Mono 时明确失败,不静默替换。`renderer_metrics` 独立复核实际 GDI face、em 高度与 `TEXTMETRIC.tmHeight`;96/120/144/192 DPI 的 em 为 12/15/18/24,行高为 16/20/24/32,不能把二者混为“字符高度”。 - 终端绘制当前使用系统 Consolas,DirectWrite `CreateTextFormat` 与 GDI `CreateFontW` 是独立路径。系统字体不分发;Microsoft 系统字体可用性、实际 fallback 和缺字必须由实际绘制路径证明。 -- DirectWrite `GlyphAudit` 读取实际被绘制的 `IDWriteTextLayout`,记录 glyph run 的字体身份、glyph 数和 glyph index 0。控件字体、请求的 family 和最终 glyph 字体不能混为一谈。 +- DirectWrite `GlyphAudit` 读取实际被绘制的 `IDWriteTextLayout`,记录 glyph run 的字体身份、glyph 数和 glyph index 0。控件字体、请求的 family 和最终 glyph 字体不能混为一谈。固定 ZWJ/合格 keycap 按[流式字素合同](../research/streaming-graphemes.md)另要求整段为 1 个 Segoe UI Emoji glyph;keycap 整段指定该系统字体,避免数字与方框被两个不同 fallback 字体拆开。 - 不复制 WindTerm 字体、Nerd Font 或主题资源。新增可携带字体或 fallback 资产必须另行审核许可。 ## 固定网格设计决定 diff --git a/docs/research/streaming-graphemes.md b/docs/research/streaming-graphemes.md new file mode 100644 index 00000000..67541cd8 --- /dev/null +++ b/docs/research/streaming-graphemes.md @@ -0,0 +1,56 @@ +# R2 流式字素、宽度与固定 emoji 合同(#39) + +本合同属于显式候选 `Term/Processor` 路线,不扩展默认自研解析器。固定首批样本为 `e\u0301`、`🙂`、`👩‍💻`、`1️⃣`;要求原始标量、不经 NFC 的原文、单元格、光标、选择复制和实际绘制一致。WindTerm 2.7.0 的对应运行时行为仍未知,不能由静态 QString 测量路径推断。 + +## 依据与路线 + +- [UAX #29 revision 47 / Unicode 17](https://www.unicode.org/reports/tr29/tr29-47.html)定义扩展字素边界。固定官方 `GraphemeBreakTest-17.0.0.txt` 的 766 个用例验证流式**分段 helper**边界(不冒充完整 Term/VT 状态正确性),保留 [Unicode 许可证](../../experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt)。分段不改变编码,不把 feed 结束当作文本结束。 +- [UTS #51](https://www.unicode.org/reports/tr51/)定义 emoji ZWJ/keycap 等序列。[UAX #11](https://www.unicode.org/reports/tr11/tr11-44.html)明确终端需要额外宽度策略,不能把“所有字素两列”称为 Unicode 强制要求。 +- 本项目选择 `unicode-width 0.2.2` 的非 CJK 字符串宽度策略,ambiguous 为窄;其[规则](https://docs.rs/unicode-width/0.2.2/unicode_width/)支持合格 emoji 序列宽 2。现有独立 `wcwidth 0.2.13` / Unicode 15 的 `wcswidth` 对固定 ZWJ/keycap 也得到 2;它不充当完整字素或终端状态真值。 +- [kitty 官方文字尺寸说明](https://github.com/kovidgoyal/kitty/blob/master/docs/text-sizing-protocol.rst)说明 VS15 缩窄保留已经发生的 wrap。这只作为流式策略的独立旁证,不是 WindTerm 比较或 kitty 全量兼容声明。 + +选择在候选 `Term` 内保存**一个立即可见、可修订的尾字素**。延迟最后一个字素会让单字输入等待未来字节;在 C ABI 前另写 UTF-8/ANSI 解析器或在 Cell 后拼接会重复终端语义。两条路线均不采用。宽度变更继续使用候选的写格、换行和滚动路径;不保存整屏/整行回滚副本,不撤销已发生的历史滚动。 + +## 状态与动作合同 + +| 事件 | 行为 | +|---|---| +| UTF-8 分块、feed 结束、snapshot、普通选择观察 | 不结束尾字素;前缀立即可见,光标反映当前前缀宽度 | +| 后续标量 | 用 UAX #29 判断是否延续;完整序列由一个 lead Cell 保存,wide spacer 不复制第二份文本;末列 leading spacer 是明确可绘制的换行填充,选择投影跳过它但保留真实写入空格 | +| SGR、超链接、颜色、窗口标题、只读查询、bell | 保持尾字素;首标量的样式拥有整个字素,修改后的模板供下一字素使用 | +| CPR/其他查询 | 立即观察当前前缀的真实光标;不会等未来 VS16,也不会预知最终宽度 | +| 光标移动、清除/插删、滚动、屏幕/模式/字符集变更、resize、RIS、外部可变 grid 访问 | 结束尾字素;控制之后不会重新打开先前的 ZWJ/RI 分段上下文;零宽 Prepend 则建立向前延续状态,不能在后继 ASCII 到来时丢失 | +| 控制边界后的孤立零宽标量 | 保留既有候选的前格附着策略;这不重新打开复杂字素上下文。超过格预算时进入无损分片 | + +这些终端事件边界、样式归属和剪裁规则是 **TermForge 策略**,并非 UAX #29 对 VT 字节流的直接规定。未知或被解析器忽略、没有产生 Handler 动作的控制序列不增加一个伪造动作。 + +## 宽度变化、边缘和有界输入 + +- 固定首批样本宽度依次为 1、2、2、2。`👩‍💻` 与 `1️⃣` 必须各为一个 lead、一个 spacer,后续字符不得占错列;选择端点落在 spacer 内时向外展开,复制完整原文。 +- 从宽 1 增长到 2 时补占一格。若在最后一列且开启 autowrap,旧前缀成为 leading spacer,整个字素换至下一行;原生滚动规则继续执行。已显示前缀造成的覆盖不会反事实恢复。 +- 从宽 2 缩至 1 时清理 spacer,并调整光标;已经发生的换行、旧内容覆盖和历史滚动保留。INSERT 模式按宽度差移动尾部,不重复整字素插入;裁剪边缘不得留下孤立 wide lead;从 wide trailer 开始插入时先原子清理命中的整对格,再移动尾部,避免将旧 spacer 与 lead 拆离。 +- 关闭 autowrap 后最后一列、或整个窗口只有一列时,保留完整字素文本,以宽 1 的**明确 clipped footprint**显示。候选内部、C ABI 和 C++ 拥有值均保留 clipped 标志。超出当前 1/2 列模型的宽度同样明确标剪裁,不吞标量。已有 clipped 格随窗口增宽不主动反向重排;后续重绘/覆盖遵从新的输入状态。 +- 缩到一列时,reflow 必须把完整 wide lead 改为 clipped 格,只移除其专用 spacer;不能反复把一个永远装不下的 wide 格推到下一行。主/备用屏均覆盖这一边界。 +- 每片最多 **64 标量、256 UTF-8 字节**。超长组合输入无损 spill 到后续片,标明 `grapheme_fragment`,以饱和 `u64 grapheme_overflows` 汇总;无逐片无限事件、静默截字或杀死会话。可选 serde 入口同样逐个标量检查 63 字符后缀上限,拒绝外部超长格;旧无 fragment 字段的数据按 false 读取。分片后的原文仍可精确选择复制,但不宣称超限输入仍按一个完整字素布局。下一边界探测使用固定 260 字节栈缓冲,持久文本不会为探测第 65 个标量而无界增长。 + +## 实际绘制 + +候选产品使用 DWrite,对完整 Cell 文本整形。固定 ZWJ/keycap 必须实际产生 **1 个非缺失 glyph,字体为系统 Segoe UI Emoji**,且 advance 在分配网格内。仅检查 `missing==0` 不足:旧 keycap 会由 Consolas 数字和 Segoe UI Symbol 方框两个 glyph 组成。完整合格 keycap 集合 `[0-9#*] VS16 U+20E3` 因此作为一段指定 Segoe UI Emoji;不改变 ASCII 的 Consolas 字体和中文 fallback 合同。默认 GDI 路径不因此获得复杂字素绘制声明。 + +真实 owned ConPTY 场景先捕获 80×24,再通过真实 resize 和独立 `GetConsoleScreenBufferInfo` 客户端确认 100×30;两阶段均经实际产品拖选/复制、实际 DWrite glyph 审计与 retained/full frame 比较。原生 Windows 控制台的非 BMP 替换投影继续与 transport 原文分列。固定增宽场景通过不代表所有缩放、所有原生重放或 WindTerm 运行时等价。 + +## 依赖与复验 + +`unicode-segmentation = 1.13.3`,Unicode 17,MIT OR Apache-2.0,MSRV 1.85.0,注册表 SHA-256 `c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8`;无运行时依赖,开发依赖不进入本项目锁定图。与已锁定 `unicode-width = 0.2.2` 同为 Unicode 17。当前图为 59 个注册表包、两个 path crate;最高声明 MSRV 仍为 1.85,实际工具链为 1.92.0。默认自研 Unicode 15 策略不变。候选补丁版本为 `0.26.1-dev+termforge.4`,来源见[候选台账](../../experiments/candidates/alacritty/PROVENANCE.md)。 + +```powershell +cargo test --manifest-path experiments/candidates/alacritty/Cargo.toml --locked --workspace +python -m experiments.candidates.alacritty.grapheme_checks --out local/graphemes-new +cmake -S . -B build/candidate-r39 -A x64 -DTERMFORGE_BUILD_CANDIDATE_ADAPTER=ON +cmake --build build/candidate-r39 --config Release +ctest --test-dir build/candidate-r39 -C Release --output-on-failure +``` + +性能仍有成本:同宿主交错的每路线 3 对、至少 1 秒 CPU 诊断中,project 吞吐中位数从 25.86 降至 21.66 MiB/s(约 16%),interactive 从 13.84 降至 12.54 MiB/s(约 9%)。原始 12 份样本和二进制身份全部保留;共享宿主、allocator 仍被包装的 observe=0,以及缺少呈现/传输/输入时延端点使它只能说明当前成本,不能作为性能验收通过或 WindTerm 等价。[#54](https://github.com/JavaZeroo/termforge/issues/54) 负责受控定位与预算,并阻塞 #34 的性能裁决;不能由本项正确性证据豁免。 + +固定红/绿、规范数据哈希、源/二进制身份和实际场景结果见[验证记录](../../experiments/shell/results/2026-10-26-streaming-graphemes/validation.json)。本项不代表完整 IME、所有字体/脚本、真实跨屏、呈现完成或 WindTerm 性能等价;相关 R2 父项保持其独立出口。 diff --git a/experiments/candidates/alacritty/Cargo.lock b/experiments/candidates/alacritty/Cargo.lock index 683f9858..6501189a 100644 --- a/experiments/candidates/alacritty/Cargo.lock +++ b/experiments/candidates/alacritty/Cargo.lock @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.26.1-dev+termforge.3" +version = "0.26.1-dev+termforge.4" dependencies = [ "arrayvec", "base64", @@ -31,6 +31,7 @@ dependencies = [ "serde", "serde_json", "signal-hook", + "unicode-segmentation", "unicode-width", "vte", "windows-sys 0.59.0", @@ -401,6 +402,12 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + [[package]] name = "unicode-width" version = "0.2.2" diff --git a/experiments/candidates/alacritty/PROVENANCE.md b/experiments/candidates/alacritty/PROVENANCE.md index 765a0a4e..3470237b 100644 --- a/experiments/candidates/alacritty/PROVENANCE.md +++ b/experiments/candidates/alacritty/PROVENANCE.md @@ -1,6 +1,6 @@ # 固定候选来源与本地补丁 -当前适配版本为 `alacritty_terminal 0.26.1-dev+termforge.3`,不是 crates.io 原始 `0.26.0`。 +当前适配版本为 `alacritty_terminal 0.26.1-dev+termforge.4`,不是 crates.io 原始 `0.26.0`。 ## 上游身份 @@ -43,6 +43,16 @@ 原始上游与本目录的差异可用固定 commit 对比复核;差分/真实应用/计量 manifest 另记录每个 vendor 文件的 SHA-256。后续补丁必须更新版本、测试和本文件,不得覆盖来源或继续称为未修改上游版本。 +## termforge.4 补丁 + +1. `src/term/grapheme.rs` 与 Handler 边界:立即可见的流式扩展字素;首样式、透明查询、mutation seal、宽度差插入/剪裁、64 标量/256 字节无损分片与有界诊断;不复制整屏或整行。 +2. `src/term/cell.rs`:用有界后缀保存完整聚类,去除旧 9 个零宽字符静默丢弃;保留 clipped / fragment 身份。原始第三方版权和 Apache-2.0 不变。 +3. `src/grid/resize.rs`:一列目标不再无穷重排 wide 格,保留完整文本为明确 clipped footprint。普通多列 reflow 策略不变。 +4. 精确新增 `unicode-segmentation = 1.13.3`(Unicode 17,MIT OR Apache-2.0,MSRV 1.85.0,无运行时依赖),精确固定既有 `unicode-width = 0.2.2`(Unicode 17)。当前 59 注册表包、两个 path crate;锁定图最高声明 MSRV 1.85。 +5. 本地规范测试资料 `tests/data/GraphemeBreakTest-17.0.0.txt` 来自 Unicode 17 官方数据,SHA-256 `e2d134d2c52919bace503ebb6a551c1855fe1a1faec18478c78fff254a1793ec`,766 个用例;附独立 `UNICODE-LICENSE.txt`。这不是声称复制了被排除的 Alacritty 全部录制。 + +完整控制/宽度/资源和字体策略见[流式字素合同](../../../docs/research/streaming-graphemes.md)。固定 WindTerm 的运行时字素/宽度仍未知。 + ## 历史基线 #24–#26 的原始候选为 crates.io `0.26.0`,crate SHA-256 `bda177466b9524d59f1b12f0dd30b68696788e9992a7e959021c4a0ed96fcf59`,对应上游 commit `94e7c8874e526b1e67b349d9ba30ddf81669119e`。当时的 90 match + 5 reference_difference、project/interactive 计量和平台/维护审查保持原批次身份,不改写为 termforge.1/2 结果。 diff --git a/experiments/candidates/alacritty/README.md b/experiments/candidates/alacritty/README.md index 00e63a5a..21fe2031 100644 --- a/experiments/candidates/alacritty/README.md +++ b/experiments/candidates/alacritty/README.md @@ -6,10 +6,10 @@ | 项目 | 当前固定值 | 来源 | |---|---|---| -| `alacritty_terminal` | `0.26.1-dev+termforge.3`,Apache-2.0 | [上游 commit `5421470`](https://github.com/alacritty/alacritty/commit/542147067dd016fc5f49120ef0ba605a98a4e3a7)与[来源/补丁台账](PROVENANCE.md) | -| 本地补丁 | ED1/ED2、稳定 Row ID/换行元数据/淘汰、安全完整 Row swap、每缓冲区滚动区域 | `vendor/alacritty_terminal/src/`;所有文件哈希进入结果 manifest | +| `alacritty_terminal` | `0.26.1-dev+termforge.4`,Apache-2.0 | [上游 commit `5421470`](https://github.com/alacritty/alacritty/commit/542147067dd016fc5f49120ef0ba605a98a4e3a7)与[来源/补丁台账](PROVENANCE.md) | +| 本地补丁 | ED1/ED2、稳定 Row ID/换行元数据/淘汰、安全完整 Row swap、每缓冲区滚动区域、流式字素与明确剪裁 | `vendor/alacritty_terminal/src/`;所有文件哈希进入结果 manifest | | 工具链 | Rust/cargo 1.92.0 | 本机与候选 CI 显式选择;当前锁定图最高声明 Rust 1.85 | -| 传递依赖 | 58 个注册表包 + 根适配器与固定 vendor 两个 path crate,共 60 个包 | `Cargo.lock` 与 `cargo metadata --locked --format-version 1` | +| 传递依赖 | 59 个注册表包 + 根适配器与固定 vendor 两个 path crate,共 61 个包 | `Cargo.lock` 与 `cargo metadata --locked --format-version 1` | 根 `Cargo.toml` 使用固定 path 依赖并关闭默认 serde feature。上游只复制 manifest、changelog、许可证和约 426 KiB 的 `src/`,不复制约 46 MiB 行为录制;内联测试保留。补丁不冒充 crates.io 原始 0.26.0,也不把第三方代码改标 MIT。候选二进制当前只用于本地/CI 实验,不进入 Alpha 或产品发行包;以后分发须完成全部依赖许可与通知审计。 @@ -32,7 +32,7 @@ cmake --build build/candidate-adapter --config Release ctest --test-dir build/candidate-adapter -C Release --output-on-failure ``` -Linux/macOS 使用相同 Cargo 命令和相应 CMake 配置。默认 trace 为 `target/release/termforge-alacritty-trace[.exe]`;可用 `TERMFORGE_ALACRITTY_TRACE` 显式选择另一构建,缺失文件会失败。候选 workflow 在 Windows/Ubuntu 构建并运行固定 crate 的 141 个单元测试、1 个 doc test 和 C++ 适配器。 +Linux/macOS 使用相同 Cargo 命令和相应 CMake 配置。默认 trace 为 `target/release/termforge-alacritty-trace[.exe]`;可用 `TERMFORGE_ALACRITTY_TRACE` 显式选择另一构建,缺失文件会失败。候选 workflow 在 Windows/Ubuntu 构建并运行固定 crate 的单元测试与 doc test 和 C++ 适配器。 ## Trace、C ABI 与检查内容 @@ -44,6 +44,8 @@ Linux/macOS 使用相同 Cargo 命令和相应 CMake 配置。默认 trace 为 ` ## 当前结果与历史边界 +- termforge.4 增加[流式字素合同](../../../docs/research/streaming-graphemes.md):Unicode 17 分段/宽度、固定 ZWJ/keycap 的整格/光标/选择、bounded lossless spill、1列 resize 和实际 DWrite keycap 整形。默认自研仍为固定 Unicode 15;新结果不改写以下历史批次。 + - termforge.3 的[共同矩阵](../../differential/results/2026-10-26-candidate-common-v6/summary.json)为 95 次一致;[扩展能力](../../differential/results/2026-10-26-capabilities-v5/summary.json)分类不变;[fuzz](../../differential/results/2026-10-26-fuzz-v4/summary.json)为 2 次固定已知差异、6 次一致;[真实应用](../../differential/results/2026-10-26-real-applications-v6/summary.json)固定 3 次一致。[margin 验证](../../shell/results/2026-10-26-margin-state/validation.json)区分公开源 VT 的恢复与真实 ConPTY 的宿主限制;#41 的宿主 margin 和 #42 的 More 尾部 ECH/捕获时序仍未闭合,不是完整产品 TUI。 - termforge.2 的[共同矩阵](../../differential/results/2026-10-25-candidate-common-v5/summary.json)为 95 次、1011 检查点全部一致;旧 ED 差异不再以 `reference_difference` 放行。[扩展能力](../../differential/results/2026-10-25-capabilities-v4/summary.json)保持 15 次自研未支持、5 次一致、5 次 resize 差异;[真实应用](../../differential/results/2026-10-25-real-applications-v3/summary.json)3 次全部一致;[fuzz](../../differential/results/2026-10-25-fuzz-v3/summary.json)为 2 次固定已知差异、6 次一致。均不是完整语义接受或产品 TUI。 - C++ 适配器验证分块一致性、真实回复、备用屏/滚动区、ED、Unicode、resize、ID/版本、选择、搜索和 HistoryViewport。显式候选 `ShellWindow` 已运行两轮真实 Vim、60×12 resize、主屏恢复及经有界 InputQueue 的真实光标回复,并修复普通空格背景残留;证据见 [validation](../../core/results/2026-10-25-candidate-adapter/validation.json)。默认构建保留自研回退;#29 的限定 resize/历史切片已有验证,#36/#38 的真实回归、#41 的宿主 margin、完整 R1 回归和实际呈现仍未关闭。 diff --git a/experiments/candidates/alacritty/grapheme_checks.py b/experiments/candidates/alacritty/grapheme_checks.py new file mode 100644 index 00000000..6b1c7d7e --- /dev/null +++ b/experiments/candidates/alacritty/grapheme_checks.py @@ -0,0 +1,48 @@ +"""Fixed streaming-grapheme contract through the real candidate Term/Processor.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from experiments.candidates.alacritty.run import run_trace, sha, trace_binary, write_json + + +FIXED_SAMPLES = (("zwj", "\U0001f469\u200d\U0001f4bb", 2), + ("keycap", "1\ufe0f\u20e3", 2)) + + +def run_fixed_samples(binary: Path, output: Path): + output.mkdir(parents=True, exist_ok=False) + cases = [] + for name, text, width in FIXED_SAMPLES: + payload = text.encode("utf-8") + for plan, sizes in (("whole", [len(payload)]), ("byte", [1] * len(payload))): + report, records = run_trace(binary, payload, 3, 8, 2, sizes, output / f"{name}-{plan}") + final = records[-1]["screen"] if records else None + occupied = ([[cell["text"], cell["width"]] for cell in final["cells"][0] if cell["width"]] + if final else None) + passed = (report["passed"] and occupied == [[text, width]] and + final["row"] == 0 and final["column"] == width and final["wrap_pending"] is False) + cases.append(dict(case=name, plan=plan, expected_text=text, expected_width=width, + occupied=occupied, row=final["row"] if final else None, + column=final["column"] if final else None, passed=passed, trace=report)) + result = dict(schema="termforge-fixed-streaming-graphemes-v1", trace_sha256=sha(binary), + passed=all(case["passed"] for case in cases), cases=cases, + windterm_runtime_executed=False, performance_measured=False) + write_json(output / "summary.json", result) + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--trace", type=Path) + parser.add_argument("--out", type=Path, required=True) + arguments = parser.parse_args() + result = run_fixed_samples(trace_binary(arguments.trace), arguments.out.resolve()) + print(json.dumps(result, ensure_ascii=True, indent=2)) + return 0 if result["passed"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/candidates/alacritty/include/termforge_candidate.h b/experiments/candidates/alacritty/include/termforge_candidate.h index 4ee1d9ae..9ab501e9 100644 --- a/experiments/candidates/alacritty/include/termforge_candidate.h +++ b/experiments/candidates/alacritty/include/termforge_candidate.h @@ -15,6 +15,7 @@ typedef struct TfTerminalMeta { int32_t fg, bg; bool bold; uint64_t mode_bits; + uint64_t grapheme_overflows; } TfTerminalMeta; typedef struct TfRowMeta { @@ -29,6 +30,7 @@ typedef struct TfCellMeta { size_t marks; bool written; size_t text_len; + bool clipped, grapheme_fragment, leading_spacer; } TfCellMeta; void* tf_terminal_new(size_t rows, size_t columns, size_t history); diff --git a/experiments/candidates/alacritty/run.py b/experiments/candidates/alacritty/run.py index 1e42ab37..7b7c609e 100644 --- a/experiments/candidates/alacritty/run.py +++ b/experiments/candidates/alacritty/run.py @@ -11,7 +11,7 @@ ROOT = Path(__file__).resolve().parents[3] HERE = Path(__file__).resolve().parent -VERSION = "0.26.1-dev+termforge.3" +VERSION = "0.26.1-dev+termforge.4" UPSTREAM_COMMIT = "542147067dd016fc5f49120ef0ba605a98a4e3a7" BASE_CRATE_SHA256 = "bda177466b9524d59f1b12f0dd30b68696788e9992a7e959021c4a0ed96fcf59" diff --git a/experiments/candidates/alacritty/src/ffi.rs b/experiments/candidates/alacritty/src/ffi.rs index 5fde3ef1..4f56d33f 100644 --- a/experiments/candidates/alacritty/src/ffi.rs +++ b/experiments/candidates/alacritty/src/ffi.rs @@ -33,6 +33,7 @@ pub struct TerminalMeta { bg: i32, bold: bool, mode_bits: u64, + grapheme_overflows: u64, } #[repr(C)] @@ -52,6 +53,9 @@ pub struct CellMeta { marks: usize, written: bool, text_len: usize, + clipped: bool, + grapheme_fragment: bool, + leading_spacer: bool, } fn color(value: Color) -> i32 { @@ -204,6 +208,7 @@ pub unsafe extern "C" fn tf_terminal_meta(value: *mut c_void, output: *mut Termi bg: color(cursor.template.bg), bold: cursor.template.flags.contains(Flags::BOLD), mode_bits: value.term.mode().bits() as u64, + grapheme_overflows: value.term.grapheme_overflows(), }; unsafe { *output = result }; 0 @@ -282,6 +287,9 @@ pub unsafe extern "C" fn tf_terminal_cell_meta( written: !cell.is_empty() || !text.is_empty() || flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER), text_len: text.len(), + clipped: flags.contains(Flags::CLIPPED_GRAPHEME), + grapheme_fragment: cell.grapheme_fragment(), + leading_spacer: flags.contains(Flags::LEADING_WIDE_CHAR_SPACER), }; unsafe { *output = result }; 0 diff --git a/experiments/candidates/alacritty/src/main.rs b/experiments/candidates/alacritty/src/main.rs index 34191639..29e5dbb7 100644 --- a/experiments/candidates/alacritty/src/main.rs +++ b/experiments/candidates/alacritty/src/main.rs @@ -122,6 +122,7 @@ pub(crate) fn snapshot(term: &mut Term, listener: &Listener, offset: u "current_style": current_style, }, "mode_bits": term.mode().bits(), + "grapheme_overflows": term.grapheme_overflows(), "replies": replies, }) } @@ -138,7 +139,7 @@ fn run() -> Result<(), String> { .ok_or("invalid resize option")?; let rows = rows.parse::().map_err(|_| "invalid resize rows")?; let columns = columns.parse::().map_err(|_| "invalid resize columns")?; - if rows == 0 || columns < 2 { + if rows == 0 || columns == 0 { return Err("resize dimensions exceed the trace budget".into()); } Some((rows, columns)) @@ -148,7 +149,7 @@ fn run() -> Result<(), String> { let rows = args[2].parse::().map_err(|_| "invalid rows")?; let columns = args[3].parse::().map_err(|_| "invalid columns")?; let history = args[4].parse::().map_err(|_| "invalid history")?; - if rows == 0 || rows > 1000 || !(2..=1000).contains(&columns) || history > 100000 { + if rows == 0 || rows > 1000 || !(1..=1000).contains(&columns) || history > 100000 { return Err("candidate dimensions or history exceed the trace budget".into()); } let data = fs::read(&args[1]).map_err(|error| error.to_string())?; diff --git a/experiments/candidates/alacritty/test_candidate.py b/experiments/candidates/alacritty/test_candidate.py index 8308c60c..096f8e4c 100644 --- a/experiments/candidates/alacritty/test_candidate.py +++ b/experiments/candidates/alacritty/test_candidate.py @@ -6,6 +6,7 @@ import unittest from experiments.candidates.alacritty.run import ROOT, run_suite, trace_binary +from experiments.candidates.alacritty.grapheme_checks import run_fixed_samples class AlacrittyCandidateTests(unittest.TestCase): @@ -23,8 +24,15 @@ def test_real_candidate_checks_pass(self): self.assertEqual(len(self.summary["cases"]), 3) self.assertFalse(self.summary["performance_measured"]) + def test_fixed_streaming_grapheme_samples(self): + summary = run_fixed_samples(self.binary, self.out / "fixed-graphemes") + self.assertTrue(summary["passed"], summary) + self.assertEqual(len(summary["cases"]), 4) + self.assertFalse(summary["windterm_runtime_executed"]) + self.assertFalse(summary["performance_measured"]) + def test_identity_and_limitations_are_recorded(self): - self.assertEqual(self.summary["version"], "0.26.1-dev+termforge.3") + self.assertEqual(self.summary["version"], "0.26.1-dev+termforge.4") self.assertEqual(self.summary["license"], "Apache-2.0") self.assertEqual(self.summary["upstream_commit"], "542147067dd016fc5f49120ef0ba605a98a4e3a7") self.assertEqual(len(self.summary["trace_sha256"]), 64) diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml b/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml index 3b168477..58192a19 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alacritty_terminal" -version = "0.26.1-dev+termforge.3" +version = "0.26.1-dev+termforge.4" authors = ["Christian Duerr ", "Joe Wilm "] license = "Apache-2.0" description = "Library for writing terminal emulators" @@ -23,7 +23,8 @@ log = "0.4" parking_lot = "0.12.0" polling = "3.8.0" regex-automata = "0.4.3" -unicode-width = "0.2.0" +unicode-width = "=0.2.2" +unicode-segmentation = "=1.13.3" vte = { version = "0.15.0", default-features = false, features = ["std", "ansi"] } serde = { version = "1", features = ["derive", "rc"], optional = true } diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs index 37f93691..aa70c4ec 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs @@ -273,6 +273,13 @@ impl Grid { let mut wrapped = match row.shrink(columns) { Some(wrapped) if reflow => wrapped, _ => { + // The adapter supports one-column windows. Preserve a + // whole glyph in a clipped footprint in the non-reflow + // alternate buffer as well. + if columns == 1 && row[Column(0)].flags().contains(Flags::WIDE_CHAR) { + row[Column(0)].flags_mut().remove(Flags::WIDE_CHAR); + row[Column(0)].flags_mut().insert(Flags::CLIPPED_GRAPHEME); + } let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; if reflow && i == cursor_buffer_line && self.cursor.point.column > columns { // If there are empty cells before the cursor, we assume it is explicit @@ -286,8 +293,27 @@ impl Grid { }, }; + // A two-cell glyph cannot be reflowed into a one-column + // grid: repeatedly pushing it to the next row never converges. + // Retain the entire cell text and remove only its owned spacer. + if columns == 1 && row[Column(0)].flags().contains(Flags::WIDE_CHAR) { + row[Column(0)].flags_mut().remove(Flags::WIDE_CHAR); + row[Column(0)].flags_mut().insert(Flags::CLIPPED_GRAPHEME); + if wrapped.first().is_some_and(|cell| cell.flags().contains(Flags::WIDE_CHAR_SPACER)) { + wrapped.remove(0); + let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; + if i == cursor_buffer_line && self.cursor.point.column.0 > 0 { + self.cursor.point.column -= 1; + } + } + if wrapped.is_empty() { + new_raw.push(row); + break; + } + } + // Insert spacer if a wide char would be wrapped into the last column. - if row.len() >= columns + if columns > 1 && row.len() >= columns && row[Column(columns - 1)].flags().contains(Flags::WIDE_CHAR) { let mut spacer = T::default(); diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs index 02737685..a55925e0 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use std::sync::atomic::{AtomicU32, Ordering}; -use arrayvec::ArrayVec; use bitflags::bitflags; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -14,7 +13,7 @@ use crate::vte::ansi::{Color, Hyperlink as VteHyperlink, NamedColor}; /// /// This enforces an upper bound on memory usage per cell, to ensure /// malicous applications cannot use this as a DoS vector. -const MAX_ZEROWIDTH_CHARS: usize = 9; +pub(crate) const MAX_ZEROWIDTH_CHARS: usize = 63; bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -37,6 +36,8 @@ bitflags! { const UNDERCURL = 0b0001_0000_0000_0000; const DOTTED_UNDERLINE = 0b0010_0000_0000_0000; const DASHED_UNDERLINE = 0b0100_0000_0000_0000; + /// Full grapheme text is retained in a clipped one/two-cell footprint. + const CLIPPED_GRAPHEME = 0b1000_0000_0000_0000; const ALL_UNDERLINES = Self::UNDERLINE.bits() | Self::DOUBLE_UNDERLINE.bits() | Self::UNDERCURL.bits() | Self::DOTTED_UNDERLINE.bits() | Self::DASHED_UNDERLINE.bits(); @@ -75,7 +76,10 @@ impl From for Hyperlink { impl From for VteHyperlink { fn from(val: Hyperlink) -> Self { - VteHyperlink { id: Some(val.id().to_owned()), uri: val.uri().to_owned() } + VteHyperlink { + id: Some(val.id().to_owned()), + uri: val.uri().to_owned(), + } } } @@ -94,10 +98,12 @@ impl HyperlinkInner { let id = match id { Some(id) => id.to_string(), None => { - let mut id = HYPERLINK_ID_SUFFIX.fetch_add(1, Ordering::Relaxed).to_string(); + let mut id = HYPERLINK_ID_SUFFIX + .fetch_add(1, Ordering::Relaxed) + .to_string(); id.push_str("_alacritty"); id - }, + } }; Self { id, uri } @@ -130,11 +136,49 @@ impl ResetDiscriminant for Cell { #[derive(Default, Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CellExtra { - zerowidth: ArrayVec, + #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_suffix"))] + zerowidth: Vec, + #[cfg_attr(feature = "serde", serde(default))] + grapheme_fragment: bool, underline_color: Option, hyperlink: Option, } +// Keep the optional upstream serde seam bounded as well as live Handler input. +// Consume at most one scalar past the limit; never deserialize an unbounded Vec +// and then reject it after allocation. +#[cfg(feature = "serde")] +fn deserialize_suffix<'de, D: serde::Deserializer<'de>>( + deserializer: D, +) -> Result, D::Error> { + struct SuffixVisitor; + impl<'de> serde::de::Visitor<'de> for SuffixVisitor { + type Value = Vec; + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + formatter, + "at most {MAX_ZEROWIDTH_CHARS} grapheme suffix scalars" + ) + } + fn visit_seq>( + self, + mut sequence: A, + ) -> Result { + let mut suffix = Vec::new(); + while let Some(c) = sequence.next_element::()? { + if suffix.len() == MAX_ZEROWIDTH_CHARS { + return Err(serde::de::Error::custom( + "grapheme suffix exceeds bounded cell budget", + )); + } + suffix.push(c); + } + Ok(suffix) + } + } + deserializer.deserialize_seq(SuffixVisitor) +} + /// Content and attributes of a single cell in the terminal grid. #[derive(Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -168,17 +212,45 @@ impl Cell { /// Write a new zerowidth character to this cell. #[inline] - pub fn push_zerowidth(&mut self, character: char) { + pub fn push_zerowidth(&mut self, character: char) -> bool { + let extra = self.extra.get_or_insert(Default::default()); + let suffix = &mut Arc::make_mut(extra).zerowidth; + if suffix.len() == MAX_ZEROWIDTH_CHARS { + return false; + } + suffix.push(character); + true + } + + /// Replace a bounded grapheme suffix. Scalars may have nonzero isolated width. + pub(crate) fn set_grapheme_suffix( + &mut self, + suffix: impl Iterator, + fragment: bool, + ) { let extra = self.extra.get_or_insert(Default::default()); - let _ = Arc::make_mut(extra).zerowidth.try_push(character); + let extra = Arc::make_mut(extra); + extra.zerowidth.clear(); + extra.zerowidth.extend(suffix); + assert!(extra.zerowidth.len() <= MAX_ZEROWIDTH_CHARS); + extra.grapheme_fragment = fragment; + } + + pub fn grapheme_fragment(&self) -> bool { + self.extra + .as_ref() + .is_some_and(|extra| extra.grapheme_fragment) } /// Remove all wide char data from a cell. #[inline(never)] pub fn clear_wide(&mut self) { - self.flags.remove(Flags::WIDE_CHAR); + self.flags + .remove(Flags::WIDE_CHAR | Flags::CLIPPED_GRAPHEME); if let Some(extra) = self.extra.as_mut() { - Arc::make_mut(extra).zerowidth = ArrayVec::new(); + let extra = Arc::make_mut(extra); + extra.zerowidth = Vec::new(); + extra.grapheme_fragment = false; } self.c = ' '; } @@ -257,14 +329,20 @@ impl GridCell for Cell { #[inline] fn reset(&mut self, template: &Self) { - *self = Cell { bg: template.bg, ..Cell::default() }; + *self = Cell { + bg: template.bg, + ..Cell::default() + }; } } impl From for Cell { #[inline] fn from(color: Color) -> Self { - Self { bg: color, ..Cell::default() } + Self { + bg: color, + ..Cell::default() + } } } @@ -304,6 +382,26 @@ mod tests { use crate::grid::Row; use crate::index::Column; + #[cfg(feature = "serde")] + #[test] + fn serde_suffix_budget_rejects_an_unbounded_external_cell() { + let mut value = serde_json::to_value(CellExtra::default()).unwrap(); + value["zerowidth"] = serde_json::json!(vec!['\u{301}'; 63]); + assert!(serde_json::from_value::(value.clone()).is_ok()); + value["zerowidth"] = serde_json::json!(vec!['\u{301}'; 64]); + assert!( + serde_json::from_value::(value).is_err(), + "serde bypassed the bounded runtime suffix policy" + ); + } + #[cfg(feature = "serde")] + #[test] + fn serde_legacy_extra_without_fragment_field_stays_compatible() { + let mut value = serde_json::to_value(CellExtra::default()).unwrap(); + value.as_object_mut().unwrap().remove("grapheme_fragment"); + let extra = serde_json::from_value::(value).unwrap(); + assert!(!extra.grapheme_fragment); + } #[test] fn cell_size_is_below_cap() { // Expected cell size on 64-bit architectures. diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/grapheme.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/grapheme.rs new file mode 100644 index 00000000..55e1abb9 --- /dev/null +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/grapheme.rs @@ -0,0 +1,672 @@ +//! Bounded, immediate streaming extended-grapheme placement. +//! +//! Feed and observation boundaries are deliberately absent from this state machine. +//! A screen/cursor mutation seals the last cluster; SGR and queries are transparent. +use std::mem; + +use arrayvec::ArrayString; +use unicode_segmentation::UnicodeSegmentation; +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + +use crate::grid::Dimensions; + +use super::{Cell, Column, EventListener, Flags, Point, Term, TermMode}; + +pub(crate) const MAX_GRAPHEME_SCALARS: usize = 64; +pub(crate) const MAX_GRAPHEME_BYTES: usize = 256; + +#[derive(Default)] +pub(super) struct StreamingGrapheme { + text: String, + point: Option, + width: usize, + template: Cell, + fragment: bool, +} + +impl StreamingGrapheme { + fn reset(&mut self, fragment: bool) { + self.text.clear(); + self.point = None; + self.fragment = fragment; + } + + fn joins(&self, c: char) -> bool { + if self.point.is_none() { + return false; + } + // Printable ASCII always has a boundary; CR/LF still follow UAX29. + if self.text.len() == 1 + && (b' '..=b'~').contains(&self.text.as_bytes()[0]) + && (' '..='~').contains(&c) + { + return false; + } + // Probe the next scalar on a fixed stack buffer. It must not grow the + // persistent String beyond its 256-byte fragment budget just to decide + // whether a boundary exists at that budget. + let mut probe = ArrayString::<{ MAX_GRAPHEME_BYTES + 4 }>::new(); + probe.push_str(&self.text); + probe.push(c); + probe.graphemes(true).nth(1).is_none() + } +} + +// Zero-width Prepend scalars need a forward editable prefix. An ordinary +// ASCII base is a boundary after every other zero-width class; query the same +// versioned UAX29 implementation instead of maintaining another property table. +fn is_forward_prefix(c: char) -> bool { + let mut probe = ArrayString::<5>::new(); + probe.push(c); + probe.push('A'); + probe.graphemes(true).nth(1).is_none() +} + +impl Term { + /// End the editable suffix without changing its already visible text/cursor. + pub fn seal_grapheme(&mut self) { + self.grapheme.reset(false); + } + + /// Number of lossless bounded fragments, not a count of discarded scalars. + pub fn grapheme_overflows(&self) -> u64 { + self.grapheme_overflows + } +} + +impl Term { + pub(super) fn input_grapheme(&mut self, c: char) { + let c = self.grid.cursor.charsets[self.active_charset].map(c); + let Some(scalar_width) = c.width() else { + return; + }; + // UAX29 always breaks between two printable ASCII scalars. Keep the + // same editable-prefix contract without moving the entire pending state + // or cloning an unchanged style template for every byte in an ASCII run. + if (' '..='~').contains(&c) + && self.grapheme.point.is_some() + && self.grapheme.text.len() == 1 + && self.grapheme.text.is_ascii() + { + self.grapheme.reset(false); + let (point, width) = self.place_grapheme(c, 1); + self.grapheme.text.push(c); + self.grapheme.point = Some(point); + self.grapheme.width = width; + if self.grapheme.template != self.grid.cursor.template { + self.grapheme.template = self.grid.cursor.template.clone(); + } + return; + } + let mut pending = mem::take(&mut self.grapheme); + if pending.joins(c) { + if pending.text.chars().count() == MAX_GRAPHEME_SCALARS + || pending.text.len() + c.len_utf8() > MAX_GRAPHEME_BYTES + { + self.grapheme_overflows = self.grapheme_overflows.saturating_add(1); + pending.reset(true); + } else { + pending.text.push(c); + self.extend_grapheme(&mut pending); + self.grapheme = pending; + return; + } + } else { + pending.reset(false); + } + + // Preserve the existing degenerate-mark attachment policy after a control + // boundary. It cannot re-open ZWJ/RI segmentation across that boundary. + if scalar_width == 0 && !pending.fragment && !is_forward_prefix(c) { + let mut point = self.grid.cursor.point; + if !self.grid.cursor.input_needs_wrap { + point.column.0 = point.column.saturating_sub(1); + } + if self.grid[point].flags.contains(Flags::WIDE_CHAR_SPACER) { + point.column.0 = point.column.saturating_sub(1); + } + if self.grid[point].push_zerowidth(c) { + self.damage + .damage_point(Point::new(point.line.0 as usize, point.column)); + self.grapheme = pending; + return; + } + self.grapheme_overflows = self.grapheme_overflows.saturating_add(1); + pending.fragment = true; + } + + pending.text.push(c); + pending.template = self.grid.cursor.template.clone(); + let (point, width) = self.place_grapheme(c, scalar_width.max(1)); + pending.point = Some(point); + pending.width = width; + if pending.fragment { + self.grid[point].set_grapheme_suffix(std::iter::empty(), true); + } + self.grapheme = pending; + } + + /// Place one currently-known prefix using the existing wrap/insert/write paths. + #[inline] + fn place_grapheme(&mut self, c: char, requested_width: usize) -> (Point, usize) { + if self.grid.cursor.input_needs_wrap { + self.wrapline(); + } + let columns = self.columns(); + let mut width = requested_width.min(2); + if width == 2 && self.grid.cursor.point.column + 1 >= columns { + if columns > 1 && self.mode.contains(TermMode::LINE_WRAP) { + self.grid + .cursor + .template + .flags + .insert(Flags::LEADING_WIDE_CHAR_SPACER); + self.write_at_cursor(' '); + self.grid + .cursor + .template + .flags + .remove(Flags::LEADING_WIDE_CHAR_SPACER); + self.wrapline(); + } else { + // Keep all scalars even when a two-cell glyph cannot fit. + width = 1; + } + } + if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.column + width < columns { + // Inserting at a wide trailer must clear the original pair before + // shifting; otherwise the shift detaches the trailer from its lead. + if self + .grid + .cursor_cell() + .flags + .intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) + { + self.write_at_cursor(' '); + } + let line = self.grid.cursor.point.line; + let col = self.grid.cursor.point.column; + for col in (col.0..(columns - width)).rev() { + self.grid[line][..].swap(col + width, col); + } + self.clip_inserted_right_edge(); + } + let point = self.grid.cursor.point; + self.grid + .cursor + .template + .flags + .set(Flags::CLIPPED_GRAPHEME, requested_width > width); + self.grid + .cursor + .template + .flags + .set(Flags::WIDE_CHAR, width == 2); + self.write_at_cursor(c); + self.grid + .cursor + .template + .flags + .remove(Flags::WIDE_CHAR | Flags::CLIPPED_GRAPHEME); + if width == 2 { + self.grid.cursor.point.column += 1; + self.grid + .cursor + .template + .flags + .insert(Flags::WIDE_CHAR_SPACER); + self.write_at_cursor(' '); + self.grid + .cursor + .template + .flags + .remove(Flags::WIDE_CHAR_SPACER); + } + self.advance_grapheme_cursor(point, width); + (point, width) + } + + fn advance_grapheme_cursor(&mut self, point: Point, width: usize) { + self.grid.cursor.point = point; + let next = point.column.0 + width; + self.grid.cursor.point.column = Column(next.min(self.columns() - 1)); + self.grid.cursor.input_needs_wrap = next >= self.columns(); + self.damage.damage_line( + point.line.0 as usize, + point.column.0, + (point.column.0 + width - 1).min(self.columns() - 1), + ); + } + + fn extend_grapheme(&mut self, pending: &mut StreamingGrapheme) { + let point = pending.point.expect("editable grapheme placement"); + let requested = pending.text.width().max(1); + let available = self.columns() - point.column.0; + let width = requested.min(2).min(available); + let live_template = mem::replace(&mut self.grid.cursor.template, pending.template.clone()); + if requested >= 2 + && available == 1 + && self.columns() > 1 + && self.mode.contains(TermMode::LINE_WRAP) + { + // The visible narrow prefix becomes the normal leading-wide spacer. + self.grid.cursor.point = point; + self.grid.cursor.input_needs_wrap = false; + let (new_point, width) = + self.place_grapheme(pending.text.chars().next().unwrap(), requested); + pending.point = Some(new_point); + pending.width = width; + } else { + if width > pending.width && self.mode.contains(TermMode::INSERT) { + self.shift_grapheme_tail(point, pending.width, width); + } + if width != pending.width { + // The writer owns neighboring wide-cell cleanup. Shrink keeps the + // prefix's prior wrap/overwrite side effects; it never unscrolls. + self.grid.cursor.point = Point::new(point.line, point.column + 1); + if width == 2 { + self.grid + .cursor + .template + .flags + .insert(Flags::WIDE_CHAR_SPACER); + } + self.write_at_cursor(' '); + self.grid + .cursor + .template + .flags + .remove(Flags::WIDE_CHAR_SPACER); + } + if width < pending.width && self.mode.contains(TermMode::INSERT) { + self.shift_grapheme_tail(point, pending.width, width); + } + let cell = &mut self.grid[point]; + *cell = pending.template.clone(); + cell.c = pending.text.chars().next().unwrap(); + cell.flags.set(Flags::WIDE_CHAR, width == 2); + cell.flags.set(Flags::CLIPPED_GRAPHEME, requested > width); + pending.width = width; + self.advance_grapheme_cursor(point, width); + } + let point = pending.point.unwrap(); + self.grid[point].set_grapheme_suffix(pending.text.chars().skip(1), pending.fragment); + self.damage.damage_line( + point.line.0 as usize, + point.column.0, + (point.column.0 + pending.width - 1).min(self.columns() - 1), + ); + self.grid.cursor.template = live_template; + } + + fn shift_grapheme_tail(&mut self, point: Point, old_width: usize, new_width: usize) { + let columns = self.columns(); + let row = &mut self.grid[point.line][..]; + if new_width > old_width { + for column in ((point.column.0 + old_width)..columns - 1).rev() { + row.swap(column + 1, column); + } + } else { + for column in (point.column.0 + new_width)..columns - 1 { + row.swap(column, column + 1); + } + row[columns - 1] = Cell::default(); + } + self.clip_inserted_right_edge(); + self.damage + .damage_line(point.line.0 as usize, point.column.0, columns - 1); + } + + // INSERT cropping must remove a whole wide glyph, including at the + // immediately visible one-scalar prefix before a later width increase. + fn clip_inserted_right_edge(&mut self) { + let line = self.grid.cursor.point.line; + let last = Column(self.columns() - 1); + if self.grid[line][last].flags.contains(Flags::WIDE_CHAR) { + self.grid[line][last].clear_wide(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::event::Event; + use crate::index::Line; + use crate::term::{Config, test::TermSize}; + use crate::vte::ansi::{Color, NamedColor, Processor}; + use std::sync::{Arc, Mutex}; + + #[derive(Clone, Default)] + struct Listener(Arc>>); + impl EventListener for Listener { + fn send_event(&self, event: Event) { + if let Event::PtyWrite(text) = event { + self.0.lock().unwrap().push(text); + } + } + } + fn term(columns: usize) -> Term { + Term::new( + Config { + scrolling_history: 8, + ..Config::default() + }, + &TermSize::new(columns, 3), + Listener::default(), + ) + } + fn feed(term: &mut Term, text: &str, fragmented: bool) { + let mut parser: Processor = Processor::new(); + if fragmented { + for byte in text.bytes() { + parser.advance(term, &[byte]); + } + } else { + parser.advance(term, text.as_bytes()); + } + } + fn text(cell: &Cell) -> String { + let mut text = cell.c.to_string(); + text.extend(cell.zerowidth().into_iter().flatten()); + text + } + fn assert_cluster(term: &Term, row: i32, column: usize, value: &str, width: usize) { + let cell = &term.grid()[Line(row)][Column(column)]; + assert_eq!(text(cell), value); + assert_eq!(cell.flags.contains(Flags::WIDE_CHAR), width == 2); + if width == 2 { + assert!( + term.grid()[Line(row)][Column(column + 1)] + .flags + .contains(Flags::WIDE_CHAR_SPACER) + ); + } + } + #[test] + fn unicode17_normative_grapheme_break_data_matches_streaming_boundaries() { + assert_eq!(unicode_segmentation::UNICODE_VERSION, (17, 0, 0)); + assert_eq!(unicode_width::UNICODE_VERSION, (17, 0, 0)); + let data = include_str!("../../tests/data/GraphemeBreakTest-17.0.0.txt"); + let mut tested = 0; + for (number, line) in data.lines().enumerate() { + let line = line.split('#').next().unwrap().trim(); + if line.is_empty() { + continue; + } + let tokens: Vec<_> = line.split_whitespace().collect(); + let mut pending = StreamingGrapheme::default(); + pending.point = Some(Point::new(Line(0), Column(0))); + for pair in tokens[..tokens.len() - 1].chunks_exact(2) { + let c = char::from_u32(u32::from_str_radix(pair[1], 16).unwrap()).unwrap(); + if !pending.text.is_empty() { + assert_eq!( + pending.joins(c), + pair[0] == "×", + "official case line {}", + number + 1 + ); + if pair[0] == "÷" { + pending.text.clear(); + } + } + pending.text.push(c); + } + tested += 1; + } + assert_eq!(tested, 766); + } + #[test] + fn fixed_zwj_keycap_whole_and_byte_are_one_two_cell_cluster() { + for fragmented in [false, true] { + for value in ["👩\u{200d}💻", "1\u{fe0f}\u{20e3}"] { + let mut term = term(8); + feed(&mut term, value, fragmented); + assert_cluster(&term, 0, 0, value, 2); + assert_eq!(term.grid.cursor.point, Point::new(Line(0), Column(2))); + } + } + } + #[test] + fn ordinary_text_ri_parity_non_emoji_zwj_and_normalization_are_not_overjoined() { + let mut term = term(24); + feed(&mut term, "Ae\u{301}x\u{200d}y🇨🇳🇯B", true); + assert_cluster(&term, 0, 0, "A", 1); + assert_cluster(&term, 0, 1, "e\u{301}", 1); + assert_cluster(&term, 0, 2, "x\u{200d}", 1); + assert_cluster(&term, 0, 3, "y", 1); + assert_cluster(&term, 0, 4, "🇨🇳", 2); + assert_cluster(&term, 0, 6, "🇯", 1); + assert_cluster(&term, 0, 7, "B", 1); + } + #[test] + fn prefix_is_visible_and_query_sgr_and_observation_are_transparent() { + let mut term = term(8); + let mut parser: Processor = Processor::new(); + parser.advance(&mut term, "1".as_bytes()); + assert_cluster(&term, 0, 0, "1", 1); + assert_eq!(term.grid.cursor.point.column, Column(1)); + let _ = term.grid(); + let _ = term.renderable_content(); + parser.advance(&mut term, b"\x1b[6n\x1b[31m"); + parser.advance(&mut term, "\u{fe0f}\u{20e3}".as_bytes()); + assert_cluster(&term, 0, 0, "1\u{fe0f}\u{20e3}", 2); + assert_eq!( + term.grid()[Line(0)][Column(0)].fg, + Color::Named(NamedColor::Foreground) + ); + assert_eq!(*term.event_proxy.0.lock().unwrap(), vec!["\x1b[1;2R"]); + parser.advance(&mut term, b"X"); + assert_eq!( + term.grid()[Line(0)][Column(2)].fg, + Color::Named(NamedColor::Red) + ); + } + #[test] + fn growth_at_right_margin_wraps_whole_cluster_and_shrink_keeps_prior_wrap() { + let mut growing = term(4); + feed(&mut growing, "ABC1\u{fe0f}\u{20e3}", true); + assert_cluster(&growing, 1, 0, "1\u{fe0f}\u{20e3}", 2); + assert!( + growing.grid()[Line(0)][Column(3)] + .flags + .contains(Flags::LEADING_WIDE_CHAR_SPACER) + ); + let mut shrinking = term(4); + feed(&mut shrinking, "ABC⌚\u{fe0e}", true); + assert_cluster(&shrinking, 1, 0, "⌚\u{fe0e}", 1); + assert_eq!(shrinking.grid.cursor.point, Point::new(Line(1), Column(1))); + } + #[test] + fn no_autowrap_and_one_column_clip_footprint_but_keep_complete_text() { + for value in ["1\u{fe0f}\u{20e3}", "👩\u{200d}💻"] { + let mut term = term(4); + feed(&mut term, &format!("\x1b[?7lABC{value}"), true); + assert_cluster(&term, 0, 3, value, 1); + assert!( + term.grid()[Line(0)][Column(3)] + .flags + .contains(Flags::CLIPPED_GRAPHEME) + ); + assert!(term.grid.cursor.input_needs_wrap); + let mut single = super::tests::term(1); + feed(&mut single, value, true); + assert_cluster(&single, 0, 0, value, 1); + assert!( + single.grid()[Line(0)][Column(0)] + .flags + .contains(Flags::CLIPPED_GRAPHEME) + ); + } + } + #[test] + fn shrinking_a_wide_cluster_to_one_column_terminates_and_retains_text_in_both_buffers() { + let mut term = term(4); + feed(&mut term, "👩\u{200d}💻", true); + term.resize(TermSize::new(1, 3)); + assert_cluster(&term, 0, 0, "👩\u{200d}💻", 1); + assert!( + term.grid()[Line(0)][Column(0)] + .flags + .contains(Flags::CLIPPED_GRAPHEME) + ); + assert_eq!(term.grid.cursor.point, Point::new(Line(0), Column(0))); + assert!(term.grid.cursor.input_needs_wrap); + term.resize(TermSize::new(4, 3)); + feed(&mut term, "\x1b[?1049h\x1b[H1\u{fe0f}\u{20e3}", true); + term.resize(TermSize::new(1, 3)); + assert_cluster(&term, 0, 0, "1\u{fe0f}\u{20e3}", 1); + feed(&mut term, "\x1b[?1049l", true); + assert_cluster(&term, 0, 0, "👩\u{200d}💻", 1); + assert!( + term.grid()[Line(0)][Column(0)] + .flags + .contains(Flags::CLIPPED_GRAPHEME) + ); + } + #[test] + fn insert_growth_and_shrink_shift_tail_by_width_delta_only() { + for (value, expected_column) in [("1\u{fe0f}\u{20e3}", 2), ("⌚\u{fe0e}", 1)] { + let mut term = term(8); + feed(&mut term, &format!("ABCDEF\r\x1b[4h{value}"), true); + assert_cluster(&term, 0, 0, value, expected_column); + assert_cluster(&term, 0, expected_column, "A", 1); + assert_cluster(&term, 0, expected_column + 1, "B", 1); + assert_cluster(&term, 0, 7, if expected_column == 2 { "F" } else { " " }, 1); + } + } + #[test] + fn insert_into_a_wide_trailer_clears_the_pair_before_every_prefix_shift() { + for (value, expected) in [ + ("X", " X ABCDE"), + ("1\u{fe0f}\u{20e3}", " 1\u{fe0f}\u{20e3} ABCD"), + ("⌚\u{fe0e}", " ⌚\u{fe0e} ABCD"), + ] { + let mut term = term(8); + let mut parser: Processor = Processor::new(); + parser.advance(&mut term, "中ABCDEF\x1b[1;2H\x1b[4h".as_bytes()); + for byte in value.bytes() { + parser.advance(&mut term, &[byte]); + for column in 0..8 { + let flags = term.grid()[Line(0)][Column(column)].flags; + if flags.contains(Flags::WIDE_CHAR) { + assert!( + column < 7 + && term.grid()[Line(0)][Column(column + 1)] + .flags + .contains(Flags::WIDE_CHAR_SPACER) + ); + } + if flags.contains(Flags::WIDE_CHAR_SPACER) { + assert!( + column > 0 + && term.grid()[Line(0)][Column(column - 1)] + .flags + .contains(Flags::WIDE_CHAR) + ); + } + } + } + let projected = (0..8) + .filter(|column| { + !term.grid()[Line(0)][Column(*column)] + .flags + .contains(Flags::WIDE_CHAR_SPACER) + }) + .map(|column| text(&term.grid()[Line(0)][Column(column)])) + .collect::(); + assert_eq!(projected.trim_end(), expected); + } + } + #[test] + fn zero_width_prepend_is_a_visible_forward_prefix_and_preserves_its_scalar() { + let mut prefix = term(8); + let mut parser: Processor = Processor::new(); + parser.advance(&mut prefix, "\u{70f}".as_bytes()); + assert_cluster(&prefix, 0, 0, "\u{70f}", 1); + let _ = prefix.renderable_content(); + parser.advance(&mut prefix, b"A"); + assert_cluster(&prefix, 0, 0, "\u{70f}A", 1); + assert_eq!(prefix.grid.cursor.point.column, Column(1)); + for fragmented in [false, true] { + let mut term = term(8); + feed(&mut term, "\u{70f}A", fragmented); + assert_cluster(&term, 0, 0, "\u{70f}A", 1); + } + } + #[test] + fn insert_prefix_and_width_growth_never_leave_a_wide_lead_at_cropped_right_edge() { + let mut term = term(8); + let mut parser: Processor = Processor::new(); + parser.advance(&mut term, "ABCDEF中\r\x1b[4h1".as_bytes()); + assert!( + !term.grid()[Line(0)][Column(7)] + .flags + .contains(Flags::WIDE_CHAR), + "one-cell insert cropped the trailing half but retained its wide lead" + ); + parser.advance(&mut term, "\u{fe0f}\u{20e3}".as_bytes()); + assert_cluster(&term, 0, 0, "1\u{fe0f}\u{20e3}", 2); + for column in 0..8 { + let flags = term.grid()[Line(0)][Column(column)].flags; + if flags.contains(Flags::WIDE_CHAR) { + assert!( + column < 7 + && term.grid()[Line(0)][Column(column + 1)] + .flags + .contains(Flags::WIDE_CHAR_SPACER) + ); + } + if flags.contains(Flags::WIDE_CHAR_SPACER) { + assert!( + column > 0 + && term.grid()[Line(0)][Column(column - 1)] + .flags + .contains(Flags::WIDE_CHAR) + ); + } + } + } + #[test] + fn cursor_erase_newline_resize_and_alt_mutations_seal_zwj_context() { + for barrier in [ + "\x1b[1C", + "\x1b[X", + "\r\n", + "\x1b[?1049h\x1b[?1049l", + "\x1bc", + ] { + let mut term = term(12); + feed(&mut term, &format!("👩\u{200d}{barrier}💻"), true); + assert!( + term.grid() + .display_iter() + .all(|item| text(item.cell) != "👩\u{200d}💻") + ); + } + let mut term = term(12); + feed(&mut term, "👩\u{200d}", true); + term.resize(TermSize::new(14, 3)); + feed(&mut term, "💻", true); + assert_cluster(&term, 0, 0, "👩\u{200d}", 2); + assert_cluster(&term, 0, 2, "💻", 2); + } + #[test] + fn long_combining_input_spills_without_dropping_or_normalizing_scalars() { + let value = format!("e{}", "\u{301}".repeat(200)); + let mut term = term(16); + feed(&mut term, &value, true); + let actual = (0..4) + .map(|column| text(&term.grid()[Line(0)][Column(column)])) + .collect::(); + assert_eq!(actual, value); + assert_eq!(term.grapheme_overflows(), 3); + assert_eq!( + term.grid()[Line(0)][Column(0)].zerowidth().unwrap().len(), + 63 + ); + assert!(term.grid()[Line(0)][Column(1)].grapheme_fragment()); + assert_eq!(term.grapheme.text.chars().count(), 9); + assert!(term.grapheme.text.len() <= MAX_GRAPHEME_BYTES); + } +} diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs index f90012c1..e0139580 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs @@ -26,14 +26,15 @@ use crate::vte::ansi::{ StandardCharset, }; +mod grapheme; pub mod cell; pub mod color; pub mod search; /// Minimum number of columns. /// -/// A minimum of 2 is necessary to hold fullwidth unicode characters. -pub const MIN_COLUMNS: usize = 2; +/// One-column windows retain full grapheme text in an explicitly clipped cell. +pub const MIN_COLUMNS: usize = 1; /// Minimum number of visible lines. pub const MIN_SCREEN_LINES: usize = 1; @@ -328,6 +329,10 @@ pub struct Term { /// Information about damaged cells. damage: TermDamageState, + /// One visible, editable trailing extended grapheme and bounded diagnostic. + grapheme: grapheme::StreamingGrapheme, + grapheme_overflows: u64, + /// Config directly for the terminal. config: Config, } @@ -426,6 +431,8 @@ impl Term { let damage = TermDamageState::new(num_cols, num_lines); Term { + grapheme: Default::default(), + grapheme_overflows: 0, inactive_grid, inactive_scroll_region: scroll_region.clone(), scroll_region, @@ -652,11 +659,13 @@ impl Term { /// Mutable access to the raw grid data structure. pub fn grid_mut(&mut self) -> &mut Grid { + self.seal_grapheme(); &mut self.grid } /// Resize terminal to new dimensions. pub fn resize(&mut self, size: S) { + self.seal_grapheme(); let old_cols = self.columns(); let old_lines = self.screen_lines(); @@ -717,6 +726,7 @@ impl Term { /// Swap primary and alternate screen buffer. pub fn swap_alt(&mut self) { + self.seal_grapheme(); if !self.mode.contains(TermMode::ALT_SCREEN) { // Set alt screen cursor to the current primary screen cursor. self.inactive_grid.cursor = self.grid.cursor.clone(); @@ -1068,85 +1078,12 @@ impl Handler for Term { /// A character to be displayed. #[inline(never)] fn input(&mut self, c: char) { - // Number of cells the char will occupy. - let width = match c.width() { - Some(width) => width, - None => return, - }; - - // Handle zero-width characters. - if width == 0 { - // Get previous column. - let mut column = self.grid.cursor.point.column; - if !self.grid.cursor.input_needs_wrap { - column.0 = column.saturating_sub(1); - } - - // Put zerowidth characters over first fullwidth character cell. - let line = self.grid.cursor.point.line; - if self.grid[line][column].flags.contains(Flags::WIDE_CHAR_SPACER) { - column.0 = column.saturating_sub(1); - } - - self.grid[line][column].push_zerowidth(c); - return; - } - - // Move cursor to next line. - if self.grid.cursor.input_needs_wrap { - self.wrapline(); - } - - // If in insert mode, first shift cells to the right. - let columns = self.columns(); - if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.column + width < columns { - let line = self.grid.cursor.point.line; - let col = self.grid.cursor.point.column; - let row = &mut self.grid[line][..]; - - for col in (col.0..(columns - width)).rev() { - row.swap(col + width, col); - } - } - - if width == 1 { - self.write_at_cursor(c); - } else { - if self.grid.cursor.point.column + 1 >= columns { - if self.mode.contains(TermMode::LINE_WRAP) { - // Insert placeholder before wide char if glyph does not fit in this row. - self.grid.cursor.template.flags.insert(Flags::LEADING_WIDE_CHAR_SPACER); - self.write_at_cursor(' '); - self.grid.cursor.template.flags.remove(Flags::LEADING_WIDE_CHAR_SPACER); - self.wrapline(); - } else { - // Prevent out of bounds crash when linewrapping is disabled. - self.grid.cursor.input_needs_wrap = true; - return; - } - } - - // Write full width glyph to current cursor cell. - self.grid.cursor.template.flags.insert(Flags::WIDE_CHAR); - self.write_at_cursor(c); - self.grid.cursor.template.flags.remove(Flags::WIDE_CHAR); - - // Write spacer to cell following the wide glyph. - self.grid.cursor.point.column += 1; - self.grid.cursor.template.flags.insert(Flags::WIDE_CHAR_SPACER); - self.write_at_cursor(' '); - self.grid.cursor.template.flags.remove(Flags::WIDE_CHAR_SPACER); - } - - if self.grid.cursor.point.column + 1 < columns { - self.grid.cursor.point.column += 1; - } else { - self.grid.cursor.input_needs_wrap = true; - } + self.input_grapheme(c); } #[inline] fn decaln(&mut self) { + self.seal_grapheme(); trace!("Decalnning"); for line in (0..self.screen_lines()).map(Line::from) { @@ -1162,6 +1099,7 @@ impl Handler for Term { #[inline] fn goto(&mut self, line: i32, col: usize) { + self.seal_grapheme(); let line = Line(line); let col = Column(col); @@ -1181,18 +1119,21 @@ impl Handler for Term { #[inline] fn goto_line(&mut self, line: i32) { + self.seal_grapheme(); trace!("Going to line: {line}"); self.goto(line, self.grid.cursor.point.column.0) } #[inline] fn goto_col(&mut self, col: usize) { + self.seal_grapheme(); trace!("Going to column: {col}"); self.goto(self.grid.cursor.point.line.0, col) } #[inline] fn insert_blank(&mut self, count: usize) { + self.seal_grapheme(); let cursor = &self.grid.cursor; let bg = cursor.template.bg; @@ -1221,6 +1162,7 @@ impl Handler for Term { #[inline] fn move_up(&mut self, lines: usize) { + self.seal_grapheme(); trace!("Moving up: {lines}"); let line = self.grid.cursor.point.line - lines; @@ -1230,6 +1172,7 @@ impl Handler for Term { #[inline] fn move_down(&mut self, lines: usize) { + self.seal_grapheme(); trace!("Moving down: {lines}"); let line = self.grid.cursor.point.line + lines; @@ -1239,6 +1182,7 @@ impl Handler for Term { #[inline] fn move_forward(&mut self, cols: usize) { + self.seal_grapheme(); trace!("Moving forward: {cols}"); let last_column = cmp::min(self.grid.cursor.point.column + cols, self.last_column()); @@ -1251,6 +1195,7 @@ impl Handler for Term { #[inline] fn move_backward(&mut self, cols: usize) { + self.seal_grapheme(); trace!("Moving backward: {cols}"); let column = self.grid.cursor.point.column.saturating_sub(cols); @@ -1294,6 +1239,7 @@ impl Handler for Term { #[inline] fn push_keyboard_mode(&mut self, mode: KeyboardModes) { + self.seal_grapheme(); if !self.config.kitty_keyboard { return; } @@ -1314,6 +1260,7 @@ impl Handler for Term { #[inline] fn pop_keyboard_modes(&mut self, to_pop: u16) { + self.seal_grapheme(); if !self.config.kitty_keyboard { return; } @@ -1329,6 +1276,7 @@ impl Handler for Term { #[inline] fn set_keyboard_mode(&mut self, mode: KeyboardModes, apply: KeyboardModesApplyBehavior) { + self.seal_grapheme(); if !self.config.kitty_keyboard { return; } @@ -1355,6 +1303,7 @@ impl Handler for Term { #[inline] fn move_down_and_cr(&mut self, lines: usize) { + self.seal_grapheme(); trace!("Moving down and cr: {lines}"); let line = self.grid.cursor.point.line + lines; @@ -1363,6 +1312,7 @@ impl Handler for Term { #[inline] fn move_up_and_cr(&mut self, lines: usize) { + self.seal_grapheme(); trace!("Moving up and cr: {lines}"); let line = self.grid.cursor.point.line - lines; @@ -1372,6 +1322,7 @@ impl Handler for Term { /// Insert tab at cursor position. #[inline] fn put_tab(&mut self, mut count: u16) { + self.seal_grapheme(); // A tab after the last column is the same as a linebreak. if self.grid.cursor.input_needs_wrap { self.wrapline(); @@ -1404,6 +1355,7 @@ impl Handler for Term { /// Backspace. #[inline] fn backspace(&mut self) { + self.seal_grapheme(); trace!("Backspace"); if self.grid.cursor.point.column > Column(0) { @@ -1418,6 +1370,7 @@ impl Handler for Term { /// Carriage return. #[inline] fn carriage_return(&mut self) { + self.seal_grapheme(); trace!("Carriage return"); let new_col = 0; let line = self.grid.cursor.point.line.0 as usize; @@ -1429,6 +1382,7 @@ impl Handler for Term { /// Linefeed. #[inline] fn linefeed(&mut self) { + self.seal_grapheme(); trace!("Linefeed"); let current = self.grid.cursor.point.line; let wrapped = self.grid[current].last().is_some_and(|cell| cell.flags.contains(Flags::WRAPLINE)); @@ -1452,6 +1406,7 @@ impl Handler for Term { #[inline] fn substitute(&mut self) { + self.seal_grapheme(); trace!("[unimplemented] Substitute"); } @@ -1479,6 +1434,7 @@ impl Handler for Term { /// on it. #[inline] fn newline(&mut self) { + self.seal_grapheme(); self.linefeed(); if self.mode.contains(TermMode::LINE_FEED_NEW_LINE) { @@ -1488,24 +1444,28 @@ impl Handler for Term { #[inline] fn set_horizontal_tabstop(&mut self) { + self.seal_grapheme(); trace!("Setting horizontal tabstop"); self.tabs[self.grid.cursor.point.column] = true; } #[inline] fn scroll_up(&mut self, lines: usize) { + self.seal_grapheme(); let origin = self.scroll_region.start; self.scroll_up_relative(origin, lines); } #[inline] fn scroll_down(&mut self, lines: usize) { + self.seal_grapheme(); let origin = self.scroll_region.start; self.scroll_down_relative(origin, lines); } #[inline] fn insert_blank_lines(&mut self, lines: usize) { + self.seal_grapheme(); trace!("Inserting blank {lines} lines"); let origin = self.grid.cursor.point.line; @@ -1516,6 +1476,7 @@ impl Handler for Term { #[inline] fn delete_lines(&mut self, lines: usize) { + self.seal_grapheme(); let origin = self.grid.cursor.point.line; let lines = cmp::min(self.screen_lines() - origin.0 as usize, lines); @@ -1528,6 +1489,7 @@ impl Handler for Term { #[inline] fn erase_chars(&mut self, count: usize) { + self.seal_grapheme(); let cursor = &self.grid.cursor; trace!("Erasing chars: count={}, col={}", count, cursor.point.column); @@ -1547,6 +1509,7 @@ impl Handler for Term { #[inline] fn delete_chars(&mut self, count: usize) { + self.seal_grapheme(); let columns = self.columns(); let cursor = &self.grid.cursor; let bg = cursor.template.bg; @@ -1576,6 +1539,7 @@ impl Handler for Term { #[inline] fn move_backward_tabs(&mut self, count: u16) { + self.seal_grapheme(); trace!("Moving backward {count} tabs"); let old_col = self.grid.cursor.point.column.0; @@ -1601,6 +1565,7 @@ impl Handler for Term { #[inline] fn move_forward_tabs(&mut self, count: u16) { + self.seal_grapheme(); trace!("Moving forward {count} tabs"); let num_cols = self.columns(); @@ -1628,6 +1593,7 @@ impl Handler for Term { #[inline] fn save_cursor_position(&mut self) { + self.seal_grapheme(); trace!("Saving cursor position"); self.grid.saved_cursor = self.grid.cursor.clone(); @@ -1635,6 +1601,7 @@ impl Handler for Term { #[inline] fn restore_cursor_position(&mut self) { + self.seal_grapheme(); trace!("Restoring cursor position"); self.damage_cursor(); @@ -1644,6 +1611,7 @@ impl Handler for Term { #[inline] fn clear_line(&mut self, mode: ansi::LineClearMode) { + self.seal_grapheme(); trace!("Clearing line: {mode:?}"); let cursor = &self.grid.cursor; @@ -1759,6 +1727,7 @@ impl Handler for Term { #[inline] fn clear_screen(&mut self, mode: ansi::ClearMode) { + self.seal_grapheme(); trace!("Clearing screen: {mode:?}"); let bg = self.grid.cursor.template.bg; @@ -1817,6 +1786,7 @@ impl Handler for Term { #[inline] fn clear_tabs(&mut self, mode: ansi::TabulationClearMode) { + self.seal_grapheme(); trace!("Clearing tabs: {mode:?}"); match mode { ansi::TabulationClearMode::Current => { @@ -1831,6 +1801,7 @@ impl Handler for Term { /// Reset all important fields in the term struct. #[inline] fn reset_state(&mut self) { + self.seal_grapheme(); if self.mode.contains(TermMode::ALT_SCREEN) { mem::swap(&mut self.grid, &mut self.inactive_grid); } @@ -1858,6 +1829,7 @@ impl Handler for Term { #[inline] fn reverse_index(&mut self) { + self.seal_grapheme(); trace!("Reversing index"); // If cursor is at the top. if self.grid.cursor.point.line == self.scroll_region.start { @@ -1931,6 +1903,7 @@ impl Handler for Term { #[inline] fn set_private_mode(&mut self, mode: PrivateMode) { + self.seal_grapheme(); let mode = match mode { PrivateMode::Named(mode) => mode, PrivateMode::Unknown(mode) => { @@ -1994,6 +1967,7 @@ impl Handler for Term { #[inline] fn unset_private_mode(&mut self, mode: PrivateMode) { + self.seal_grapheme(); let mode = match mode { PrivateMode::Named(mode) => mode, PrivateMode::Unknown(mode) => { @@ -2095,6 +2069,7 @@ impl Handler for Term { #[inline] fn set_mode(&mut self, mode: ansi::Mode) { + self.seal_grapheme(); let mode = match mode { ansi::Mode::Named(mode) => mode, ansi::Mode::Unknown(mode) => { @@ -2112,6 +2087,7 @@ impl Handler for Term { #[inline] fn unset_mode(&mut self, mode: ansi::Mode) { + self.seal_grapheme(); let mode = match mode { ansi::Mode::Named(mode) => mode, ansi::Mode::Unknown(mode) => { @@ -2152,6 +2128,7 @@ impl Handler for Term { #[inline] fn set_scrolling_region(&mut self, top: usize, bottom: Option) { + self.seal_grapheme(); // Fallback to the last line as default. let bottom = bottom.unwrap_or_else(|| self.screen_lines()); @@ -2177,24 +2154,28 @@ impl Handler for Term { #[inline] fn set_keypad_application_mode(&mut self) { + self.seal_grapheme(); trace!("Setting keypad application mode"); self.mode.insert(TermMode::APP_KEYPAD); } #[inline] fn unset_keypad_application_mode(&mut self) { + self.seal_grapheme(); trace!("Unsetting keypad application mode"); self.mode.remove(TermMode::APP_KEYPAD); } #[inline] fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) { + self.seal_grapheme(); trace!("Configuring charset {index:?} as {charset:?}"); self.grid.cursor.charsets[index] = charset; } #[inline] fn set_active_charset(&mut self, index: CharsetIndex) { + self.seal_grapheme(); trace!("Setting active charset {index:?}"); self.active_charset = index; } diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/GraphemeBreakTest-17.0.0.txt b/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/GraphemeBreakTest-17.0.0.txt new file mode 100644 index 00000000..e1215547 --- /dev/null +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/GraphemeBreakTest-17.0.0.txt @@ -0,0 +1,796 @@ +# GraphemeBreakTest-17.0.0.txt +# Date: 2025-03-24, 14:45:55 GMT +# © 2025 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use and license, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ +# +# Default Grapheme_Cluster_Break Test +# +# Format: +# (# )? +# contains hex Unicode code points, with +# ÷ wherever there is a break opportunity, and +# × wherever there is not. +# the format can change, but currently it shows: +# - the sample character name +# - (x) the Grapheme_Cluster_Break property value for the sample character and +# any other properties relevant to the algorithm, as described in +# GraphemeBreakTest.html +# - [x] the rule that determines whether there is a break or not, +# as listed in the Rules section of GraphemeBreakTest.html +# +# These samples may be extended or changed in the future. +# +÷ 000D ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] (CR) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 000D × 000A ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 000D ÷ 0000 ÷ # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 000D ÷ 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0308 × 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000D ÷ 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000D ÷ 0308 × 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000D ÷ 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000D ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000D ÷ 06DD ÷ # ÷ [0.2] (CR) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000D ÷ 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000D ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000D ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000D ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000D ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000D ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000D ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000D ÷ 00A9 ÷ # ÷ [0.2] (CR) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000D ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] (CR) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 000A ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] (LF) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 000A ÷ 0000 ÷ # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 000A ÷ 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0308 × 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000A ÷ 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000A ÷ 0308 × 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000A ÷ 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000A ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000A ÷ 06DD ÷ # ÷ [0.2] (LF) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000A ÷ 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000A ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000A ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000A ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000A ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000A ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000A ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000A ÷ 00A9 ÷ # ÷ [0.2] (LF) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000A ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] (CR) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0000 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] (LF) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0000 ÷ 0000 ÷ # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0000 ÷ 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0308 × 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0308 × 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0000 ÷ 0308 × 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0000 ÷ 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0000 ÷ 0308 × 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0000 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0000 ÷ 06DD ÷ # ÷ [0.2] (Control) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0000 ÷ 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0000 ÷ 0308 × 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0000 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0000 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0000 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0000 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0000 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0000 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0000 ÷ 00A9 ÷ # ÷ [0.2] (Control) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0000 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 094D × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 094D ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 094D × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 094D ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 094D × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 094D × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 094D × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 094D × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 094D × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 094D × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 094D × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 094D × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 094D × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 094D ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 094D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 094D ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 094D × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 094D × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 094D × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 094D ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 094D × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 094D ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 094D × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 094D ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 094D × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 094D ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 094D × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 094D ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 094D × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 094D ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 094D × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 094D ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 094D ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0300 ÷ 0000 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0300 × 0308 ÷ 0000 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0300 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0300 × 0308 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0300 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0300 × 0308 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0300 ÷ 06DD ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0300 × 0308 ÷ 06DD ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0300 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0300 × 0308 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0300 ÷ 00A9 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 00A9 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (CR) ÷ [0.3] +÷ 200C × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 200C ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (LF) ÷ [0.3] +÷ 200C × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 200C ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (Control) ÷ [0.3] +÷ 200C × 0308 ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 200C × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200C × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200C × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200C × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200C × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200C × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200C × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200C × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200C ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200C × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200C ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200C × 0308 ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200C × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200C × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200C ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200C × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200C ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200C × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200C ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200C × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200C ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200C × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200C ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200C × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200C ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200C × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200C ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200C ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (CR) ÷ [0.3] +÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (LF) ÷ [0.3] +÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 200D ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (Control) ÷ [0.3] +÷ 200D × 0308 ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 200D × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200D × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200D × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200D × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200D ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200D × 0308 ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200D ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200D × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200D ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CR) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 1F1E6 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (LF) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1F1E6 ÷ 0000 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0000 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1F1E6 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0308 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1F1E6 × 0308 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1F1E6 × 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1F1E6 ÷ 06DD ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 06DD ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1F1E6 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1F1E6 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1F1E6 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1F1E6 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1F1E6 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1F1E6 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1F1E6 ÷ 00A9 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 00A9 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD ÷ 000D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (CR) ÷ [0.3] +÷ 06DD × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 06DD ÷ 000A ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (LF) ÷ [0.3] +÷ 06DD × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 06DD ÷ 0000 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (Control) ÷ [0.3] +÷ 06DD × 0308 ÷ 0000 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 06DD × 094D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 06DD × 0308 × 094D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 06DD × 0300 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 06DD × 0308 × 0300 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 06DD × 200C ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 06DD × 0308 × 200C ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 06DD × 200D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 06DD × 0308 × 200D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 06DD × 1F1E6 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 06DD × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 06DD × 06DD ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 06DD × 0308 ÷ 06DD ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 06DD × 0903 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 06DD × 0308 × 0903 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 06DD × 1100 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 06DD × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 06DD × 1160 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 06DD × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 06DD × 11A8 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 06DD × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 06DD × AC00 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 06DD × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 06DD × AC01 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 06DD × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 06DD × 0915 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 06DD × 0308 ÷ 0915 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 06DD × 00A9 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 00A9 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 06DD × 0020 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0378 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] +÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0903 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] +÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0903 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] +÷ 0903 × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0903 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0903 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0903 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0903 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0903 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0903 × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0903 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0903 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0903 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0903 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0903 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0903 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0903 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0903 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0903 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CR) ÷ [0.3] +÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 1100 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (LF) ÷ [0.3] +÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1100 ÷ 0000 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] +÷ 1100 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1100 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1100 × 0308 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1100 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1100 × 0308 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1100 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1100 ÷ 06DD ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1100 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1100 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1100 × 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1100 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1100 × AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1100 × AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1100 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1100 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1100 ÷ 00A9 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (CR) ÷ [0.3] +÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 1160 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (LF) ÷ [0.3] +÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1160 ÷ 0000 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] +÷ 1160 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1160 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1160 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1160 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1160 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1160 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1160 ÷ 06DD ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1160 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1160 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1160 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1160 × 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1160 × 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1160 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1160 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1160 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1160 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1160 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (CR) ÷ [0.3] +÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 11A8 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (LF) ÷ [0.3] +÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 11A8 ÷ 0000 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 11A8 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 11A8 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 11A8 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 11A8 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 11A8 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 11A8 ÷ 06DD ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 11A8 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 11A8 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 11A8 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 11A8 × 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 11A8 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 11A8 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 11A8 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 11A8 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (CR) ÷ [0.3] +÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ AC00 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (LF) ÷ [0.3] +÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ AC00 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] +÷ AC00 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ AC00 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC00 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC00 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC00 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC00 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC00 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC00 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC00 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC00 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC00 × 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC00 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC00 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC00 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC00 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC00 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC00 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (CR) ÷ [0.3] +÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ AC01 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (LF) ÷ [0.3] +÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ AC01 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] +÷ AC01 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ AC01 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC01 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC01 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC01 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC01 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC01 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC01 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC01 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC01 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC01 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC01 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC01 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC01 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC01 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC01 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC01 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (CR) ÷ [0.3] +÷ 0915 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0915 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (LF) ÷ [0.3] +÷ 0915 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0915 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3] +÷ 0915 × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0915 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0915 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0915 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0915 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0915 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0915 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0915 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0915 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0915 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0915 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0915 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0915 × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0915 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0915 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0915 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0915 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0915 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0915 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0915 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0915 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0915 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0915 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0915 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0915 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0915 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0915 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0915 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 ÷ 000D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 00A9 × 0308 ÷ 000D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 00A9 ÷ 000A ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 00A9 × 0308 ÷ 000A ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 00A9 ÷ 0000 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0000 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 00A9 × 094D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 00A9 × 0308 × 094D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 00A9 × 0300 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 00A9 × 0308 × 0300 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 00A9 × 200C ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 00A9 × 0308 × 200C ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 00A9 × 200D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 00A9 × 0308 × 200D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 00A9 ÷ 1F1E6 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 00A9 ÷ 06DD ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 00A9 × 0308 ÷ 06DD ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 00A9 × 0903 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 00A9 × 0308 × 0903 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 00A9 ÷ 1100 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1100 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 00A9 ÷ 1160 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1160 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 00A9 ÷ 11A8 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 00A9 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 00A9 ÷ AC00 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 00A9 × 0308 ÷ AC00 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 00A9 ÷ AC01 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 00A9 × 0308 ÷ AC01 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 00A9 ÷ 0915 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0915 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 00A9 ÷ 00A9 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 00A9 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 00A9 ÷ 0020 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0020 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 ÷ 0378 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0378 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 ÷ 000D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0020 ÷ 000A ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0020 ÷ 0000 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 0020 × 0308 ÷ 0000 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0020 × 094D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0020 × 0308 × 094D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 200C ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0020 × 0308 × 200C ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0020 ÷ 06DD ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0020 × 0308 ÷ 06DD ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0020 × 0903 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0020 ÷ 1100 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0020 ÷ 1160 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0020 ÷ 11A8 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0020 ÷ AC00 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0020 ÷ AC01 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0020 ÷ 0915 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0020 × 0308 ÷ 0915 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0020 ÷ 00A9 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 00A9 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0020 ÷ 0020 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 ÷ 000D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0378 ÷ 000A ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0378 ÷ 0000 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 0378 × 0308 ÷ 0000 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0378 × 094D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0378 × 0308 × 094D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0378 × 0300 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0378 × 200C ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0378 × 0308 × 200C ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0378 × 200D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0378 ÷ 06DD ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0378 × 0308 ÷ 06DD ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0378 × 0903 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0378 ÷ 1100 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0378 ÷ 1160 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0378 ÷ 11A8 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0378 ÷ AC00 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0378 ÷ AC01 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0378 ÷ 0915 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0378 × 0308 ÷ 0915 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0378 ÷ 00A9 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 00A9 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0378 ÷ 0020 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 ÷ 0378 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC LETTER NOON (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC00 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC01 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 × 1F1E9 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 0903 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 0600 × 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F476 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) ÷ [0.3] +÷ 0061 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) ÷ [0.3] +÷ 0061 × 1F3FF ÷ 1F476 × 200D × 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 1F476 × 1F3FF × 0308 × 200D × 1F476 × 1F3FF ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F6D1 × 200D × 1F6D1 ÷ # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 2701 × 200D ÷ 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 093C × 200D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 093C × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 0924 × 094D × 092F ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER YA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D ÷ 0061 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 094D ÷ 0924 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 003F × 094D ÷ 0924 ÷ # ÷ [0.2] QUESTION MARK (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0AB8 × 0AFB × 0ACD × 0AB8 × 0AFB ÷ # ÷ [0.2] GUJARATI LETTER SA (LinkingConsonant) × [9.0] GUJARATI SIGN SHADDA (Extend_ConjunctExtendermConjunctLinker) × [9.0] GUJARATI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] GUJARATI LETTER SA (LinkingConsonant) × [9.0] GUJARATI SIGN SHADDA (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1019 × 1039 × 1018 ÷ 102C × 1037 ÷ # ÷ [0.2] MYANMAR LETTER MA (LinkingConsonant) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER BHA (LinkingConsonant) ÷ [999.0] MYANMAR VOWEL SIGN AA (XXmLinkingConsonantmExtPict) × [9.0] MYANMAR SIGN DOT BELOW (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1004 × 103A × 1039 × 1011 × 1039 × 1011 ÷ # ÷ [0.2] MYANMAR LETTER NGA (LinkingConsonant) × [9.0] MYANMAR SIGN ASAT (Extend_ConjunctExtendermConjunctLinker) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER THA (LinkingConsonant) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER THA (LinkingConsonant) ÷ [0.3] +÷ 1B12 × 1B01 ÷ 1B32 × 1B44 × 1B2F ÷ 1B32 × 1B44 × 1B22 × 1B44 × 1B2C ÷ 1B32 × 1B44 × 1B22 × 1B38 ÷ # ÷ [0.2] BALINESE LETTER OKARA TEDUNG (XXmLinkingConsonantmExtPict) × [9.0] BALINESE SIGN ULU CANDRA (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER WA (LinkingConsonant) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER TA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER YA (LinkingConsonant) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER TA (LinkingConsonant) × [9.0] BALINESE VOWEL SIGN SUKU (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 179F × 17D2 × 178F × 17D2 × 179A × 17B8 ÷ # ÷ [0.2] KHMER LETTER SA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER LETTER TA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER LETTER RO (LinkingConsonant) × [9.0] KHMER VOWEL SIGN II (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1B26 ÷ 1B17 × 1B44 × 1B13 ÷ # ÷ [0.2] BALINESE LETTER NA (LinkingConsonant) ÷ [999.0] BALINESE LETTER NGA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1B27 ÷ 1B13 × 1B44 × 1B0B ÷ 1B0B × 1B04 ÷ # ÷ [0.2] BALINESE LETTER PA (LinkingConsonant) ÷ [999.0] BALINESE LETTER KA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER RA REPA (LinkingConsonant) ÷ [999.0] BALINESE LETTER RA REPA (LinkingConsonant) × [9.1] BALINESE SIGN BISAH (SpacingMark) ÷ [0.3] +÷ 1795 × 17D2 × 17AF ÷ 1798 ÷ # ÷ [0.2] KHMER LETTER PHA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER INDEPENDENT VOWEL QE (LinkingConsonant) ÷ [999.0] KHMER LETTER MO (LinkingConsonant) ÷ [0.3] +÷ 17A0 × 17D2 × 17AB ÷ 1791 × 17D0 ÷ 1799 ÷ # ÷ [0.2] KHMER LETTER HA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER INDEPENDENT VOWEL RY (LinkingConsonant) ÷ [999.0] KHMER LETTER TO (LinkingConsonant) × [9.0] KHMER SIGN SAMYOK SANNYA (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] KHMER LETTER YO (LinkingConsonant) ÷ [0.3] +# +# Lines: 766 +# +# EOF diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt b/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt new file mode 100644 index 00000000..56da5891 --- /dev/null +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt @@ -0,0 +1,39 @@ +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2026 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. diff --git a/experiments/core/CANDIDATE.md b/experiments/core/CANDIDATE.md index 1800c7f7..36d9f244 100644 --- a/experiments/core/CANDIDATE.md +++ b/experiments/core/CANDIDATE.md @@ -1,6 +1,6 @@ # 候选 C++ 终端适配器 -`CandidateTerminal` 是 #27 的拥有值适配层,不是自研核心的替代品声明。它把固定 `0.26.1-dev+termforge.3` 的 `Term/Processor` 接到现有 `Screen`/`Line`;默认 CMake 保留自研核心且不需要 Rust,候选产品由 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 显式启用。 +`CandidateTerminal` 是 #27 的拥有值适配层,不是自研核心的替代品声明。它把固定 `0.26.1-dev+termforge.4` 的 `Term/Processor` 接到现有 `Screen`/`Line`;默认 CMake 保留自研核心且不需要 Rust,候选产品由 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 显式启用。 ## 构建与验证 @@ -19,6 +19,7 @@ Linux 使用相应 CMake 配置。候选 workflow 在 Windows/Ubuntu 构建 `can - `feed` 以 1 MiB 为单次上限;`resize` 有独立尺寸检查;`finish` 保留当前候选 trace 的 no-op 语义,不宣称刷新不完整序列。 - 每次 feed 同步网格并排干事件。候选 `ShellWindow` 将 `PtyWrite` 排入现有有界 `InputQueue`,不直接写 ConPTY;本切片仅保留标题状态、处理 Bell 及已明确忽略的事件,未声明支持的事件不会被静默当成成功。 - C++ 同步历史/可见行、稳定 ID、逐行版本、样式、光标、软硬换行、next_id/wrap_columns 和实际淘汰数。普通 reset 保留 ID,滚动复用行更新 ID;初始 Storage 方向也显式检查。 +- `marks` 是 lead 后附加标量数,复杂字素中可含独立宽度非零的标量,并非 Unicode Mark 类计数。clip、fragment、leading_spacer 与饱和 overflowcounter 在 C ABI 和拥有值中显式保留;leading padding 仍绘制背景,只有文本投影跳过它,真实写入的空格不被 trim。 - 普通空单元格保留一个可绘制背景格,只有宽字符 spacer 是零宽。未写格与显式空格的语义仍受上游 `is_empty` 限制,不能由当前投影裁决;真彩色等非 16 色样式仍为 `-2`。 - 不走 JSON。Rust/C++ 文本由调用方拥有,未做增量 FFI 或性能验收;候选 damage 的产品化仍属后续。 - `screen.resize` 与候选 `Term::resize` 同步尺寸;默认自研回退继续采用 ADR-0005 的物理行裁剪,候选不把它称为完整 reflow。真实 ConPTY 重放可能先由上游把历史拉入可见区再覆盖,候选在完整重放后合并有界历史,按 ID 和完整行键去重,保留 Find/选区所需的稳定身份,且不让冲突产生重复 ID。连续同步缓存原始 FFI ID 与最终身份的映射,仅在需要重放的未知旧 ID 时回退到完整键匹配,不重复扫描全部历史。真实淘汰不恢复,显式清空不复活;普通空白行、未决 reflow、备用屏/滚动区全语义不在此保证内。合同与验证见 [resize/历史记录](results/2026-10-26-resize-replay/validation.json)。 @@ -33,7 +34,7 @@ Linux 使用相应 CMake 配置。候选 workflow 在 Windows/Ubuntu 构建 `can `candidate-margins` 固定主屏 `2;3r`、备用屏 `4;5r`,保存源 VT、四阶段真实控制台快照和 transport。[限定证据](../shell/results/2026-10-26-margin-state/validation.json)分别检查公开 feed 的主屏 margin 恢复、resize/RIS 边界与真实 ConPTY 的宿主差异。源 VT 的整段/逐字节候选回归通过;当前真实 ConPTY 只恢复主屏内容/样式/光标,未恢复主屏 margin,最终出现 `REGION_CIXED`,由 [#41](https://github.com/JavaZeroo/termforge/issues/41) 保留。真实观察返回 `margins_per_buffer_verified=false`,不是能力通过;源 LF 保持列与 transport 的 CRLF 分别核对,未修改输入或注入恢复命令。 -`candidate-unicode` 另以真实辅助进程输出固定分解组合、单 emoji、ZWJ 和 keycap,保存源字节、真实控制台 `CHAR_INFO`、ConPTY transport、候选单元格及实际 DWrite glyph。两轮[限定验证](../shell/results/2026-10-26-unicode-renderer/validation.json)要求原文保留、glyph 无缺字且布局不超出原网格;过宽文字只缩小字号。当前 ZWJ 分格和 keycap 宽度是观测结果,不是完整聚类合同;完整流式字素与宽度由 [#39](https://github.com/JavaZeroo/termforge/issues/39) 阻塞,不能将本场景写成完整 Unicode/IME 验收。 +`candidate-unicode` v2 使用真实辅助进程输出固定分解组合、单 emoji、ZWJ 和 keycap,保存源字节、真实控制台 `CHAR_INFO`、ConPTY transport、候选单元格及实际 DWrite glyph。按[流式字素合同](../../docs/research/streaming-graphemes.md),两阶段检查 80×24→100×30 的子控制台尺寸确认、单 lead/两格宽度、产品拖尾选择复制、实际 1 glyph/Segoe UI Emoji 与网格适配。旧 [v1 原文/glyph 证据](../shell/results/2026-10-26-unicode-renderer/validation.json)保留原始分格观测;它不覆盖 v2,也不改写为聚类通过。当前 v2 验证限定首批四样本,不是完整 Unicode/IME、所有脚本或 WindTerm 运行时等价。 原有 `runtime_observation` 也在候选适配构建运行,保留滚动后的逐行内容、retained/full RGB 一致、no-op 状态端点及会话清理断言。所有此类检查只使用自有窗口的 DIB,不截图,`presented_done` 仍未知。 diff --git a/experiments/core/candidate_terminal.cpp b/experiments/core/candidate_terminal.cpp index bc9b67e3..cc12246e 100644 --- a/experiments/core/candidate_terminal.cpp +++ b/experiments/core/candidate_terminal.cpp @@ -13,7 +13,8 @@ namespace { bool same_cell(const Cell& a, const Cell& b) { return a.text == b.text && a.width == b.width && a.style.fg == b.style.fg && a.style.bg == b.style.bg && a.style.bold == b.style.bold && a.marks == b.marks && - a.written == b.written; + a.written == b.written && a.clipped == b.clipped && + a.grapheme_fragment == b.grapheme_fragment && a.leading_spacer == b.leading_spacer; } bool same_line(const Line& a, const Line& b) { @@ -76,7 +77,8 @@ std::string replay_key(const Line& line) { for (size_t index = first; index < last; ++index) { const auto& cell = line[index]; out << cell.text.size() << ':' << cell.text << cell.width << ',' << cell.style.fg << ',' - << cell.style.bg << ',' << cell.style.bold << ',' << cell.marks << ',' << cell.written << ';'; + << cell.style.bg << ',' << cell.style.bold << ',' << cell.marks << ',' << cell.written + << ',' << cell.clipped << ',' << cell.grapheme_fragment << ',' << cell.leading_spacer << ';'; } return out.str(); } @@ -285,6 +287,7 @@ void CandidateTerminal::synchronize() { TfTerminalMeta meta{}; check(tf_terminal_meta(impl_, &meta), "candidate metadata failed"); mode_bits_ = meta.mode_bits; + grapheme_overflows_ = meta.grapheme_overflows; std::unordered_map previous; for (const auto& line : screen.history) previous.emplace(line.id, &line); for (const auto& line : screen.lines) previous.emplace(line.id, &line); @@ -306,7 +309,7 @@ void CandidateTerminal::synchronize() { check(tf_terminal_cell_meta(impl_, r, c, &cell), "candidate cell metadata failed"); std::string text = cell_text(impl_, r, c, cell.text_len); if (text.empty() && cell.width != 0) text = " "; - line[c] = {std::move(text), cell.width, {cell.fg, cell.bg, cell.bold}, cell.marks, cell.written != 0}; + line[c] = {std::move(text), cell.width, {cell.fg, cell.bg, cell.bold}, cell.marks, cell.written != 0, cell.clipped, cell.grapheme_fragment, cell.leading_spacer}; } retained[r] = std::move(line); } diff --git a/experiments/core/candidate_terminal.hpp b/experiments/core/candidate_terminal.hpp index 97db99eb..55de3176 100644 --- a/experiments/core/candidate_terminal.hpp +++ b/experiments/core/candidate_terminal.hpp @@ -16,7 +16,7 @@ struct CandidateEvent { class CandidateTerminal { void* impl_ = nullptr; - uint64_t mode_bits_ = 0; + uint64_t mode_bits_ = 0, grapheme_overflows_ = 0; std::unordered_map versions_; std::vector events_; struct IdentityAlias { uint64_t id = 0, version = 0; }; @@ -46,6 +46,7 @@ class CandidateTerminal { void finish(); void resize(int rows, int columns); uint64_t mode_bits() const { return mode_bits_; } + uint64_t grapheme_overflows() const { return grapheme_overflows_; } std::vector take_events(); }; } // namespace termforge diff --git a/experiments/core/candidate_terminal_test.cpp b/experiments/core/candidate_terminal_test.cpp index 7ae9b805..8f1082c0 100644 --- a/experiments/core/candidate_terminal_test.cpp +++ b/experiments/core/candidate_terminal_test.cpp @@ -48,6 +48,125 @@ void fragmentation() { !whole.screen.history[0][7].written, "unused cell must retain its paintable width"); } +void streaming_graphemes_selection_and_history() { + const std::string zwj = "👩\xe2\x80\x8d💻", keycap = "1\xef\xb8\x8f\xe2\x83\xa3"; + for (const bool fragmented : {false, true}) { + termforge::CandidateTerminal terminal(3, 16, 8); + const auto feed = [&](const std::string& input) { + if (fragmented) for (const char byte : input) terminal.feed(std::string_view(&byte, 1)); + else terminal.feed(input); + }; + feed("A" + zwj + keycap + "Z\r\n"); + const auto& row = terminal.screen.lines[0]; + require(row[1].text == zwj && row[1].width == 2 && row[2].width == 0 && + row[3].text == keycap && row[3].width == 2 && row[4].width == 0 && + row[5].text == "Z", "streaming whole-cluster cell layout differs"); + require(termforge::project_line(row).text == "A" + zwj + keycap + "Z", + "whole-cluster literal projection differs"); + termforge::TextDocument document(terminal.screen); + termforge::TextSelection selection; + require(selection.set(document, {*document.position(0, 2), *document.position(0, 4)}), + "inside-trailer selection failed"); + require(selection.copy(document, 1024).text == zwj + keycap && + selection.range()->begin.column == 1 && selection.range()->end.column == 5, + "selection split a complex grapheme"); + terminal.resize(3, 20); + require(selection.reconcile(document) && selection.copy(document, 1024).text == zwj + keycap, + "width-only resize lost whole grapheme selection"); + feed("B\r\nC\r\nD"); + require(terminal.screen.history.size() == 1 && + termforge::project_line(terminal.screen.history[0]).text == "A" + zwj + keycap + "Z" && + selection.reconcile(document) && selection.copy(document, 1024).text == zwj + keycap, + "complex grapheme history or retained selection differs"); + } + termforge::CandidateTerminal clipped(3, 4, 8); + clipped.feed("\x1b[?7lABC" + zwj); + require(clipped.screen.lines[0][3].text == zwj && clipped.screen.lines[0][3].width == 1 && + clipped.screen.lines[0][3].clipped, "clipped grapheme metadata or scalar retention differs"); + termforge::CandidateTerminal resized_one(3, 4, 8); + resized_one.feed(zwj); + resized_one.resize(3, 1); + require(resized_one.screen.lines[0][0].text == zwj && resized_one.screen.lines[0][0].width == 1 && + resized_one.screen.lines[0][0].clipped && resized_one.screen.row == 0 && + resized_one.screen.column == 0 && resized_one.screen.wrap_pending, + "resize to one column lost text, clipping metadata or cursor"); + termforge::CandidateTerminal bounded(3, 16, 8); + std::string long_marks = "e"; + for (int index = 0; index < 200; ++index) long_marks += "\xcc\x81"; + bounded.feed(long_marks); + require(termforge::project_line(bounded.screen.lines[0]).text == long_marks && + bounded.grapheme_overflows() == 3 && bounded.screen.lines[0][1].grapheme_fragment, + "bounded grapheme spill discarded text or diagnostics"); + termforge::TextDocument document(bounded.screen); + termforge::TextSelection selection; + require(selection.select_line(document, 0) && selection.copy(document, 4096).text == long_marks, + "bounded grapheme fragments did not copy the original literal text"); +} + +void streaming_margin_padding_is_not_copied_as_an_inserted_space() { + const std::string keycap = "1\xef\xb8\x8f\xe2\x83\xa3"; + for (const bool fragmented : {false, true}) { + for (const std::string prefix : {"ABC", "ABC "}) { + termforge::CandidateTerminal terminal(3, 4, 8); + const std::string literal = prefix + keycap; + if (fragmented) for (const char byte : literal) terminal.feed(std::string_view(&byte, 1)); + else terminal.feed(literal); + require(terminal.screen.lines[0][3].leading_spacer == (prefix == "ABC"), + "actual written space and leading-wide padding were conflated"); + termforge::TextDocument document(terminal.screen); + termforge::TextSelection selection; + require(selection.set(document, {*document.position(0, 0), *document.position(1, 2)}) && + selection.copy(document, 1024).text == literal, + "margin growth copy inserted padding or removed a literal space"); + terminal.resize(3, 8); + require(termforge::project_line(terminal.screen.lines[0]).text == literal && + selection.select_line(document, 0) && selection.copy(document, 1024).text == literal, + "resize copied padding or lost a true space"); + terminal.feed("\r\nD\r\nE\r\nF"); + require(terminal.screen.history.size() == 1 && + termforge::project_line(terminal.screen.history[0]).text == literal && + selection.select_line(document, 0) && selection.copy(document, 1024).text == literal, + "history copied padding or lost original margin text"); + } + } +} + +void prepend_and_inserted_wide_trailer_copy() { + const std::string prepend = "\xdc\x8f"; + for (const bool fragmented : {false, true}) { + termforge::CandidateTerminal prefix(3, 8, 8); + const std::string literal = prepend + "A"; + if (fragmented) for (const char byte : literal) prefix.feed(std::string_view(&byte, 1)); + else prefix.feed(literal); + termforge::TextDocument prefix_document(prefix.screen); + termforge::TextSelection prefix_selection; + require(prefix.screen.lines[0][0].text == literal && prefix_selection.select_line(prefix_document,0) && + prefix_selection.copy(prefix_document,1024).text == literal, + "zero-width Prepend scalar disappeared from terminal or copied text"); + for (const auto& pair : std::vector>{ + {"X"," X ABCDE"}, {"1\xef\xb8\x8f\xe2\x83\xa3", " 1\xef\xb8\x8f\xe2\x83\xa3 ABCD"}, + {"⌚\xef\xb8\x8e", " ⌚\xef\xb8\x8e ABCD"}}) { + termforge::CandidateTerminal terminal(3,8,8); + terminal.feed("中ABCDEF\x1b[1;2H\x1b[4h"); + const auto check_pairs = [&] { + const auto& line = terminal.screen.lines[0]; + for (size_t column=0;column(cell.width))}); diff --git a/experiments/measure/README.md b/experiments/measure/README.md index 7982304a..be60aa8e 100644 --- a/experiments/measure/README.md +++ b/experiments/measure/README.md @@ -41,6 +41,8 @@ python experiments/measure/compare.py --route interactive --out local/core-inter 普通 C++ `new` 与 Rust global allocator 统计范围不同,不是等价内存指标;peak working set 包含整个进程准备/检查。观察开启/关闭不是无插桩 A/B,任何差值都不扣除。当前仅覆盖固定 ASCII 长输出与编辑回放,无插桩对照、真实输入/接收/呈现、跨平台与维护成本另审;`performance_gate=not_evaluated`,不能据此作最终路线决定或 WindTerm 性能结论。 +候选 `.4` 为完整 trace 投影新增 `grapheme_overflows: 0`,因此 project / interactive 的全对象摘要分别从 `11966868740884721388` / `8035857386658397897` 迁移为 `16178973312633948535` / `6154007251599136798`。屏幕、历史、光标、样式、模式和回复逐字段不变;字段继续进入哈希,不排除计数器来维持旧摘要。独立构造的完整固定负载状态、旧/新摘要迁移及实际 trace 对象由 `test_compare.py` 验证,实际计量批次继续覆盖全部块大小与观察开关,错误摘要继续拒绝。旧结果原样保留;首次 CI 红反馈及身份见[迁移记录](results/2026-10-26-projection-digest/validation.json)。这次修复仅更新诊断身份合同,没有改变产品、负载、计时或性能验收标准。 + ## 校准能回答什么 `core_measure calibrate seconds [observe_first]` 提供两个可重复诊断: diff --git a/experiments/measure/compare.py b/experiments/measure/compare.py index fb1eccc8..8b29f47c 100644 --- a/experiments/measure/compare.py +++ b/experiments/measure/compare.py @@ -17,7 +17,9 @@ from experiments.candidates.alacritty.run import vendor_sources from experiments.measure.run import EXPECTED_STATE, execute -CANDIDATE_STATE = "11966868740884721388" +# Full snapshot identity includes grapheme_overflows:0 since termforge.4. +# test_compare independently derives both fixture states and the legacy hashes. +CANDIDATE_STATE = "16178973312633948535" ENDPOINT = ("same 78,000-byte project workload and continuous interval; observation off excludes projection, " "observation on includes projection/JSON and allocation counting; counters are not an uninstrumented baseline") WORKLOADS = { @@ -30,7 +32,7 @@ bytes_per_round=22000, endpoint=("same 22,000-byte interactive workload and continuous interval; observation off excludes projection, " "observation on includes projection/JSON and allocation counting; counters are not an uninstrumented baseline"), - states=dict(termforge="8292075482910706113", alacritty="8035857386658397897")), + states=dict(termforge="8292075482910706113", alacritty="6154007251599136798")), } diff --git a/experiments/measure/results/2026-10-26-projection-digest/validation.json b/experiments/measure/results/2026-10-26-projection-digest/validation.json new file mode 100644 index 00000000..78e007c6 --- /dev/null +++ b/experiments/measure/results/2026-10-26-projection-digest/validation.json @@ -0,0 +1,95 @@ +{ + "schema": "termforge-measure-projection-migration-v1", + "ci_red": { + "run_id": 40292096700, + "attempt": 1, + "head_sha": "23616d13c6f16e5ad1ca671cafc45dd166788a24", + "checkout_merge_sha": "cb001e0825bf591a54d068322a6033b8974002a7", + "url": "https://github.com/JavaZeroo/termforge/actions/runs/40292096700", + "jobs": [ + { + "id": 121469315574, + "platform": "ubuntu-24.04" + }, + { + "id": 121469315818, + "platform": "windows-2025" + } + ], + "first_failed_step": 28, + "step": "Compare interleaved project costs", + "error": "RuntimeError: unexpected state digest", + "original_decoded_logs": { + "local_retention": "local/ci-r39-red-40292096700; GitHub decoded log text preserved with LF newlines", + "sha256": { + "121469315574": "45c3dc8dc9476ef7290554c134a64fb1d9d4f63fa8c37c28c6b03d302e928a9b", + "121469315818": "d401dc3561a751da5cff61fe3273c0778c1749f97ee620b54fd01ad44f54f550" + } + } + }, + "cause": "The full snapshot gained grapheme_overflows:0; all other fields are byte-equivalent after canonical serialization. Obsolete fixed full-projection digests caused the measurement contract failure; not a terminal state mismatch.", + "baseline_git_commit": "65988f5f20f62294ff52fcac5b5b81e563f93f3b", + "candidate_product_freeze": "821b9e6282e41822d5c977914aeca10fe8329c98", + "routes": { + "project": { + "baseline_digest": "11966868740884721388", + "candidate_digest": "16178973312633948535", + "only_difference": { + "grapheme_overflows": [ + null, + 0 + ] + }, + "binaries": { + "baseline": "d74970c2101fa05f9d894285937e78662ea9a84690be1e60844c0f0f6cbb6663", + "candidate": "6dbe42cfefc8b5df64e652193d3b5863fbf679f12ccfa2e422d9ca1e9e1a5c3d" + }, + "fixture_bytes": 78000, + "fixture_sha256": "02ff60ca95c13ab3e41909685634ee6a800310dc39a07a98ac6965b0c57f2b14" + }, + "interactive": { + "baseline_digest": "8035857386658397897", + "candidate_digest": "6154007251599136798", + "only_difference": { + "grapheme_overflows": [ + null, + 0 + ] + }, + "binaries": { + "baseline": "d74970c2101fa05f9d894285937e78662ea9a84690be1e60844c0f0f6cbb6663", + "candidate": "6dbe42cfefc8b5df64e652193d3b5863fbf679f12ccfa2e422d9ca1e9e1a5c3d" + }, + "fixture_bytes": 22000, + "fixture_sha256": "405d574c15d33d81e17745173dac95ed8c0082884e9692c71fbc67b5a588c148" + } + }, + "independent_expectation": { + "description": "Literal 24x80 screen: 23 completed rows plus one empty row, 100 completed history rows, cursor23:0, default style/modes, no replies, no overflow; project completed row is 76 x; interactive row is abcdeQgh.", + "full_object_equal": true, + "legacy_hash_reproduced": true, + "counter_included_in_full_hash": true, + "counter_mutation_changes_hash": true, + "actual_all_chunk_measurement_and_observer_cases_passed": true, + "wrong_digest_negatives_still_reject": true + }, + "verification": { + "red_command": "python -m unittest experiments.measure.test_compare.CandidateProjectionIdentityTests -v", + "red_tests": 2, + "red_subtest_failures": 4, + "red_log_sha256": "b1ab5c5279ed8863c3769f805ca4c3084b8b5513b4530945ee2fe3cfa417f83b", + "green_command": "python -m unittest experiments.measure.test_compare experiments.measure.test_measure -v", + "green_tests": 17, + "green_failed": 0, + "green_skipped": 0, + "green_seconds": 19.489, + "green_log_sha256": "abcbc4692fe63f1142537452e23f8c9d4d100633e90e24f4b1dcb6a59c224df1" + }, + "source_sha256": { + "experiments/measure/compare.py": "d165b63b694dc6c8127a7dd5610a9660a774dbef0603be482dd9512babe726fb", + "experiments/measure/test_compare.py": "7cb017383a8f6dc354586ac643b38e29bfdebb9e1d01607d04210480ce1e2718", + "experiments/measure/README.md": "fe5e494d7e6492ba9f13681e748eae58bdfe489abcb6480ae5b5e2ec338cc55f" + }, + "changes": "Only fixed digest identities, independent regression tests, documentation and proof; product, fixture, timer, rejection gates and historical results unchanged.", + "performance_gate": "not_evaluated" +} diff --git a/experiments/measure/test_compare.py b/experiments/measure/test_compare.py index dd3a6467..9f625691 100644 --- a/experiments/measure/test_compare.py +++ b/experiments/measure/test_compare.py @@ -1,4 +1,5 @@ """Candidate measurement contracts and interleaved raw-sample gates; no speed assertions.""" +import copy import json from pathlib import Path import tempfile @@ -6,6 +7,69 @@ from experiments.measure.compare import CANDIDATE_STATE, WORKLOADS, candidate_binary, run_batch from experiments.measure.run import execute +from experiments.candidates.alacritty.run import run_trace, trace_binary + + +def expected_candidate_projection(route): + """Derive the fixed fixture's terminal state without replaying a parser. + + Each of 1,000 CR/LF-terminated rows fills fewer than 80 columns. The final + screen has 23 completed rows and one empty row; the last 100 completed rows + occupy history. Interactive editing changes abcdefgh to abcdeQgh. Neither + fixture changes default style/modes, generates replies or overflows an EGC. + """ + text = "x" * 76 if route == "project" else "abcdeQgh" + style = dict(fg=-1, bg=-1, bold=False) + + def row(value): + return [dict(text=value[c] if c < len(value) else "", + width=1 if c < len(value) else 0, style=style.copy()) + for c in range(80)] + + return dict(offset=0, mode_bits=163969, replies=[], grapheme_overflows=0, + screen=dict(row=23, column=0, wrap_pending=False, + current_style=style, cells=[row(text) for _ in range(23)] + [row("")], + history=[row(text) for _ in range(100)])) + + +def projection_digest(value): + # serde_json's default map is key-sorted and serializes compact UTF-8. + result = 14695981039346656037 + for byte in json.dumps(value, sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode("utf-8"): + result = ((result ^ byte) * 1099511628211) & ((1 << 64) - 1) + return str(result) + + +class CandidateProjectionIdentityTests(unittest.TestCase): + def test_overflow_counter_migrates_the_full_projection_digest(self): + legacy = dict(project="11966868740884721388", interactive="8035857386658397897") + for route, workload in WORKLOADS.items(): + with self.subTest(route=route): + expected = expected_candidate_projection(route) + old = copy.deepcopy(expected) + del old["grapheme_overflows"] + self.assertEqual(projection_digest(old), legacy[route]) + self.assertEqual(projection_digest(expected), workload["states"]["alacritty"]) + # Keep the diagnostic field in the hash; do not exempt it to + # preserve an obsolete identity or accept a corrupted counter. + changed = copy.deepcopy(expected) + changed["grapheme_overflows"] = 1 + self.assertNotEqual(projection_digest(changed), workload["states"]["alacritty"]) + + def test_actual_fixture_projection_matches_independent_complete_state(self): + candidate = trace_binary() + with tempfile.TemporaryDirectory() as folder: + for route, workload in WORKLOADS.items(): + with self.subTest(route=route): + payload = workload["fixture"] + report, records = run_trace(candidate, payload, 24, 80, 100, [len(payload)], + Path(folder) / route) + self.assertTrue(report["passed"], report) + actual = records[-1] + self.assertEqual(actual["offset"], len(payload)) + actual["offset"] = 0 # measure.rs snapshots at observation offset zero. + self.assertEqual(actual, expected_candidate_projection(route)) + self.assertEqual(projection_digest(actual), workload["states"]["alacritty"]) class CandidateMeasurementTests(unittest.TestCase): diff --git a/experiments/presentation/render.hpp b/experiments/presentation/render.hpp index de1ad2ff..ba502f26 100644 --- a/experiments/presentation/render.hpp +++ b/experiments/presentation/render.hpp @@ -215,6 +215,13 @@ class Renderer { Layout result; const auto wide = utf16(text); check(write_->CreateTextLayout(wide.data(),static_cast(wide.size()),formats_[bold].Get(), 64.0f*zoom_percent_/100.0f,logical_y(1),result.text.GetAddressOf())); + // UTS #51 fully-qualified keycap sequences must shape as one cluster. + // Starting fallback from the ASCII digit's Consolas face splits the + // enclosing keycap into an unrelated Segoe UI Symbol glyph. + const bool keycap = text.size() == 7 && + ((text[0] >= '0' && text[0] <= '9') || text[0] == '#' || text[0] == '*') && + text.compare(1, 6, "\xef\xb8\x8f\xe2\x83\xa3") == 0; + if (keycap) check(result.text->SetFontFamilyName(L"Segoe UI Emoji", {0,static_cast(wide.size())})); const float available = float(cell_width_*columns)*96.0f/dpi_; DWRITE_TEXT_METRICS metrics{}; check(result.text->GetMetrics(&metrics)); if (metrics.widthIncludingTrailingWhitespace > available+0.01f) { diff --git a/experiments/presentation/renderer_metrics_test.cpp b/experiments/presentation/renderer_metrics_test.cpp index 8ceaa0ba..939c5034 100644 --- a/experiments/presentation/renderer_metrics_test.cpp +++ b/experiments/presentation/renderer_metrics_test.cpp @@ -104,6 +104,14 @@ static void scenario(const std::string& backend, int dpi, int zoom, float baseli const auto& fallback = renderer.layout("\xe4\xb8\xad", false); require(fallback.missing == 0 && !fallback.faces.empty() && !fallback.faces.count("Consolas"), "CJK fallback identity was not resolved"); + if (backend == "dwrite") { + for (const std::string text : {"👩\xe2\x80\x8d💻", "1\xef\xb8\x8f\xe2\x83\xa3"}) { + const auto& emoji = renderer.layout(text, false, 2); + require(emoji.missing == 0 && emoji.glyphs == 1 && + emoji.faces == std::set{"Segoe UI Emoji"}, + "whole emoji cluster was split across glyphs or fonts"); + } + } const auto& missing = renderer.layout("\xf4\x8f\xbf\xbf", false); require(missing.missing > 0 && !missing.faces.empty(), "missing-glyph control failed"); diff --git a/experiments/shell/README.md b/experiments/shell/README.md index e28c2b36..c730de05 100644 --- a/experiments/shell/README.md +++ b/experiments/shell/README.md @@ -118,7 +118,7 @@ E2产物在`TERMFORGE_TEST_ARTIFACTS/workflow-sessions/run-*`;公开白名单 | `candidate-alt` | 仅显式候选构建:两次 1049h/1049l、主屏文字/样式/光标恢复、历史隔离 | [两轮限定验证](results/2026-10-26-alternate-screen/validation.json)独立核对源 VT、五阶段真实 `CHAR_INFO`、transport 和候选状态;原生恢复快照一致,备用屏文字不进入主历史。夹具按实际控制台状态有界同步,不把切换前快照误记为切换后;origin/wrap、独立 margin、resize 和 #27 整项未验收,`presented_done=null` | | `candidate-scroll` | 仅显式候选构建:DECSTBM 局部滚动、区域外保持、有限历史语义 | [两轮限定验证](results/2026-10-26-scroll-region/validation.json)独立核对源 VT、三阶段真实 `CHAR_INFO`、候选保留文档及 `pyte==0.8.2` transport 重放;区域外不变、C/D 位于正确区域、A/B 不混入主历史。origin/wrap、独立 margin、resize 和 #27 整项未验收,`presented_done=null` | | `candidate-margins` | 仅显式候选构建:主屏 `2;3r`、备用屏 `4;5r`、恢复后局部滚动及四阶段真实控制台快照 | [限定验证](results/2026-10-26-margin-state/validation.json)分别核对公开源 VT 的恢复、resize/RIS 与真实 ConPTY 的宿主差异;当前宿主出现 `REGION_CIXED`,由 [#41](https://github.com/JavaZeroo/termforge/issues/41) 保留。两轮差异观察均为 `margins_per_buffer_verified=false`,不是能力通过;无截图或前台输入,`presented_done=null` | -| `candidate-unicode` | 仅显式候选构建:固定分解组合、单 emoji、ZWJ/keycap 的真实 ConPTY 文本、子控制台 `CHAR_INFO`、候选单元格和实际 DWrite glyph | [两轮限定验证](results/2026-10-26-unicode-renderer/validation.json)独立核对源、transport、单元格和 glyph;过宽文字只在原网格内缩小字号。ZWJ/keycap 只验收原文保留和 glyph,不宣称聚类或宽度合同已通过,后者归 [#39](https://github.com/JavaZeroo/termforge/issues/39);`presented_done=null` | +| `candidate-unicode` | 仅显式候选构建:固定分解组合、单 emoji、ZWJ/keycap 的真实 ConPTY 原文、子控制台 `CHAR_INFO`、整格宽度、实际 DWrite 单 glyph 与产品拖选复制 | v2 按[流式字素合同](../../docs/research/streaming-graphemes.md)两轮核对初始/真实 100×30 resize;保留源/运输/原生映射的区别,缺字、分裂 glyph、半字复制和伪尺寸被拒绝。限定四样本,不声明完整 Unicode/IME 或 WindTerm 等价;`presented_done=null` | | `ui-workflow-sessions` | 8真实会话/拒绝第9个、五路有限洪流、阅读/Find/输入/缩放/淘汰、合成粘贴取消/关闭、5次8→6→8 | `test_workflow_sessions`独立literal/raw/journal/UI和回收校验;每轮26检查点、18身份、235/256观测,资源scope=launcher-tree。data-only,不代替同次实际CF/IME、native阶段校准或操作提交端点 | | `ui` / `ui-edit` / `ui-restore` | 多标签、五处分隔线、重命名、捕获/Escape 回滚、新进程恢复布局 | 真实子进程尺寸、保存文件、焦点与回收;只新建会话,不恢复原进程,恢复范围见工作区保存 | | `ui-main` | 主区/底部隔离、隐藏输出、焦点模式、空区域与关闭目标 | `geometry.json` 按实际客户区/DPI和布局合同检查 HWND/字体/矩形;内部网格由实际终端窗口扣除原生滚动条和物理单元格后计算 | diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/candidate.jsonl new file mode 100644 index 00000000..40e9ad21 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/candidate.jsonl @@ -0,0 +1,8 @@ +{"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":1,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":2,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":3,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":4,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":5,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":6,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":7,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️⃣","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/chunks.txt new file mode 100644 index 00000000..fcb49fa9 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/chunks.txt @@ -0,0 +1,7 @@ +1 +1 +1 +1 +1 +1 +1 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/input.bin new file mode 100644 index 00000000..1655d594 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/input.bin @@ -0,0 +1 @@ +1️⃣ \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/report.json new file mode 100644 index 00000000..44a1bf4b --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-byte/report.json @@ -0,0 +1,20 @@ +{ + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 8, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/candidate.jsonl new file mode 100644 index 00000000..375d4d80 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/candidate.jsonl @@ -0,0 +1,2 @@ +{"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":7,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️⃣","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/chunks.txt new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/chunks.txt @@ -0,0 +1 @@ +7 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/input.bin new file mode 100644 index 00000000..1655d594 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/input.bin @@ -0,0 +1 @@ +1️⃣ \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/report.json new file mode 100644 index 00000000..ce44e7db --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/keycap-whole/report.json @@ -0,0 +1,14 @@ +{ + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 7 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/summary.json b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/summary.json new file mode 100644 index 00000000..4306acb1 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/summary.json @@ -0,0 +1,149 @@ +{ + "schema": "termforge-fixed-streaming-graphemes-v1", + "trace_sha256": "d74970c2101fa05f9d894285937e78662ea9a84690be1e60844c0f0f6cbb6663", + "passed": false, + "cases": [ + { + "case": "zwj", + "plan": "whole", + "expected_text": "👩‍💻", + "expected_width": 2, + "occupied": [ + [ + "👩‍", + 2 + ], + [ + "💻", + 2 + ] + ], + "row": 0, + "column": 4, + "passed": false, + "trace": { + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 11 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true + } + }, + { + "case": "zwj", + "plan": "byte", + "expected_text": "👩‍💻", + "expected_width": 2, + "occupied": [ + [ + "👩‍", + 2 + ], + [ + "💻", + 2 + ] + ], + "row": 0, + "column": 4, + "passed": false, + "trace": { + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 12, + "passed": true + } + }, + { + "case": "keycap", + "plan": "whole", + "expected_text": "1️⃣", + "expected_width": 2, + "occupied": [ + [ + "1️⃣", + 1 + ] + ], + "row": 0, + "column": 1, + "passed": false, + "trace": { + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 7 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true + } + }, + { + "case": "keycap", + "plan": "byte", + "expected_text": "1️⃣", + "expected_width": 2, + "occupied": [ + [ + "1️⃣", + 1 + ] + ], + "row": 0, + "column": 1, + "passed": false, + "trace": { + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 8, + "passed": true + } + } + ], + "windterm_runtime_executed": false, + "performance_measured": false +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/candidate.jsonl new file mode 100644 index 00000000..74a52c81 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/candidate.jsonl @@ -0,0 +1,12 @@ +{"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":1,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":2,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":3,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":4,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":5,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":6,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":7,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":8,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":9,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":10,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":11,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"💻","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":4,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/chunks.txt new file mode 100644 index 00000000..993dd9b1 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/chunks.txt @@ -0,0 +1,11 @@ +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/input.bin new file mode 100644 index 00000000..7c3bcf1a --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/input.bin @@ -0,0 +1 @@ +👩‍💻 \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/report.json new file mode 100644 index 00000000..80327291 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-byte/report.json @@ -0,0 +1,24 @@ +{ + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 12, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/candidate.jsonl new file mode 100644 index 00000000..11818371 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/candidate.jsonl @@ -0,0 +1,2 @@ +{"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"mode_bits":163969,"offset":11,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"💻","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":4,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/chunks.txt new file mode 100644 index 00000000..b4de3947 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/chunks.txt @@ -0,0 +1 @@ +11 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/input.bin new file mode 100644 index 00000000..7c3bcf1a --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/input.bin @@ -0,0 +1 @@ +👩‍💻 \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/report.json new file mode 100644 index 00000000..dadf8da1 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/baseline-red/zwj-whole/report.json @@ -0,0 +1,14 @@ +{ + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 11 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/candidate.jsonl new file mode 100644 index 00000000..c44c82f9 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/candidate.jsonl @@ -0,0 +1,8 @@ +{"grapheme_overflows":0,"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":1,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":2,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":3,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1","width":1},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":1,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":4,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":5,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":6,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":7,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️⃣","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/chunks.txt new file mode 100644 index 00000000..fcb49fa9 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/chunks.txt @@ -0,0 +1,7 @@ +1 +1 +1 +1 +1 +1 +1 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/input.bin new file mode 100644 index 00000000..1655d594 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/input.bin @@ -0,0 +1 @@ +1️⃣ \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/report.json new file mode 100644 index 00000000..44a1bf4b --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-byte/report.json @@ -0,0 +1,20 @@ +{ + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 8, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/candidate.jsonl new file mode 100644 index 00000000..5192f773 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/candidate.jsonl @@ -0,0 +1,2 @@ +{"grapheme_overflows":0,"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":7,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"1️⃣","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/chunks.txt new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/chunks.txt @@ -0,0 +1 @@ +7 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/input.bin new file mode 100644 index 00000000..1655d594 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/input.bin @@ -0,0 +1 @@ +1️⃣ \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/report.json new file mode 100644 index 00000000..ce44e7db --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/keycap-whole/report.json @@ -0,0 +1,14 @@ +{ + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 7 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/summary.json b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/summary.json new file mode 100644 index 00000000..1856053c --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/summary.json @@ -0,0 +1,141 @@ +{ + "schema": "termforge-fixed-streaming-graphemes-v1", + "trace_sha256": "6dbe42cfefc8b5df64e652193d3b5863fbf679f12ccfa2e422d9ca1e9e1a5c3d", + "passed": true, + "cases": [ + { + "case": "zwj", + "plan": "whole", + "expected_text": "👩‍💻", + "expected_width": 2, + "occupied": [ + [ + "👩‍💻", + 2 + ] + ], + "row": 0, + "column": 2, + "passed": true, + "trace": { + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 11 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true + } + }, + { + "case": "zwj", + "plan": "byte", + "expected_text": "👩‍💻", + "expected_width": 2, + "occupied": [ + [ + "👩‍💻", + 2 + ] + ], + "row": 0, + "column": 2, + "passed": true, + "trace": { + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 12, + "passed": true + } + }, + { + "case": "keycap", + "plan": "whole", + "expected_text": "1️⃣", + "expected_width": 2, + "occupied": [ + [ + "1️⃣", + 2 + ] + ], + "row": 0, + "column": 2, + "passed": true, + "trace": { + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 7 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true + } + }, + { + "case": "keycap", + "plan": "byte", + "expected_text": "1️⃣", + "expected_width": 2, + "occupied": [ + [ + "1️⃣", + 2 + ] + ], + "row": 0, + "column": 2, + "passed": true, + "trace": { + "bytes": 7, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "3a54154ed9521ac47fb973d028932325fa75f99fea9a0d50958bb32878f5cd0b", + "returncode": 0, + "error": null, + "checkpoints": 8, + "passed": true + } + } + ], + "windterm_runtime_executed": false, + "performance_measured": false +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/candidate.jsonl new file mode 100644 index 00000000..440f975c --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/candidate.jsonl @@ -0,0 +1,12 @@ +{"grapheme_overflows":0,"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":1,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":2,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":3,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":4,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":5,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":6,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":7,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":8,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":9,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":10,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":11,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍💻","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/chunks.txt new file mode 100644 index 00000000..993dd9b1 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/chunks.txt @@ -0,0 +1,11 @@ +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/input.bin new file mode 100644 index 00000000..7c3bcf1a --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/input.bin @@ -0,0 +1 @@ +👩‍💻 \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/report.json new file mode 100644 index 00000000..80327291 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-byte/report.json @@ -0,0 +1,24 @@ +{ + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 12, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/candidate.jsonl b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/candidate.jsonl new file mode 100644 index 00000000..cd28440d --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/candidate.jsonl @@ -0,0 +1,2 @@ +{"grapheme_overflows":0,"mode_bits":163969,"offset":0,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":0,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} +{"grapheme_overflows":0,"mode_bits":163969,"offset":11,"replies":[],"screen":{"cells":[[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"👩‍💻","width":2},{"style":{"bg":-1,"bold":false,"fg":-1},"text":" ","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}],[{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0},{"style":{"bg":-1,"bold":false,"fg":-1},"text":"","width":0}]],"column":2,"current_style":{"bg":-1,"bold":false,"fg":-1},"history":[],"row":0,"wrap_pending":false}} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/candidate.stderr.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/candidate.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/chunks.txt b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/chunks.txt new file mode 100644 index 00000000..b4de3947 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/chunks.txt @@ -0,0 +1 @@ +11 diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/input.bin b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/input.bin new file mode 100644 index 00000000..7c3bcf1a --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/input.bin @@ -0,0 +1 @@ +👩‍💻 \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/report.json b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/report.json new file mode 100644 index 00000000..dadf8da1 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/candidate-green/zwj-whole/report.json @@ -0,0 +1,14 @@ +{ + "bytes": 11, + "rows": 3, + "columns": 8, + "history": 2, + "chunk_sizes": [ + 11 + ], + "input_sha256": "427274538f1f24ef137872891551ffb4263f6edd90c80c533534578e8a5a9893", + "returncode": 0, + "error": null, + "checkpoints": 2, + "passed": true +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/diagnostic.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/diagnostic.json new file mode 100644 index 00000000..4a87e8ff --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/diagnostic.json @@ -0,0 +1,108 @@ +{ + "records": [ + { + "route": "project", + "variant": "baseline", + "repeat": 0, + "mib_s": 35.13313248743142, + "valid": true + }, + { + "route": "project", + "variant": "candidate", + "repeat": 0, + "mib_s": 23.99623214998744, + "valid": true + }, + { + "route": "project", + "variant": "candidate", + "repeat": 1, + "mib_s": 20.181988135698283, + "valid": true + }, + { + "route": "project", + "variant": "baseline", + "repeat": 1, + "mib_s": 25.864392294883505, + "valid": true + }, + { + "route": "project", + "variant": "baseline", + "repeat": 2, + "mib_s": 25.618390174831767, + "valid": true + }, + { + "route": "project", + "variant": "candidate", + "repeat": 2, + "mib_s": 21.65593524971545, + "valid": true + }, + { + "route": "interactive", + "variant": "baseline", + "repeat": 0, + "mib_s": 11.921779104981667, + "valid": true + }, + { + "route": "interactive", + "variant": "candidate", + "repeat": 0, + "mib_s": 12.011916408888272, + "valid": true + }, + { + "route": "interactive", + "variant": "candidate", + "repeat": 1, + "mib_s": 12.540056052875373, + "valid": true + }, + { + "route": "interactive", + "variant": "baseline", + "repeat": 1, + "mib_s": 14.566102322864767, + "valid": true + }, + { + "route": "interactive", + "variant": "baseline", + "repeat": 2, + "mib_s": 13.841216054068614, + "valid": true + }, + { + "route": "interactive", + "variant": "candidate", + "repeat": 2, + "mib_s": 13.759937264193896, + "valid": true + } + ], + "medians": { + "project": { + "baseline": 25.864392294883505, + "candidate": 21.65593524971545 + }, + "interactive": { + "baseline": 13.841216054068614, + "candidate": 12.540056052875373 + } + }, + "binary_sha256": { + "baseline": "8fd16ce2487bedfab4bd43297baac5a61b2db83cbfc29cbee449799fe18303c1", + "candidate": "a11098f0bd20796c9c95520a7cf9b78dbc0d498bf099f211f1a5bad0f4b79c89" + }, + "performance_equivalence_claim": false, + "limitations": [ + "Host shared with other agents", + "No presentation/transport/input-latency timing", + "Observe0 is counter-off but allocator still wrapped" + ] +} \ No newline at end of file diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-0.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-0.json new file mode 100644 index 00000000..8626cdf8 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-0.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":22000,"chunk":4096,"counted_parser_destructions":591,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0355063,"interval_cpu_seconds":1.046875,"interval_wall_seconds":1.0418457,"iterations":592,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":48979968,"presented_done":null,"receive_seconds":null,"rounds":592,"route":"interactive","schema_version":2,"state_fnv1a64":"8035857386658397897","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":74,"warmup_and_oracle_seconds":0.0414602} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-1.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-1.json new file mode 100644 index 00000000..9c8535f0 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-1.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":22000,"chunk":4096,"counted_parser_destructions":895,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":5,"cpu_interval_differences":1,"final_oracle_seconds":0.0315957,"interval_cpu_seconds":1.1875,"interval_wall_seconds":1.2905874,"iterations":896,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":48971776,"presented_done":null,"receive_seconds":null,"rounds":896,"route":"interactive","schema_version":2,"state_fnv1a64":"8035857386658397897","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":112,"warmup_and_oracle_seconds":0.0350683} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-2.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-2.json new file mode 100644 index 00000000..fa8917e4 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-baseline-2.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":22000,"chunk":4096,"counted_parser_destructions":839,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":5,"cpu_interval_differences":1,"final_oracle_seconds":0.0336656,"interval_cpu_seconds":1.234375,"interval_wall_seconds":1.2732914,"iterations":840,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":48930816,"presented_done":null,"receive_seconds":null,"rounds":840,"route":"interactive","schema_version":2,"state_fnv1a64":"8035857386658397897","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":105,"warmup_and_oracle_seconds":0.0359342} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-0.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-0.json new file mode 100644 index 00000000..d647b971 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-0.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":22000,"chunk":4096,"counted_parser_destructions":591,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0294646,"interval_cpu_seconds":1.015625,"interval_wall_seconds":1.0340277,"iterations":592,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":48951296,"presented_done":null,"receive_seconds":null,"rounds":592,"route":"interactive","schema_version":2,"state_fnv1a64":"6154007251599136798","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":74,"warmup_and_oracle_seconds":0.0364087} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-1.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-1.json new file mode 100644 index 00000000..bd93aa1d --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-1.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":22000,"chunk":4096,"counted_parser_destructions":615,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0341919,"interval_cpu_seconds":1.03125,"interval_wall_seconds":1.0306329,"iterations":616,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":48947200,"presented_done":null,"receive_seconds":null,"rounds":616,"route":"interactive","schema_version":2,"state_fnv1a64":"6154007251599136798","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":77,"warmup_and_oracle_seconds":0.0367942} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-2.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-2.json new file mode 100644 index 00000000..cf3b3840 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/interactive-candidate-2.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":22000,"chunk":4096,"counted_parser_destructions":671,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0292225,"interval_cpu_seconds":1.0,"interval_wall_seconds":1.0246501,"iterations":672,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":48963584,"presented_done":null,"receive_seconds":null,"rounds":672,"route":"interactive","schema_version":2,"state_fnv1a64":"6154007251599136798","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":84,"warmup_and_oracle_seconds":0.0344894} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-0.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-0.json new file mode 100644 index 00000000..f72b1bc3 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-0.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":78000,"chunk":4096,"counted_parser_destructions":487,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0387365,"interval_cpu_seconds":1.015625,"interval_wall_seconds":1.0332315,"iterations":488,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":49405952,"presented_done":null,"receive_seconds":null,"rounds":488,"route":"project","schema_version":2,"state_fnv1a64":"11966868740884721388","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":61,"warmup_and_oracle_seconds":0.0331653} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-1.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-1.json new file mode 100644 index 00000000..e2964955 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-1.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":78000,"chunk":4096,"counted_parser_destructions":367,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0456638,"interval_cpu_seconds":1.0,"interval_wall_seconds":1.0583766,"iterations":368,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":49397760,"presented_done":null,"receive_seconds":null,"rounds":368,"route":"project","schema_version":2,"state_fnv1a64":"11966868740884721388","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":46,"warmup_and_oracle_seconds":0.0422445} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-2.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-2.json new file mode 100644 index 00000000..0c4901c6 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-baseline-2.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":78000,"chunk":4096,"counted_parser_destructions":359,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0301495,"interval_cpu_seconds":1.0,"interval_wall_seconds":1.0453106,"iterations":360,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":49410048,"presented_done":null,"receive_seconds":null,"rounds":360,"route":"project","schema_version":2,"state_fnv1a64":"11966868740884721388","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":45,"warmup_and_oracle_seconds":0.0440747} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-0.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-0.json new file mode 100644 index 00000000..3e743a89 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-0.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":78000,"chunk":4096,"counted_parser_destructions":343,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0330774,"interval_cpu_seconds":1.046875,"interval_wall_seconds":1.0663753,"iterations":344,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":49434624,"presented_done":null,"receive_seconds":null,"rounds":344,"route":"project","schema_version":2,"state_fnv1a64":"16178973312633948535","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":43,"warmup_and_oracle_seconds":0.0454235} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-1.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-1.json new file mode 100644 index 00000000..baff8b2a --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-1.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":78000,"chunk":4096,"counted_parser_destructions":287,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0417629,"interval_cpu_seconds":1.015625,"interval_wall_seconds":1.0615079,"iterations":288,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":49422336,"presented_done":null,"receive_seconds":null,"rounds":288,"route":"project","schema_version":2,"state_fnv1a64":"16178973312633948535","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":36,"warmup_and_oracle_seconds":0.0395011} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-2.json b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-2.json new file mode 100644 index 00000000..980ec343 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/cost/project-candidate-2.json @@ -0,0 +1 @@ +{"allocation_scope":"Rust global allocator requests; not equivalent to C++ new or live memory","bytes_per_round":78000,"chunk":4096,"counted_parser_destructions":311,"counter_off_is_uninstrumented":false,"cpu_clock":"GetProcessTimes kernel plus user","cpu_floor_seconds":1.0,"cpu_gate_min_wall_spacing_seconds":0.25,"cpu_gate_polls":4,"cpu_interval_differences":1,"final_oracle_seconds":0.0569238,"interval_cpu_seconds":1.0625,"interval_wall_seconds":1.0716978,"iterations":312,"kind":"core_cost","observe":false,"oracle_scope":"warmup and final iteration","ordinary_allocated_bytes":null,"ordinary_allocations":null,"peak_working_set_bytes":49414144,"presented_done":null,"receive_seconds":null,"rounds":312,"route":"project","schema_version":2,"state_fnv1a64":"16178973312633948535","valid":true,"wall_clock":"std::time::Instant","wall_gate_every_iterations":8,"wall_gate_polls":39,"warmup_and_oracle_seconds":0.0382161} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/owned-conpty-0.json b/experiments/shell/results/2026-10-26-streaming-graphemes/owned-conpty-0.json new file mode 100644 index 00000000..50629fb0 --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/owned-conpty-0.json @@ -0,0 +1,369 @@ +{ + "repeat": 0, + "status": "passed", + "timed_out": false, + "supervisor_exit_code": 0, + "desktop_gone": true, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "frame_sha256": "f32b396e098ce7ff162b8e7b6b42dcea2de33e2af3b851c4fcb434d6341f81d7", + "result": { + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "command_verified": true, + "presented_done": null + }, + "phases": [ + { + "schema": "termforge-candidate-unicode-v2", + "phase": "initial", + "native_size_marker": null, + "grapheme_overflows": 0, + "columns": 80, + "rows": 24, + "dpi": 96, + "cell_width": 12, + "cell_height": 26, + "frame_matches_full": true, + "presented_done": null + }, + { + "schema": "termforge-candidate-unicode-v2", + "phase": "resized", + "native_size_marker": "TF_SIZE_100x30", + "grapheme_overflows": 0, + "columns": 100, + "rows": 30, + "dpi": 96, + "cell_width": 12, + "cell_height": 26, + "frame_matches_full": true, + "presented_done": null + } + ], + "validated_unicode": { + "samples": [ + { + "label": "TF_U_DECOMPOSE", + "text": "é", + "cells": [ + { + "column": 15, + "width": 1, + "text": "é", + "glyphs": 1, + "faces": [ + "Consolas" + ] + } + ] + }, + { + "label": "TF_U_EMOJI", + "text": "🙂", + "cells": [ + { + "column": 11, + "width": 2, + "text": "🙂", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_ZWJ", + "text": "👩‍💻", + "cells": [ + { + "column": 9, + "width": 2, + "text": "👩‍💻", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_KEYCAP", + "text": "1️⃣", + "cells": [ + { + "column": 12, + "width": 2, + "text": "1️⃣", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + } + ], + "selections": [ + { + "label": "TF_U_DECOMPOSE", + "copied": "é", + "begin": 15, + "end": 16 + }, + { + "label": "TF_U_EMOJI", + "copied": "🙂", + "begin": 11, + "end": 13 + }, + { + "label": "TF_U_ZWJ", + "copied": "👩‍💻", + "begin": 9, + "end": 11 + }, + { + "label": "TF_U_KEYCAP", + "copied": "1️⃣", + "begin": 12, + "end": 14 + } + ], + "console_samples": [ + { + "label": "TF_U_DECOMPOSE", + "projection": "original", + "widths": [ + 1, + 1 + ] + }, + { + "label": "TF_U_EMOJI", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0 + ] + }, + { + "label": "TF_U_ZWJ", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0, + 1, + 2, + 0 + ] + }, + { + "label": "TF_U_KEYCAP", + "projection": "original", + "widths": [ + 1, + 1, + 1 + ] + } + ], + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "f45368de27a5185c1bd31ce7513595d654d52131ecfa8102444672728f18cd53", + "transport_bytes": 753, + "scoped_validation_passed": true, + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": false, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null, + "resize": { + "samples": [ + { + "label": "TF_U_DECOMPOSE", + "text": "é", + "cells": [ + { + "column": 15, + "width": 1, + "text": "é", + "glyphs": 1, + "faces": [ + "Consolas" + ] + } + ] + }, + { + "label": "TF_U_EMOJI", + "text": "🙂", + "cells": [ + { + "column": 11, + "width": 2, + "text": "🙂", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_ZWJ", + "text": "👩‍💻", + "cells": [ + { + "column": 9, + "width": 2, + "text": "👩‍💻", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_KEYCAP", + "text": "1️⃣", + "cells": [ + { + "column": 12, + "width": 2, + "text": "1️⃣", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + } + ], + "selections": [ + { + "label": "TF_U_DECOMPOSE", + "copied": "é", + "begin": 15, + "end": 16 + }, + { + "label": "TF_U_EMOJI", + "copied": "🙂", + "begin": 11, + "end": 13 + }, + { + "label": "TF_U_ZWJ", + "copied": "👩‍💻", + "begin": 9, + "end": 11 + }, + { + "label": "TF_U_KEYCAP", + "copied": "1️⃣", + "begin": 12, + "end": 14 + } + ], + "console_samples": [ + { + "label": "TF_U_DECOMPOSE", + "projection": "original", + "widths": [ + 1, + 1 + ] + }, + { + "label": "TF_U_EMOJI", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0 + ] + }, + { + "label": "TF_U_ZWJ", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0, + 1, + 2, + 0 + ] + }, + { + "label": "TF_U_KEYCAP", + "projection": "original", + "widths": [ + 1, + 1, + 1 + ] + } + ], + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "e2ea6aa721725d163831125be5cbef94a415d715fe699307f1ce603c7a67f1f2", + "transport_bytes": 1373, + "scoped_validation_passed": true, + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": true, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null + } + }, + "artifact_identity": { + "candidate-unicode-resized.json": { + "bytes": 28255, + "sha256": "09c59f740443f50d0a1a326d2544d5c03d49ebc5a346e72e87adddf057ac8f80" + }, + "candidate-unicode-resized.transport.bin": { + "bytes": 1373, + "sha256": "e2ea6aa721725d163831125be5cbef94a415d715fe699307f1ce603c7a67f1f2" + }, + "candidate-unicode.cells.bin": { + "bytes": 7680, + "sha256": "86c36ff9ab2fb4aa9b3b4883da7079b21176981052d6f9d8e7293e387380e1f8" + }, + "candidate-unicode.console.bin": { + "bytes": 3834, + "sha256": "6b6d7218f098f18cd3ebd077e73566fb850f2dc8adf7e4230ffa56f44db65f79" + }, + "candidate-unicode.console.json": { + "bytes": 438, + "sha256": "86130298c82b7902c05ed129888c14fe5d579e35cffec345c9d88c146dd0312b" + }, + "candidate-unicode.json": { + "bytes": 10520, + "sha256": "45fd8d68c7ba9da54d1b2af6450e3193b08c7d104a19baae4abc3710f3d6e7bc" + }, + "candidate-unicode.source.bin": { + "bytes": 91, + "sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2" + }, + "candidate-unicode.transport.bin": { + "bytes": 753, + "sha256": "f45368de27a5185c1bd31ce7513595d654d52131ecfa8102444672728f18cd53" + }, + "frame.bmp": { + "bytes": 3744054, + "sha256": "f32b396e098ce7ff162b8e7b6b42dcea2de33e2af3b851c4fcb434d6341f81d7" + } + } +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/owned-conpty-1.json b/experiments/shell/results/2026-10-26-streaming-graphemes/owned-conpty-1.json new file mode 100644 index 00000000..aa7d2a0a --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/owned-conpty-1.json @@ -0,0 +1,369 @@ +{ + "repeat": 1, + "status": "passed", + "timed_out": false, + "supervisor_exit_code": 0, + "desktop_gone": true, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "frame_sha256": "f32b396e098ce7ff162b8e7b6b42dcea2de33e2af3b851c4fcb434d6341f81d7", + "result": { + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "command_verified": true, + "presented_done": null + }, + "phases": [ + { + "schema": "termforge-candidate-unicode-v2", + "phase": "initial", + "native_size_marker": null, + "grapheme_overflows": 0, + "columns": 80, + "rows": 24, + "dpi": 96, + "cell_width": 12, + "cell_height": 26, + "frame_matches_full": true, + "presented_done": null + }, + { + "schema": "termforge-candidate-unicode-v2", + "phase": "resized", + "native_size_marker": "TF_SIZE_100x30", + "grapheme_overflows": 0, + "columns": 100, + "rows": 30, + "dpi": 96, + "cell_width": 12, + "cell_height": 26, + "frame_matches_full": true, + "presented_done": null + } + ], + "validated_unicode": { + "samples": [ + { + "label": "TF_U_DECOMPOSE", + "text": "é", + "cells": [ + { + "column": 15, + "width": 1, + "text": "é", + "glyphs": 1, + "faces": [ + "Consolas" + ] + } + ] + }, + { + "label": "TF_U_EMOJI", + "text": "🙂", + "cells": [ + { + "column": 11, + "width": 2, + "text": "🙂", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_ZWJ", + "text": "👩‍💻", + "cells": [ + { + "column": 9, + "width": 2, + "text": "👩‍💻", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_KEYCAP", + "text": "1️⃣", + "cells": [ + { + "column": 12, + "width": 2, + "text": "1️⃣", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + } + ], + "selections": [ + { + "label": "TF_U_DECOMPOSE", + "copied": "é", + "begin": 15, + "end": 16 + }, + { + "label": "TF_U_EMOJI", + "copied": "🙂", + "begin": 11, + "end": 13 + }, + { + "label": "TF_U_ZWJ", + "copied": "👩‍💻", + "begin": 9, + "end": 11 + }, + { + "label": "TF_U_KEYCAP", + "copied": "1️⃣", + "begin": 12, + "end": 14 + } + ], + "console_samples": [ + { + "label": "TF_U_DECOMPOSE", + "projection": "original", + "widths": [ + 1, + 1 + ] + }, + { + "label": "TF_U_EMOJI", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0 + ] + }, + { + "label": "TF_U_ZWJ", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0, + 1, + 2, + 0 + ] + }, + { + "label": "TF_U_KEYCAP", + "projection": "original", + "widths": [ + 1, + 1, + 1 + ] + } + ], + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "b3710119f79b80728142aa9022159a3a2be297eaca4a5d171d1963b4a920f021", + "transport_bytes": 753, + "scoped_validation_passed": true, + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": false, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null, + "resize": { + "samples": [ + { + "label": "TF_U_DECOMPOSE", + "text": "é", + "cells": [ + { + "column": 15, + "width": 1, + "text": "é", + "glyphs": 1, + "faces": [ + "Consolas" + ] + } + ] + }, + { + "label": "TF_U_EMOJI", + "text": "🙂", + "cells": [ + { + "column": 11, + "width": 2, + "text": "🙂", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_ZWJ", + "text": "👩‍💻", + "cells": [ + { + "column": 9, + "width": 2, + "text": "👩‍💻", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + }, + { + "label": "TF_U_KEYCAP", + "text": "1️⃣", + "cells": [ + { + "column": 12, + "width": 2, + "text": "1️⃣", + "glyphs": 1, + "faces": [ + "Segoe UI Emoji" + ] + } + ] + } + ], + "selections": [ + { + "label": "TF_U_DECOMPOSE", + "copied": "é", + "begin": 15, + "end": 16 + }, + { + "label": "TF_U_EMOJI", + "copied": "🙂", + "begin": 11, + "end": 13 + }, + { + "label": "TF_U_ZWJ", + "copied": "👩‍💻", + "begin": 9, + "end": 11 + }, + { + "label": "TF_U_KEYCAP", + "copied": "1️⃣", + "begin": 12, + "end": 14 + } + ], + "console_samples": [ + { + "label": "TF_U_DECOMPOSE", + "projection": "original", + "widths": [ + 1, + 1 + ] + }, + { + "label": "TF_U_EMOJI", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0 + ] + }, + { + "label": "TF_U_ZWJ", + "projection": "replacement-non-bmp", + "widths": [ + 2, + 0, + 1, + 2, + 0 + ] + }, + { + "label": "TF_U_KEYCAP", + "projection": "original", + "widths": [ + 1, + 1, + 1 + ] + } + ], + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "b64fb331fa979edcb7c0a65d3e88c7ae5a22ef5cc083ca2ba7ce02c28c12c5c9", + "transport_bytes": 1373, + "scoped_validation_passed": true, + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": true, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null + } + }, + "artifact_identity": { + "candidate-unicode-resized.json": { + "bytes": 28255, + "sha256": "09c59f740443f50d0a1a326d2544d5c03d49ebc5a346e72e87adddf057ac8f80" + }, + "candidate-unicode-resized.transport.bin": { + "bytes": 1373, + "sha256": "b64fb331fa979edcb7c0a65d3e88c7ae5a22ef5cc083ca2ba7ce02c28c12c5c9" + }, + "candidate-unicode.cells.bin": { + "bytes": 7680, + "sha256": "86c36ff9ab2fb4aa9b3b4883da7079b21176981052d6f9d8e7293e387380e1f8" + }, + "candidate-unicode.console.bin": { + "bytes": 3834, + "sha256": "6b6d7218f098f18cd3ebd077e73566fb850f2dc8adf7e4230ffa56f44db65f79" + }, + "candidate-unicode.console.json": { + "bytes": 438, + "sha256": "86130298c82b7902c05ed129888c14fe5d579e35cffec345c9d88c146dd0312b" + }, + "candidate-unicode.json": { + "bytes": 10520, + "sha256": "45fd8d68c7ba9da54d1b2af6450e3193b08c7d104a19baae4abc3710f3d6e7bc" + }, + "candidate-unicode.source.bin": { + "bytes": 91, + "sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2" + }, + "candidate-unicode.transport.bin": { + "bytes": 753, + "sha256": "b3710119f79b80728142aa9022159a3a2be297eaca4a5d171d1963b4a920f021" + }, + "frame.bmp": { + "bytes": 3744054, + "sha256": "f32b396e098ce7ff162b8e7b6b42dcea2de33e2af3b851c4fcb434d6341f81d7" + } + } +} diff --git a/experiments/shell/results/2026-10-26-streaming-graphemes/validation.json b/experiments/shell/results/2026-10-26-streaming-graphemes/validation.json new file mode 100644 index 00000000..9cf5042a --- /dev/null +++ b/experiments/shell/results/2026-10-26-streaming-graphemes/validation.json @@ -0,0 +1,683 @@ +{ + "schema": "termforge-streaming-graphemes-proof-v1", + "date": "2026-10-26", + "issue": 39, + "scope": "candidate first fixed streaming graphemes, adapters, selection, history/resize, actual DWrite glyph and owned ConPTY", + "baseline": { + "git_commit": "65988f5f20f62294ff52fcac5b5b81e563f93f3b", + "candidate_version": "0.26.1-dev+termforge.3", + "fresh_build": true, + "tracked_tree_clean": true + }, + "candidate": { + "version": "0.26.1-dev+termforge.4", + "source_identity": "source_sha256 below identifies the pre-commit worktree; final commit is the commit containing this proof", + "last_after_focused_change": "only cfg(test) exact normative corpus count assertion; no product runtime changes" + }, + "source_sha256": { + "CMakeLists.txt": "98843df715c78b64b58c9aba979cf177b96ce4b52e4e5d1b2c8b6216db4a75f8", + "docs/EVIDENCE.md": "d83d785b7351eaf3cba561d35279caa075166616473f85d4eb9d9ccdd4763b83", + "docs/FEATURES.md": "73f4790bc738aacbc16840c627301373d439bac83bfe391e5cc7e137b15b3dfe", + "docs/milestones/R2-font-display.md": "04e08f5fef0a9d0e4eaabbb45c828dbd4f5e82c56323d9cdd8e64226d7046bf5", + "docs/research/streaming-graphemes.md": "d933ce4a4fe643626d58620a687f6bc1894bbbf46fea69ad4beec3fc6417283b", + "experiments/candidates/alacritty/Cargo.lock": "051c783ab917c4a25ac588fe780751cef6c453f08661100b96638c0bc375f6c8", + "experiments/candidates/alacritty/Cargo.toml": "c5f89fda48ab73f30adfdcc4437fb3eeb1779647886d85364d91395bf8beec86", + "experiments/candidates/alacritty/PROVENANCE.md": "6105b1aa65ac6bb4e14d7fd6b0c2c907dd12947b74191870d61b26d658d84ba5", + "experiments/candidates/alacritty/README.md": "8193bd9ad305c973d010fa4418d89c1d2ee41cb7eb5569eb6e9432c606c998da", + "experiments/candidates/alacritty/grapheme_checks.py": "0654cb6868a94bb6a7cdb0a328dc8e854966aa5dd8c14d075d334a6a4fd983f0", + "experiments/candidates/alacritty/include/termforge_candidate.h": "6f6f6df22d65354e1ef0d39d6eca3ed0608a45a6cf8c41381b6287122fbab17b", + "experiments/candidates/alacritty/run.py": "c00b24ae170fc512b7fe15159c4245457f7b2d2fc9656f9074621526cd4fb289", + "experiments/candidates/alacritty/src/ffi.rs": "0ae1b36f2f23cdb5af5670ffb0b4201bec13578dfbf0152c3d1f14f7163a0804", + "experiments/candidates/alacritty/src/main.rs": "95c16e8ff9487396693ba9021c3cee676955e5a388d4952f4e134e26be7b1d17", + "experiments/candidates/alacritty/test_candidate.py": "e140ba53406b87696688dff8f335275af0a0e0b9aaf3bf1bee113d4e6a16a6f0", + "experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml": "6b60e0b2db23e5d1fe16f23978fe7f6f9965c8663419cd6cce8e2b1e73df5b93", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "ce093c7624f7035f56e7a186460774c8348c8687a54334842f7e8d5f95a79bf6", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs": "8b31cf5389398c74f141bd1f3e7945a1dbde8944bb754f5fd623313c73262496", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/grapheme.rs": "e09d3b858c303864c6ad029089387b5c42442b34528f6c5043feb32f0ff3932c", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs": "4a86c3e0a18400539a6e7c1d6117024f3ba868de278992140f6c7aa38c252f6e", + "experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/GraphemeBreakTest-17.0.0.txt": "e2d134d2c52919bace503ebb6a551c1855fe1a1faec18478c78fff254a1793ec", + "experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt": "e7a93b009565cfce55919a381437ac4db883e9da2126fa28b91d12732bc53d96", + "experiments/core/CANDIDATE.md": "22a7f99c4bdc22a05f28d3d10ff0512b28b7926e7a47d7cfd3c83792cb57ceeb", + "experiments/core/candidate_terminal.cpp": "7db1064a5f5b69dfad58f806f8d0e477b87eca07330e8e1b5104d20fc11dacae", + "experiments/core/candidate_terminal.hpp": "1fa9dc00063c10e7cf76accc2d626693baba2494bf51dd9902d96fc04df8ca45", + "experiments/core/candidate_terminal_test.cpp": "533695d6ebdc90213ef270907500ea4525334d640bed1f747fa4121d549d2953", + "experiments/core/screen.hpp": "8b8bf2c21084060637327091d00c07c827fc5dc0bf8719a3b8c3bfa43fd8de68", + "experiments/core/text_projection.hpp": "7e4b401bc40b0e4909af6b98fb84661e7fa96228c42df4706375c632f2199a82", + "experiments/presentation/render.hpp": "4c67f74e7a3791526630aaa1a8cab685356b7a7c88da25f70ea25be8dd633db2", + "experiments/presentation/renderer_metrics_test.cpp": "b3094cde0641aefb1795ba46676e4e292e694bda947d4dad810e7823d353ae2f", + "experiments/shell/README.md": "a0bda14582d3144330d44fc74cfe53bf0e94508fc845cf23da32cf8cb8579ca1", + "experiments/shell/shell.cpp": "552b38c185cc3a8579ad7913d9c70403bc1f02de80968a3b1756b04e43e31d4b", + "experiments/shell/test_candidate_tui.py": "37c42ec6e18fd2711c19c4a3705badf38de9e6228d53574ed0e22f38b85f1b4f", + "experiments/shell/unicode_checks.py": "267622788537c5b12bce94f27aa196cdc7dd102124baa51fc5db3592eb485d37" + }, + "binaries": { + "build/candidate-r39/bin/Release/termforge_shell.exe": { + "bytes": 1725440, + "sha256": "6d62785febc99423b8b84d830df28d75c29ad90ac87c24bc73dd7e6ed7424cae" + }, + "build/candidate-r39/bin/Release/shell_text_client.exe": { + "bytes": 147968, + "sha256": "1df0479cf229bed2939e5c77d049c11b6a61ad382ed907c2b5cab5c2e505c3f7" + }, + "build/candidate-r39/bin/Release/shell_size_client.exe": { + "bytes": 17408, + "sha256": "836131bdb41d534a9637a52cb76ae8ea961cfcfffaa7cc399259ebf94e3d4b36" + }, + "build/candidate-r39/bin/Release/candidate_core_test.exe": { + "bytes": 660992, + "sha256": "77cee5a7eaa8af95c2090c76374416bb43e7bfd53e013eb6399841206a437273" + }, + "build/candidate-r39/bin/Release/renderer_metrics_test.exe": { + "bytes": 129024, + "sha256": "5feae9ec997512b51ff963faed862e64fa23087ce66abba88a54bd4e8dfdcd94" + }, + "experiments/candidates/alacritty/target/release/alacritty-measure.exe": { + "bytes": 572928, + "sha256": "a11098f0bd20796c9c95520a7cf9b78dbc0d498bf099f211f1a5bad0f4b79c89" + }, + "baseline/experiments/candidates/alacritty/target/release/alacritty-measure.exe": { + "bytes": 516096, + "sha256": "8fd16ce2487bedfab4bd43297baac5a61b2db83cbfc29cbee449799fe18303c1" + }, + "experiments/candidates/alacritty/target/release/termforge-alacritty-trace.exe": { + "bytes": 570368, + "sha256": "6dbe42cfefc8b5df64e652193d3b5863fbf679f12ccfa2e422d9ca1e9e1a5c3d" + }, + "baseline/experiments/candidates/alacritty/target/release/termforge-alacritty-trace.exe": { + "bytes": 510976, + "sha256": "d74970c2101fa05f9d894285937e78662ea9a84690be1e60844c0f0f6cbb6663" + } + }, + "contracts": [ + "feed/snapshot/query/SGR do not seal; first scalar owns cluster style; current prefix cursor is immediately observable", + "mutating terminal events seal; Unicode 17 extended grapheme segmentation and unicode-width 0.2.2 non-CJK string width", + "1-to-2 right-margin growth wraps whole cluster; 2-to-1 retains already committed wrap/scroll; INSERT uses bounded local width-delta changes", + "one-column/no-autowrap footprints explicitly clip while preserving full scalars", + "64 scalars/256 UTF-8 bytes per lossless fragment, saturating overflow counter; overflow layout is not claimed as one complete EGC", + "leading wrap padding is explicitly distinguished from real written spaces; wide pairs are cleared atomically before INSERT shift; zero-width Prepend retains forward context" + ], + "fixed_feedback": { + "command": "python -m experiments.candidates.alacritty.grapheme_checks --trace --out ", + "baseline": "baseline-red/summary.json", + "baseline_expected_failures": 4, + "candidate": "candidate-green/summary.json", + "candidate_passed": 4 + }, + "verification": { + "rust": { + "command": "cargo test --manifest-path experiments/candidates/alacritty/Cargo.toml --workspace --locked", + "vendor_tests": 157, + "ffi_tests": 2, + "measure_tests": 2, + "main_tests": 2, + "doc_tests": 1, + "total_passed": 164, + "failed": 0, + "ignored": 0, + "log_sha256": "cb9e06b959d7ad193fa8633d4287e0b2f1added92bb1c9bc47b4c5702e604188" + }, + "python": { + "command": "python -m unittest experiments.candidates.alacritty.test_candidate", + "passed": 6, + "failed": 0, + "log_sha256": "a9416731ee6f37117a9afa8596cdcd0b617e6d935582ea8c405ddb8bdda2e00f" + }, + "earlier_full_ctest": { + "tests": 46, + "failures": 1, + "skipped": 0, + "seconds": 348, + "cases": [ + { + "name": "shell_launch", + "passed": true + }, + { + "name": "candidate_title", + "passed": true + }, + { + "name": "runtime_observation", + "passed": true + }, + { + "name": "ime_input_expectation", + "passed": true + }, + { + "name": "sent_message_wait", + "passed": true + }, + { + "name": "find_bar", + "passed": true + }, + { + "name": "renderer_metrics", + "passed": true + }, + { + "name": "ime_context", + "passed": true + }, + { + "name": "layout_state", + "passed": true + }, + { + "name": "workspace_state", + "passed": true + }, + { + "name": "transport_completion", + "passed": true + }, + { + "name": "candidate_core", + "passed": true + }, + { + "name": "resize_core", + "passed": true + }, + { + "name": "history_core", + "passed": true + }, + { + "name": "selection_core", + "passed": true + }, + { + "name": "search_core", + "passed": true + }, + { + "name": "paste", + "passed": true + }, + { + "name": "clipboard_policy", + "passed": true + }, + { + "name": "keyboard", + "passed": true + }, + { + "name": "text_action_policy", + "passed": true + }, + { + "name": "candidate_shell_tui", + "passed": false + }, + { + "name": "core", + "passed": true + }, + { + "name": "recovery", + "passed": true + }, + { + "name": "measure", + "passed": true + }, + { + "name": "binary_paths", + "passed": true + }, + { + "name": "benchmark_tools", + "passed": true + }, + { + "name": "differential", + "passed": true + }, + { + "name": "ime_inventory", + "passed": true + }, + { + "name": "text_actions", + "passed": true + }, + { + "name": "workflow_fixture", + "passed": true + }, + { + "name": "workflow_sessions", + "passed": true + }, + { + "name": "workflow_resources", + "passed": true + }, + { + "name": "workflow_resource_phases", + "passed": true + }, + { + "name": "workflow_system_content", + "passed": true + }, + { + "name": "workflow_system_runner", + "passed": true + }, + { + "name": "copy_error_frame", + "passed": true + }, + { + "name": "research_guards", + "passed": true + }, + { + "name": "receive", + "passed": true + }, + { + "name": "conpty", + "passed": true + }, + { + "name": "conpty_ctrl_c", + "passed": true + }, + { + "name": "presentation", + "passed": true + }, + { + "name": "shell", + "passed": true + }, + { + "name": "selection_bytes", + "passed": true + }, + { + "name": "selection_context", + "passed": true + }, + { + "name": "raw_console_receiver", + "passed": true + }, + { + "name": "paste_system", + "passed": true + } + ], + "original_log_sha256": "ef1fc3dd0fcf1be91e506cd4781bece24efaa2d5f4b23c0a793f4de203510e8e" + }, + "final_focused_ctest": { + "tests": 10, + "failures": 0, + "skipped": 0, + "seconds": 42, + "cases": [ + { + "name": "candidate_title", + "passed": true + }, + { + "name": "runtime_observation", + "passed": true + }, + { + "name": "renderer_metrics", + "passed": true + }, + { + "name": "candidate_core", + "passed": true + }, + { + "name": "resize_core", + "passed": true + }, + { + "name": "history_core", + "passed": true + }, + { + "name": "selection_core", + "passed": true + }, + { + "name": "search_core", + "passed": true + }, + { + "name": "candidate_shell_tui", + "passed": true + }, + { + "name": "differential", + "passed": true + } + ], + "original_log_sha256": "df9a200367b56b165d59ead5df5bb5339c1969c06d5120e1ff72f77a15f7229a" + }, + "ctest_scope_note": "Earlier full run was 45/46 before independent review fixes; only failure was Vim not found in process PATH, with a differential Vim internal skip. Final post-review rebuild reran whole candidate_shell_tui and differential with existing Git/usr/bin in child-process PATH only: all 10 focused tests passed, including all three real applications without Vim skip. This is not a claim that final full 46/46 was rerun.", + "comparison_suites": { + "candidate_common": { + "schema": 1, + "version": "0.26.1-dev+termforge.4", + "counts": { + "match": 95 + }, + "passed": true, + "differences_adjudicated": true, + "original_sha256": "59159c23e9c4a58cf7edc0b536e5965481b7e272621bfbc26d1e19c82a2821ee" + }, + "capabilities": { + "schema": 1, + "version": "0.26.1-dev+termforge.4", + "counts": { + "core_unsupported": 15, + "match": 5, + "resize_difference": 5 + }, + "passed": true, + "original_sha256": "a823c1baa4be1a521da3cb02641caf6adbda46aec36675a8e6ceb209ecff887e" + }, + "fuzz": { + "schema": 1, + "version": "0.26.1-dev+termforge.4", + "counts": { + "known_failure": 2, + "match": 6 + }, + "passed": true, + "original_sha256": "9d381324134d98890557e10cd9080ccbf42f9cb3907726b5d6dc1cd87dafac89" + }, + "default_differential": { + "schema": 1, + "counts": { + "expected_difference": 25, + "match": 70 + }, + "passed": true, + "original_sha256": "b4f0c7306d7dc431ed96a527138cf43202b2fe5cc612b078c97d657be781a144" + }, + "real_applications": { + "schema": 1, + "version": "0.26.1-dev+termforge.4", + "counts": { + "match": 3 + }, + "passed": true, + "original_sha256": "1ec0b2ee7afce7c7ce9225cc3a80915fa3687f28e8d8bc85da11551edb39dc46" + } + }, + "normative_boundary_cases": 766, + "normative_scope": "stream-segmentation helper boundaries only, not full Term/VT correctness", + "owned_conpty_runs": [ + { + "repeat": 0, + "evidence": "owned-conpty-0.json", + "sha256": "be68687554de4b935c58f769bfe8cc53ca7658a7482fe1efa138fec91732c32c" + }, + { + "repeat": 1, + "evidence": "owned-conpty-1.json", + "sha256": "279662a4db1747aef7b2a46d2271d2fcc08d99da6a4834004cf1923279df7960" + } + ], + "renderer_metrics": { + "scenarios": 32, + "scope": "DWrite DPI/zoom/backend metrics; fixed emoji actual one glyph/Segoe UI Emoji; missing-glyph negative retained" + } + }, + "review_regressions": [ + { + "finding": "copy included leading wrap padding", + "trace": "ABC1️⃣ at 3x4", + "fix": "explicit leading_spacer metadata skipped by text projection, preserving actual written spaces", + "coverage": "actual CandidateTerminal whole/byte select_all/copy, true-space negative, resize and history" + }, + { + "finding": "INSERT starting on a wide trailer detached old pair (also reproducible with pre-existing literal ASCII path)", + "trace": "中ABCDEF ESC[1;2H ESC[4h 1️⃣", + "fix": "clear hit wide pair before tail shift; remove cropped wide lead", + "coverage": "ASCII/keycap/VS15 whole/byte pair invariant and actual adapter copy" + }, + { + "finding": "zero-width U+070F Prepend overwritten by following A", + "trace": "܏A", + "fix": "forward pending prefix identified by same segmentation engine", + "coverage": "whole/byte actual Term plus prefix observation and adapter original-text copy" + } + ], + "dependency": { + "unicode_segmentation": { + "version": "1.13.3", + "unicode": "17.0.0", + "license": "MIT OR Apache-2.0", + "msrv": "1.85.0", + "runtime_dependencies": 0, + "crate_bytes": 112325, + "sha256": "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + }, + "unicode_width": { + "version": "0.2.2", + "unicode": "17.0.0" + }, + "default_core_unicode": "15.0.0 (unchanged)", + "registry_packages": 59, + "path_packages": 2, + "actual_toolchain": "1.92.0", + "normative_data": { + "cases": 766, + "bytes": 126570, + "sha256": "e2d134d2c52919bace503ebb6a551c1855fe1a1faec18478c78fff254a1793ec", + "license": "Unicode License V3", + "license_sha256": "e7a93b009565cfce55919a381437ac4db883e9da2126fa28b91d12732bc53d96" + } + }, + "cost_diagnostic": { + "raw_directory": "cost", + "raw_samples": 12, + "pairs_per_route": 3, + "minimum_cpu_seconds": 1.0, + "chunk_bytes": 4096, + "observe": false, + "interleaved_order": [ + { + "route": "project", + "variant": "baseline", + "repeat": 0, + "mib_s": 35.13313248743142, + "valid": true + }, + { + "route": "project", + "variant": "candidate", + "repeat": 0, + "mib_s": 23.99623214998744, + "valid": true + }, + { + "route": "project", + "variant": "candidate", + "repeat": 1, + "mib_s": 20.181988135698283, + "valid": true + }, + { + "route": "project", + "variant": "baseline", + "repeat": 1, + "mib_s": 25.864392294883505, + "valid": true + }, + { + "route": "project", + "variant": "baseline", + "repeat": 2, + "mib_s": 25.618390174831767, + "valid": true + }, + { + "route": "project", + "variant": "candidate", + "repeat": 2, + "mib_s": 21.65593524971545, + "valid": true + }, + { + "route": "interactive", + "variant": "baseline", + "repeat": 0, + "mib_s": 11.921779104981667, + "valid": true + }, + { + "route": "interactive", + "variant": "candidate", + "repeat": 0, + "mib_s": 12.011916408888272, + "valid": true + }, + { + "route": "interactive", + "variant": "candidate", + "repeat": 1, + "mib_s": 12.540056052875373, + "valid": true + }, + { + "route": "interactive", + "variant": "baseline", + "repeat": 1, + "mib_s": 14.566102322864767, + "valid": true + }, + { + "route": "interactive", + "variant": "baseline", + "repeat": 2, + "mib_s": 13.841216054068614, + "valid": true + }, + { + "route": "interactive", + "variant": "candidate", + "repeat": 2, + "mib_s": 13.759937264193896, + "valid": true + } + ], + "medians": [ + { + "route": "project", + "baseline_mib_s": 25.864392294883505, + "candidate_mib_s": 21.65593524971545, + "throughput_ratio": 0.8372876115863519, + "throughput_reduction_percent": 16.27123884136481 + }, + { + "route": "interactive", + "baseline_mib_s": 13.841216054068614, + "candidate_mib_s": 12.540056052875373, + "throughput_ratio": 0.9059938089174784, + "throughput_reduction_percent": 9.400619108252162 + } + ], + "summary": "cost/diagnostic.json", + "remaining_acceptance_issue": 34, + "performance_acceptance_passed": false, + "windterm_equivalence_claim": false, + "limitations": [ + "Host shared with other agents", + "No presentation/transport/input-latency timing", + "Observe0 is counter-off but allocator still wrapped" + ] + }, + "unknowns": { + "windterm_runtime_equivalence": false, + "complete_unicode_ime": false, + "all_fonts_scripts": false, + "real_cross_monitor": false, + "presented_done": null + }, + "privacy": "Published JSON is derived from synthetic fixtures and explicit whitelists; raw session command lines, stdout/screens, private absolute paths and desktop names are omitted.", + "source_git_blob_commit": "821b9e6282e41822d5c977914aeca10fe8329c98", + "source_git_blob_sha256": { + "CMakeLists.txt": "98843df715c78b64b58c9aba979cf177b96ce4b52e4e5d1b2c8b6216db4a75f8", + "docs/EVIDENCE.md": "270671d48531dcc2abf4dd0ee4b032ff81904d8b81ec59b248fd80697762dd89", + "docs/FEATURES.md": "ad2edcf9ed03801a0ba7c7831cc6ce5b7efb83df45e666e58c3d6f42ef29efa3", + "docs/milestones/R2-font-display.md": "c62363e991b7018d1261e035e89bad3bcf45aa50342f7adacf2c7bda87ab77af", + "docs/research/streaming-graphemes.md": "d61e2c774b55b09878cbc5ef1c4d831720ab0420d661d3523770f411afeaa85a", + "experiments/candidates/alacritty/Cargo.lock": "896c6e60909d55d61fb26d277b1a98daf9245e8aee811caff37fc3e9d06ca627", + "experiments/candidates/alacritty/Cargo.toml": "c5f89fda48ab73f30adfdcc4437fb3eeb1779647886d85364d91395bf8beec86", + "experiments/candidates/alacritty/PROVENANCE.md": "c27d90c196dab7481c56169a227c3c02aec68ebfa2152c7f21105b0bbc4f1f15", + "experiments/candidates/alacritty/README.md": "41b7dbabf5dbb40885298b1737bbc8811031e248a474ea22887d386cdbbd0a17", + "experiments/candidates/alacritty/grapheme_checks.py": "2a3c816068638a78bc7e5e53be9c29e52ceb9e9e237beb5bb744ff5f0614ef87", + "experiments/candidates/alacritty/include/termforge_candidate.h": "999c635ecd6d4482303d39d6548312afb3179edaeb17d14d4683688d83334ecc", + "experiments/candidates/alacritty/run.py": "e84bfcd530a7a79bcb9e722e507257f9b92a60b2c3ca8043c15da432b6220a01", + "experiments/candidates/alacritty/src/ffi.rs": "8c6fffa0559f0ab5f6e776bab03a35a1375b2b9f977c047da9bc62b8e5c30bfa", + "experiments/candidates/alacritty/src/main.rs": "078f238ded11e2eb6780140d29fb3eb1d41c66bbe2ecc4ed62f40304c43d6260", + "experiments/candidates/alacritty/test_candidate.py": "776fec0e62084e3a5626ca61abebf9a8605307c539faac9fb0ff70dfaef358f4", + "experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml": "bce82942276bab9c4967998d654316f9c85e698babd0945c66a55fcb91ef2333", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "4907427c9488435f1e1ee624629bc8445c4dde5399f0349955d1bbc2042080c4", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs": "8b31cf5389398c74f141bd1f3e7945a1dbde8944bb754f5fd623313c73262496", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/grapheme.rs": "1ef6450b8bf8735b85e7a6fc3bb55c849e317f04778d297020ba3cb4dd7ecf74", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs": "b4a6fdaa4f8c3fdf56b614689ed381408acc68407bd05c8ea592fb6fd0021f78", + "experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/GraphemeBreakTest-17.0.0.txt": "e2d134d2c52919bace503ebb6a551c1855fe1a1faec18478c78fff254a1793ec", + "experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt": "e7a93b009565cfce55919a381437ac4db883e9da2126fa28b91d12732bc53d96", + "experiments/core/CANDIDATE.md": "0571f25d9ba42af2beb75cea0d7ca54211d83c0d3969b817d58ead22967a86c2", + "experiments/core/candidate_terminal.cpp": "0d0eaabe5ef896aa7b263c8151622f579f37c421bf9473fc3d69ada010a64e61", + "experiments/core/candidate_terminal.hpp": "328411c1af6ddb509623ded8fe9c814cb6ac56cd502a2952e8cedd0a3bb2d997", + "experiments/core/candidate_terminal_test.cpp": "df0ee08e4f1ecfcdeaae5286b6204740308fed69a2ffbdb2dcbe3faa0ae04c4a", + "experiments/core/screen.hpp": "fa80d3e67375b0cfacfe4ad640ecef0a47c36ca493499c0054ab5b3e7abf46c1", + "experiments/core/text_projection.hpp": "3853bcf053964e54ea00bb7acfd0d302abd536ae0ed4eddfe791a6bcffdb6914", + "experiments/presentation/render.hpp": "bc360fcf671d36e7e3510c845011c69f81225b8f6dbb7dc5c93db97b0c350b3a", + "experiments/presentation/renderer_metrics_test.cpp": "cfdf2bcc443b92b9cd0152fa4603695d5654b1a98488f21901baa24a51561514", + "experiments/shell/README.md": "bee8bcab9e0eff91e134d7fabf2324ff93b2d29d323930a00c6e45659dd908ef", + "experiments/shell/shell.cpp": "49703b00131d88b491f58287f43daa0a6f12fbf5b36a19a097d901af7d62ce44", + "experiments/shell/test_candidate_tui.py": "ccbab94cf0b9587a9c914c84cd8bec5ae9caabe516767b84457a6be7ff294ed8", + "experiments/shell/unicode_checks.py": "95fffb54274b695be2b857d942555dd8001801eeec5e3f020a9d448cbe1a82e5" + }, + "source_newline_normalization": { + "scope": "The original source_sha256 records actual tested pre-commit worktree bytes and remains unchanged. source_git_blob_sha256 identifies canonical Git blob bytes at the fixed freeze commit, independent of checkout newline conversion. This follow-up changes documentation/proof metadata only, with no product source change or new test matrix claim.", + "normalized_files": 29, + "map": { + "docs/EVIDENCE.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "docs/FEATURES.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "docs/milestones/R2-font-display.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "docs/research/streaming-graphemes.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/Cargo.lock": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/PROVENANCE.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/README.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/grapheme_checks.py": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/include/termforge_candidate.h": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/run.py": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/src/ffi.rs": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/src/main.rs": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/test_candidate.py": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/grapheme.rs": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/core/CANDIDATE.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/core/candidate_terminal.cpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/core/candidate_terminal.hpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/core/candidate_terminal_test.cpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/core/screen.hpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/core/text_projection.hpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/presentation/render.hpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/presentation/renderer_metrics_test.cpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/shell/README.md": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/shell/shell.cpp": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/shell/test_candidate_tui.py": "CRLF worktree bytes -> LF committed blob; no other byte changes", + "experiments/shell/unicode_checks.py": "CRLF worktree bytes -> LF committed blob; no other byte changes" + } + } +} diff --git a/experiments/shell/shell.cpp b/experiments/shell/shell.cpp index 60f62f27..b3681dd0 100644 --- a/experiments/shell/shell.cpp +++ b/experiments/shell/shell.cpp @@ -507,16 +507,23 @@ class ShellWindow { stage_ = 1; return; } - if (stage_ != 1 || !live_contains_line("TF_U_DONE") || !resize_.settled()) return; + if ((stage_ != 1 && stage_ != 2 && stage_ != 3) || !live_contains_line("TF_U_DONE") || !resize_.settled()) return; + if (stage_ == 2) { query_size(); stage_ = 3; return; } + const bool resized = stage_ == 3; + if (resized && !line_is("TF_SIZE_100x30")) return; + if (resized && (parser_.screen.width() != 100 || parser_.screen.height() != 30)) return; + const std::string suffix = resized ? "-resized" : ""; const auto save = [](const std::filesystem::path& path, const std::string& bytes) { std::ofstream out(path, std::ios::binary | std::ios::trunc); out.write(bytes.data(), static_cast(bytes.size())); out.close(); require(bool(out), "Unicode evidence write failed"); }; - save(directory / "candidate-unicode.transport.bin", observed_output_); + save(directory / ("candidate-unicode" + suffix + ".transport.bin"), observed_output_); require(frame_matches_full(), "Unicode retained frame mismatch"); std::ostringstream out; - out << "{\"schema\":\"termforge-candidate-unicode-v1\",\"columns\":" << parser_.screen.width() + out << "{\"schema\":\"termforge-candidate-unicode-v2\",\"phase\":\"" << (resized ? "resized" : "initial") + << "\",\"native_size_marker\":" << (resized ? "\"TF_SIZE_100x30\"" : "null") + << ",\"grapheme_overflows\":" << parser_.grapheme_overflows() << ",\"columns\":" << parser_.screen.width() << ",\"rows\":" << parser_.screen.height() << ",\"dpi\":" << renderer_->dpi() << ",\"cell_width\":" << renderer_->cell_width() << ",\"cell_height\":" << renderer_->cell_height() << ",\"frame_matches_full\":true,\"presented_done\":null,\"lines\":["; @@ -534,7 +541,8 @@ class ShellWindow { const Layout* layout = nullptr; if (cell.width && cell.text != " ") layout = &renderer_->layout(cell.text, cell.style.bold, cell.width); out << "{\"column\":" << column << ",\"width\":" << cell.width - << ",\"marks\":" << cell.marks << ",\"written\":true,\"text\":"; + << ",\"marks\":" << cell.marks << ",\"written\":true,\"clipped\":" << (cell.clipped ? "true" : "false") + << ",\"fragment\":" << (cell.grapheme_fragment ? "true" : "false") << ",\"text\":"; termforge::Screen::quoted(out, cell.text); out << ",\"glyphs\":" << (layout ? layout->glyphs : 0) << ",\"missing\":" << (layout ? layout->missing : 0) @@ -551,8 +559,39 @@ class ShellWindow { } out << "]}"; } + out << "],\"selections\":["; + const std::vector> samples = { + {"TF_U_DECOMPOSE", "e\xcc\x81"}, {"TF_U_EMOJI", "🙂"}, + {"TF_U_ZWJ", "👩\xe2\x80\x8d💻"}, {"TF_U_KEYCAP", "1\xef\xb8\x8f\xe2\x83\xa3"}, + }; + for (size_t sample = 0; sample < samples.size(); ++sample) { + const auto& [label, value] = samples[sample]; + size_t row = 0; + while (row < snapshot_->lines.size() && termforge::project_line(snapshot_->lines[row]).text != label + " " + value) ++row; + require(row < snapshot_->lines.size(), "Unicode selection literal row missing"); + const int column = static_cast(label.size() + 1); + const int width = sample ? 2 : 1; + // Start inside a wide trailing cell, through the product hit-test and + // drag methods; copied text must still contain the complete cluster. + const int x = renderer_->x(column + (width == 2 ? 1 : 0)) + 1; + const int y = renderer_->y(static_cast(row)) + renderer_->cell_height()/2; + selection_down(x, y, 1); + selection_move(renderer_->x(column + width), y); + selection_end(); + const auto copy = selected_text(); + require(copy.status == termforge::SelectionCopyStatus::ok, "Unicode product selection copy failed"); + const auto range = selection_.range(); + require(range.has_value(), "Unicode product selection range missing"); + if (sample) out << ','; + out << "{\"label\":"; termforge::Screen::quoted(out, label); + out << ",\"copied\":"; termforge::Screen::quoted(out, copy.text); + out << ",\"begin\":" << range->begin.column << ",\"end\":" << range->end.column << '}'; + } + selection_.clear(); publish(); + require(frame_matches_full(), "Unicode selection clear frame mismatch"); out << "]}"; - save(directory / "candidate-unicode.json", out.str()); + save(directory / ("candidate-unicode" + suffix + ".json"), out.str()); + if (!resized) { resize_window(100, 30); stage_ = 2; return; } result_.normal = true; type(L"exit 37\r"); stage_ = 6; diff --git a/experiments/shell/test_candidate_tui.py b/experiments/shell/test_candidate_tui.py index 422339e8..7e452db7 100644 --- a/experiments/shell/test_candidate_tui.py +++ b/experiments/shell/test_candidate_tui.py @@ -183,7 +183,8 @@ def test_real_unicode_scalar_preservation_and_glyphs(self): self.assertEqual([sample["label"] for sample in summary["samples"]], ["TF_U_DECOMPOSE", "TF_U_EMOJI", "TF_U_ZWJ", "TF_U_KEYCAP"]) self.assertFalse(summary["complete_unicode_ime_verified"]) - self.assertFalse(summary["whole_cluster_width_policy_verified"]) + self.assertTrue(summary["whole_cluster_width_policy_verified"]) + self.assertTrue(summary["resize"]["resized_native_console_verified"]) self.assertIsNone(summary["presented_done"]) data = json.loads((out / "candidate-unicode.json").read_text(encoding="utf-8")) console = validate_console_window(out, expected_role="unicode", expected_generation=1, @@ -191,7 +192,7 @@ def test_real_unicode_scalar_preservation_and_glyphs(self): source = (out / "candidate-unicode.source.bin").read_bytes() transport = (out / "candidate-unicode.transport.bin").read_bytes() validate_candidate_unicode(data, console, source, transport) - for corruption in ("missing", "width", "cluster", "source", "frame"): + for corruption in ("missing", "width", "cluster", "source", "frame", "glyph-split", "selection", "clipped", "fragment", "counter-type"): changed = copy.deepcopy(data) if corruption == "missing": next(cell for line in changed["lines"] for cell in line["cells"] @@ -204,6 +205,16 @@ def test_real_unicode_scalar_preservation_and_glyphs(self): if cell["text"] in ("👩\u200d", "👩\u200d💻"))["text"] = "👩" elif corruption == "frame": changed["frame_matches_full"] = False + elif corruption == "glyph-split": + next(cell for line in changed["lines"] for cell in line["cells"] + if cell["text"] == "1\ufe0f\u20e3")["glyphs"] = 2 + elif corruption == "selection": + changed["selections"][2]["copied"] = "👩" + elif corruption == "counter-type": + changed["grapheme_overflows"] = False + elif corruption in ("clipped", "fragment"): + next(cell for line in changed["lines"] for cell in line["cells"] + if cell["text"] == "👩\u200d💻")[corruption] = True with self.subTest(repeat=repeat, corruption=corruption): with self.assertRaises(ValueError): validate_candidate_unicode(changed, console, b"" if corruption == "source" else source, diff --git a/experiments/shell/unicode_checks.py b/experiments/shell/unicode_checks.py index 2e39bc11..9d4c9739 100644 --- a/experiments/shell/unicode_checks.py +++ b/experiments/shell/unicode_checks.py @@ -11,8 +11,8 @@ SAMPLES = ( ("TF_U_DECOMPOSE", "e\u0301", 1), ("TF_U_EMOJI", "🙂", 2), - ("TF_U_ZWJ", "👩\u200d💻", None), - ("TF_U_KEYCAP", "1\ufe0f\u20e3", None), + ("TF_U_ZWJ", "👩\u200d💻", 2), + ("TF_U_KEYCAP", "1\ufe0f\u20e3", 2), ) DONE = "TF_U_DONE" @@ -58,7 +58,7 @@ def _cell_observations(cells, columns, dpi, cell_width): "actual DWrite glyph, missing-glyph, font or grid-width check") -def validate_candidate_unicode(data, console, source, transport): +def validate_candidate_unicode(data, console, source, transport, *, resized=False): _require(source == expected_source(), "Unicode source literal differs") _require(type(transport) is bytes and 0 < len(transport) <= 8192, "Unicode transport bound") position = 0 @@ -89,14 +89,20 @@ def validate_candidate_unicode(data, console, source, transport): console_samples.append({"label": label, "projection": "original" if original else "replacement-non-bmp", "widths": [2 if attributes & 0x100 else 0 if attributes & 0x200 else 1 for _, attributes in sample_cells]}) - _require(type(data) is dict and data.get("schema") == "termforge-candidate-unicode-v1" and - data.get("columns") == console["columns"] and data.get("rows") == console["rows"] and + _require(type(data) is dict and data.get("schema") == "termforge-candidate-unicode-v2" and + data.get("phase") == ("resized" if resized else "initial") and + data.get("columns") == (100 if resized else console["columns"]) and + data.get("rows") == (30 if resized else console["rows"]) and + type(data.get("grapheme_overflows")) is int and data["grapheme_overflows"] == 0 and + data.get("native_size_marker") == ("TF_SIZE_100x30" if resized else None) and type(data.get("dpi")) is int and 48 <= data["dpi"] <= 768 and type(data.get("cell_width")) is int and data["cell_width"] > 0 and type(data.get("cell_height")) is int and data["cell_height"] > 0 and data.get("frame_matches_full") is True and data.get("presented_done") is None and type(data.get("lines")) is list and len(data["lines"]) == data["rows"], "candidate window identity, geometry or frame") + if resized: + _require(b"TF_SIZE_100x30" in transport, "native resized console acknowledgement missing") lines = data["lines"] _require(all(type(line) is dict and line.get("row") == row and type(line.get("text")) is str for row, line in enumerate(lines)), "candidate visible rows") @@ -117,6 +123,11 @@ def validate_candidate_unicode(data, console, source, transport): _require(following["column"] == previous["column"] + previous["width"], "candidate scalar cells are not contiguous: " + label) cell = cells[0] + _require(cell.get("clipped") is False and cell.get("fragment") is False, + "fixed Unicode sample was clipped or fragmented") + if label in ("TF_U_EMOJI", "TF_U_ZWJ", "TF_U_KEYCAP"): + _require(cell["glyphs"] == 1 and cell["faces"] == ["Segoe UI Emoji"], + "emoji cluster was split across actual glyphs or fallback fonts") if width is not None: _require(len(cells) == 1 and cell["text"] == text and cell["width"] == width, "candidate established cell policy differs: " + label) @@ -128,13 +139,23 @@ def validate_candidate_unicode(data, console, source, transport): accepted.append({"label": label, "text": text, "cells": [ {"column": value["column"], "width": value["width"], "text": value["text"], "glyphs": value["glyphs"], "faces": value["faces"]} for value in cells]}) - return {"samples": accepted, "console_samples": console_samples, + selections = data.get("selections") + _require(type(selections) is list and len(selections) == len(SAMPLES), + "product Unicode selections missing") + for selection, (label, text, width) in zip(selections, SAMPLES): + _require(type(selection) is dict and selection.get("label") == label and + selection.get("copied") == text and selection.get("begin") == len(label) + 1 and + selection.get("end") == len(label) + 1 + width, + "product drag split, changed or omitted a grapheme") + return {"samples": accepted, "selections": selections, "console_samples": console_samples, "source_sha256": hashlib.sha256(source).hexdigest(), "transport_sha256": hashlib.sha256(transport).hexdigest(), "transport_bytes": len(transport), "scoped_validation_passed": True, - "scope": "scalar-preservation-and-glyph-only", - "complex_grapheme_rendering_verified": False, - "whole_cluster_width_policy_verified": False, + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "complex_grapheme_rendering_verified": True, + "whole_cluster_width_policy_verified": True, + "resized_native_console_verified": resized, + "windterm_runtime_equivalence_verified": False, "complete_unicode_ime_verified": False, "presented_done": None} @@ -153,4 +174,9 @@ def validate_unicode_output(directory): prefix="candidate-unicode") source = _bounded(directory / "candidate-unicode.source.bin", 4096) transport = _bounded(directory / "candidate-unicode.transport.bin", 8192) - return validate_candidate_unicode(data, console, source, transport) + initial = validate_candidate_unicode(data, console, source, transport) + resized = json.loads(_bounded(directory / "candidate-unicode-resized.json", 512000).decode("utf-8")) + resized_transport = _bounded(directory / "candidate-unicode-resized.transport.bin", 8192) + after = validate_candidate_unicode(resized, console, source, resized_transport, resized=True) + initial["resize"] = after + return initial