<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>nqlsg_tanoshi</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://lunzima.net/</id>
  <link href="https://lunzima.net/" rel="alternate"/>
  <link href="https://lunzima.net/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, nqlsg_tanoshi</rights>
  <subtitle>我的学习心得</subtitle>
  <title>农企临时工单推人 - 我的学习心得</title>
  <updated>2026-09-25T15:32:10.545Z</updated>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="原神" scheme="https://lunzima.net/tags/%E5%8E%9F%E7%A5%9E/"/>
    <content>
      <![CDATA[<p>这件事是官方先做的。纳塔是《原神》里的火之国，而官方给纳塔写的那些带人声的曲目，歌词用的就是斯瓦希里语。所以想给纳塔写歌的时候，斯瓦希里语不是拿来制造异域感的，它就是这个地区本来的语言。</p><p>这批二创是我照着同一条路做的。合集叫「原神纳塔斯瓦希里语二创歌曲」，25 首，从 2025 年 5 月做到 2026 年 6 月，总长 93 分钟，播放加起来 23 万次。合集自己的说明是「原创或者重新填词的斯瓦希里语歌曲」。斯瓦希里语的歌词是我自己填的，演唱是合成的。</p><h2 id="四种写法"><a href="#四种写法" class="headerlink" title="四种写法"></a>四种写法</h2><p>这 25 首不是同一个主题下的变奏。立意分几路：</p><ul><li>怀念逝去的战友，以及「队长」卡皮塔诺。他与夜神融合之后长眠。</li><li>歌颂玛薇卡与旅行者「杜麦尼」的功绩。</li><li>与前任火神希巴拉克的灵魂对话。</li><li>借「玛薇卡驾驶百吨王重卡吓哭百万丘丘人」这个二创，写角色的心路历程。</li></ul><p>前三路都在正面处理纳塔的主线：牺牲、功绩、传承。第四路换了个壳子，把同样重的东西装进一个荒诞的画面。这个二创在华语玩家中间本来就流传，拿它当容器，反而让「她一个人扛下了什么」这句话容易出口。</p><p>四路不能被压成一句话。把它们的主题并成一个说法，每一首都会被读错。</p><h2 id="部分歌曲来路"><a href="#部分歌曲来路" class="headerlink" title="部分歌曲来路"></a>部分歌曲来路</h2><p>列举部分歌曲的原曲。左边是原曲，右边是这一版：</p><ul><li>《念张师》→《卡皮塔诺，我还记得你》</li><li>《天真的橡皮》→《一笑了之》</li><li>《错位时空》→《错置时空》</li><li>《如愿》→《如愿》</li><li>《怀念战友》→《怀念战友》</li><li>《映山红》→《烈阳花》</li><li>《去伊犁的路上》→《去坚岩隘谷的路上》</li><li>《嘎达梅林》→《卡皮塔诺》</li><li>《翻身农奴把歌唱》→《纳塔族人把歌唱》</li><li>《你若三冬》→《你从圣火来》</li><li>《没有您就没有祖国》→《没有您就没有纳塔》</li></ul><p>《如愿》与《怀念战友》两首连题名都没换。改动最轻的是《错位时空》，只动了一个字。</p><h2 id="写了谁"><a href="#写了谁" class="headerlink" title="写了谁"></a>写了谁</h2><p>25 首里，玛薇卡占 12 首。茜特菈莉与希诺宁各 2 首，玛拉妮 1 首。剩下 8 首不点角色名，写的是纳塔这个国家本身。</p><p>有两首配的是游戏里搭出来的景：一首对着纳塔英雄纪念碑与公墓，一首对着一座金字塔。游戏里能搭的东西有限，所以这两首更像是在替某处地点配一首它本来就该有的歌。</p><h2 id="收听"><a href="#收听" class="headerlink" title="收听"></a>收听</h2><p>合集与第一首都在 B 站：<a href="https://www.bilibili.com/video/BV1FbVf6vEFA">https://www.bilibili.com/video/BV1FbVf6vEFA</a></p><p>23 万次播放不算多，但这批东西是 25 首完整的歌，用一门外语，为一个游戏地区，做了十三个月。</p>]]>
    </content>
    <id>https://lunzima.net/natlan-swahili-songs.html</id>
    <link href="https://lunzima.net/natlan-swahili-songs.html"/>
    <published>2026-09-25T08:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<p>这件事是官方先做的。纳塔是《原神》里的火之国，而官方给纳塔写的那些带人声的曲目，歌词用的就是斯瓦希里语。所以想给纳塔写歌的时候，斯瓦希里语不是拿来制造异域感的，它就是这个地区本来的语言。</p>
<p>这批二创是我照着同一条路做的。合集叫「原神纳塔斯瓦希里语二创歌曲」，25 首，从 2025 年 5 月做到 2026 年 6 月，总长 93 分钟，播放加起来 23 万次。合集自己的说明是「原创或者重新填词的斯瓦希里语歌曲」。斯瓦希里语的歌词是我自己填的，演唱是合成的。</p>
<h2 id="四种写法"><a href="#四种写法" class="headerlink" title="四种写法"></a>四种写法</h2><p>这 25 首不是同一个主题下的变奏。立意分几路：</p>
<ul>
<li>怀念逝去的战友，以及「队长」卡皮塔诺。他与夜神融合之后长眠。</li>
<li>歌颂玛薇卡与旅行者「杜麦尼」的功绩。</li>
<li>与前任火神希巴拉克的灵魂对话。</li>
<li>借「玛薇卡驾驶百吨王重卡吓哭百万丘丘人」这个二创，写角色的心路历程。</li>
</ul>
<p>前三路都在正面处理纳塔的主线：牺牲、功绩、传承。第四路换了个壳子，把同样重的东西装进一个荒诞的画面。这个二创在华语玩家中间本来就流传，拿它当容器，反而让「她一个人扛下了什么」这句话容易出口。</p>
<p>四路不能被压成一句话。把它们的主题并成一个说法，每一首都会被读错。</p>
<h2 id="部分歌曲来路"><a href="#部分歌曲来路" class="headerlink" title="部分歌曲来路"></a>部分歌曲来路</h2><p>列举部分歌曲的原曲。左边是原曲，右边是这一版：</p>
<ul>
<li>《念张师》→《卡皮塔诺，我还记得你》</li>
<li>《天真的橡皮》→《一笑了之》</li>
<li>《错位时空》→《错置时空》</li>
<li>《如愿》→《如愿》</li>
<li>《怀念战友》→《怀念战友》</li>
<li>《映山红》→《烈阳花》</li>
<li>《去伊犁的路上》→《去坚岩隘谷的路上》</li>
<li>《嘎达梅林》→《卡皮塔诺》</li>
<li>《翻身农奴把歌唱》→《纳塔族人把歌唱》</li>
<li>《你若三冬》→《你从圣火来》</li>
<li>《没有您就没有祖国》→《没有您就没有纳塔》</li>
</ul>
<p>《如愿》与《怀念战友》两首连题名都没换。改动最轻的是《错位时空》，只动了一个字。</p>
<h2 id="写了谁"><a href="#写了谁" class="headerlink" title="写了谁"></a>写了谁</h2><p>25 首里，玛薇卡占 12 首。茜特菈莉与希诺宁各 2 首，玛拉妮 1 首。剩下 8 首不点角色名，写的是纳塔这个国家本身。</p>
<p>有两首配的是游戏里搭出来的景：一首对着纳塔英雄纪念碑与公墓，一首对着一座金字塔。游戏里能搭的东西有限，所以这两首更像是在替某处地点配一首它本来就该有的歌。</p>
<h2 id="收听"><a href="#收听" class="headerlink" title="收听"></a>收听</h2><p>合集与第一首都在 B 站：<a href="https://www.bilibili.com/video/BV1FbVf6vEFA">https://www.bilibili.com/video/BV1FbVf6vEFA</a></p>
<p>23 万次播放不算多，但这批东西是 25 首完整的歌，用一门外语，为一个游戏地区，做了十三个月。</p>]]>
    </summary>
    <title>用斯瓦希里语唱纳塔</title>
    <updated>2026-09-25T15:32:10.545Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="开发" scheme="https://lunzima.net/tags/%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<p>这是两个 peephole linter，给土豆级硬件用的：扫一遍汇编，指出哪些地方有更划算的写法。<br>目标 ISA 都不再新，却还有机器在跑 —— 而编译器的默认调优早已不是为它们做的。</p><p>一个扫 ARMv5TE 汇编，一个扫 Pentium 与 pre-P6（P5、Cyrix、K6）以及 Pentium 4 的 x86 汇编。</p><p>它们找的是在目标 CPU 上划不来的序列。只要划不来就值得看一眼，指令条数多少不是重点。</p><p>判据不靠个人经验，全部锚在 gcc 的权威源上，分三类：</p><ul><li>gcc 明确写下的怪癖。<code>gcc/config/i386/x86-tune.def</code> 里的 <code>X86_TUNE_*</code> 标志，那些「避开某条指令」的条目。</li><li>cost table 对比。一条指令在目标 CPU 上的开销，对比 Pentium II（x86）或 Cortex-A8（ARMv7-A）这两个基线。</li><li>原理上该避免的行为。长流水线上分支预测代价这一类行为谓词。</li></ul><h2 id="为什么是-census-而不是-gate"><a href="#为什么是-census-而不是-gate" class="headerlink" title="为什么是 census 而不是 gate"></a>为什么是 census 而不是 gate</h2><p>工具输出一份清单，不直接判定失败。</p><p>同一条指令在目标 CPU 上更贵，不等于这段代码就该改。改写要证明的东西在工具看不到的地方：改完之后原来被依赖的标志位是不是已经死了，对齐是不是还成立，时序有没有被别的指令补上。这些只有读代码的人能证明。</p><p>所以两个 linter 都是普查，不是闸门。报告「看这里」，判断留给读的人。</p><p>例外有两处，都在 ARM 那个里：<code>--isa</code> 是 ISA 门，<code>-a</code> 里的 Extended Asm 契约检查也算。这两处不存在权衡。指令集里没有的指令，或者写错的约束契约，就是错，没有可商量的余地。</p><h2 id="构成"><a href="#构成" class="headerlink" title="构成"></a>构成</h2><p>每个 linter 自成项目，彼此不共享代码。Python，加一张模式规则表，加两组测试：单元测试用合成的指令序列，集成测试用真实反汇编夹具。各自带 Makefile 与 <code>pyproject.toml</code>。</p><p>两个脚本各自在自己的目录里跑：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python3 armv5telint.py arm.dis</span><br><span class="line">python3 pentiumlint.py obj.wdis</span><br></pre></td></tr></table></figure><p>两个目录各自 <code>make test</code>，跑全部模块的测试。</p><p>项目在 <a href="https://github.com/lunzima/potatolint">https://github.com/lunzima/potatolint</a>，许可 MIT。代码与文档里出现的处理器、架构与设备名称是各自所有者的商标。</p>]]>
    </content>
    <id>https://lunzima.net/potatolint.html</id>
    <link href="https://lunzima.net/potatolint.html"/>
    <published>2026-09-25T07:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<p>这是两个 peephole linter，给土豆级硬件用的：扫一遍汇编，指出哪些地方有更划算的写法。<br>目标 ISA 都不再新，却还有机器在跑 —— 而编译器的默认调优早已不是为它们做的。</p>
<p>一个扫 ARMv5TE 汇编，一个扫 Pentium 与 pre-P6（P5、Cyrix、K6）以及 Pentium 4 的 x86 汇编。</p>
<p>它们找的是在目标 CPU 上划不来的序列。只要划不来就值得看一眼，指令条数多少不是重点。</p>
<p>判据不靠个人经验，全部锚在 gcc 的权威源上，分三类：</p>
<ul>
<li>gcc 明确写下的怪癖。<code>gcc/config/i386/x86-tune.def</code> 里的 <code>X86_TUNE_*</code> 标志，那些「避开某条指令」的条目。</li>
<li>cost table 对比。一条指令在目标 CPU 上的开销，对比 Pentium II（x86）或 Cortex-A8（ARMv7-A）这两个基线。</li>
<li>原理上该避免的行为。长流水线上分支预测代价这一类行为谓词。</li>
</ul>
<h2 id="为什么是-census-而不是-gate"><a href="#为什么是-census-而不是-gate" class="headerlink" title="为什么是 census 而不是 gate"></a>为什么是 census 而不是 gate</h2><p>工具输出一份清单，不直接判定失败。</p>
<p>同一条指令在目标 CPU 上更贵，不等于这段代码就该改。改写要证明的东西在工具看不到的地方：改完之后原来被依赖的标志位是不是已经死了，对齐是不是还成立，时序有没有被别的指令补上。这些只有读代码的人能证明。</p>
<p>所以两个 linter 都是普查，不是闸门。报告「看这里」，判断留给读的人。</p>
<p>例外有两处，都在 ARM 那个里：<code>--isa</code> 是 ISA 门，<code>-a</code> 里的 Extended Asm 契约检查也算。这两处不存在权衡。指令集里没有的指令，或者写错的约束契约，就是错，没有可商量的余地。</p>
<h2 id="构成"><a href="#构成" class="headerlink" title="构成"></a>构成</h2><p>每个 linter 自成项目，彼此不共享代码。Python，加一张模式规则表，加两组测试：单元测试用合成的指令序列，集成测试用真实反汇编夹具。各自带 Makefile 与 <code>pyproject.toml</code>。</p>
<p>两个脚本各自在自己的目录里跑：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python3 armv5telint.py arm.dis</span><br><span class="line">python3 pentiumlint.py obj.wdis</span><br></pre></td></tr></table></figure>

<p>两个目录各自 <code>make test</code>，跑全部模块的测试。</p>
<p>项目在 <a href="https://github.com/lunzima/potatolint">https://github.com/lunzima/potatolint</a>，许可 MIT。代码与文档里出现的处理器、架构与设备名称是各自所有者的商标。</p>]]>
    </summary>
    <title>potatolint：给土豆级硬件的指令优化建议</title>
    <updated>2026-09-25T15:28:43.560Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="资源" scheme="https://lunzima.net/tags/%E8%B5%84%E6%BA%90/"/>
    <content>
      <![CDATA[<p>输入法词典的价值不在词表有多大，而在词频排序对不对。同一份词表，词频不准，候选顺序就是错的，每次选词都要多按一下。</p><p>这个项目是一套全拼词典，覆盖单字到长词组。词频不来自单一词表，而是多个互相独立的来源融合校准而成。清洗、去冗余、离群点检测与合并的完整做法单独写在一份文档里，不藏在脚本里。</p><h2 id="同一份数据出四种格式"><a href="#同一份数据出四种格式" class="headerlink" title="同一份数据出四种格式"></a>同一份数据出四种格式</h2><p>引擎吃的格式不一样，所以按引擎分发。</p><ul><li>Rime：源码词典 <code>pinyin.dict.yaml</code>，全量约 53.4 万条。精简版是 <code>pinyin_lite.dict.yaml</code>，约 23.6 万条。</li><li>fcitx5 与 libime：预编译的 <code>pinyin_fcitx5.dict</code>，对应全量版。</li><li>fcitx4 等旧引擎：预编译的 <code>pinyin_lite_fcitx4.mb</code>，对应精简版。</li><li>纯文本导入：<code>dict.txt</code>，约 52.5 万条。</li></ul><p>预编译那两份的意义是省掉一次编译。fcitx 用户拿到就能用，不需要在本机把词表重新编一遍。</p><h2 id="词频列为什么有两份"><a href="#词频列为什么有两份" class="headerlink" title="词频列为什么有两份"></a>词频列为什么有两份</h2><p><code>dict.txt</code> 的词频是浮点数，<code>dict_intrank.txt</code> 的内容一致，词频是整数。</p><p>浮点词频能表达更细的名次差别，但不是每个引擎都收。采用整数词频机制的输入法只认排名，喂浮点数会被截断或拒绝。分成两份，是为了不让人自己去换算，也避免换算时按错误的方式取整。</p><h2 id="纯文本词库里不含单字"><a href="#纯文本词库里不含单字" class="headerlink" title="纯文本词库里不含单字"></a>纯文本词库里不含单字</h2><p>纯文本那两份只收两字以上的词组，单字只在 Rime 的源码词典里。</p><p>它们的定位是增补词库，不是基础词库 —— 基础词库里的单字够用，这里只补词组。<br>需要逐字输入的场景用 Rime 那两份，只要词组排序的场合用文本那份。</p><h2 id="取用"><a href="#取用" class="headerlink" title="取用"></a>取用</h2><p>按自己用的引擎挑一个文件。Rime 用户把 <code>.dict.yaml</code> 放进用户目录重新部署；fcitx5 与 fcitx4 直接用预编译文件。想自己导入的，先看清目标软件收浮点还是整数词频，再选 <code>dict.txt</code> 或 <code>dict_intrank.txt</code>。</p><p>项目在 <a href="https://github.com/lunzima/ChsPinyinUDL">https://github.com/lunzima/ChsPinyinUDL</a>，许可 MIT。词典的更新频率不会很高，问题与词汇建议走 issue 即可。</p>]]>
    </content>
    <id>https://lunzima.net/pinyin-dict.html</id>
    <link href="https://lunzima.net/pinyin-dict.html"/>
    <published>2026-09-25T06:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<p>输入法词典的价值不在词表有多大，而在词频排序对不对。同一份词表，词频不准，候选顺序就是错的，每次选词都要多按一下。</p>
<p>这个项目是一套全拼词典，覆盖单字到长词组。词频不来自单一词表，而是多个互相独立的来源融合校准而成。清洗、去冗余、离群点检测与合并的完整做法单独写在一份文档里，不藏在脚本里。</p>
<h2 id="同一份数据出四种格式"><a href="#同一份数据出四种格式" class="headerlink" title="同一份数据出四种格式"></a>同一份数据出四种格式</h2><p>引擎吃的格式不一样，所以按引擎分发。</p>
<ul>
<li>Rime：源码词典 <code>pinyin.dict.yaml</code>，全量约 53.4 万条。精简版是 <code>pinyin_lite.dict.yaml</code>，约 23.6 万条。</li>
<li>fcitx5 与 libime：预编译的 <code>pinyin_fcitx5.dict</code>，对应全量版。</li>
<li>fcitx4 等旧引擎：预编译的 <code>pinyin_lite_fcitx4.mb</code>，对应精简版。</li>
<li>纯文本导入：<code>dict.txt</code>，约 52.5 万条。</li>
</ul>
<p>预编译那两份的意义是省掉一次编译。fcitx 用户拿到就能用，不需要在本机把词表重新编一遍。</p>
<h2 id="词频列为什么有两份"><a href="#词频列为什么有两份" class="headerlink" title="词频列为什么有两份"></a>词频列为什么有两份</h2><p><code>dict.txt</code> 的词频是浮点数，<code>dict_intrank.txt</code> 的内容一致，词频是整数。</p>
<p>浮点词频能表达更细的名次差别，但不是每个引擎都收。采用整数词频机制的输入法只认排名，喂浮点数会被截断或拒绝。分成两份，是为了不让人自己去换算，也避免换算时按错误的方式取整。</p>
<h2 id="纯文本词库里不含单字"><a href="#纯文本词库里不含单字" class="headerlink" title="纯文本词库里不含单字"></a>纯文本词库里不含单字</h2><p>纯文本那两份只收两字以上的词组，单字只在 Rime 的源码词典里。</p>
<p>它们的定位是增补词库，不是基础词库 —— 基础词库里的单字够用，这里只补词组。<br>需要逐字输入的场景用 Rime 那两份，只要词组排序的场合用文本那份。</p>
<h2 id="取用"><a href="#取用" class="headerlink" title="取用"></a>取用</h2><p>按自己用的引擎挑一个文件。Rime 用户把 <code>.dict.yaml</code> 放进用户目录重新部署；fcitx5 与 fcitx4 直接用预编译文件。想自己导入的，先看清目标软件收浮点还是整数词频，再选 <code>dict.txt</code> 或 <code>dict_intrank.txt</code>。</p>
<p>项目在 <a href="https://github.com/lunzima/ChsPinyinUDL">https://github.com/lunzima/ChsPinyinUDL</a>，许可 MIT。词典的更新频率不会很高，问题与词汇建议走 issue 即可。</p>]]>
    </summary>
    <title>ChsPinyinUDL：词频校准与四种格式</title>
    <updated>2026-09-25T15:30:26.261Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="杂项" scheme="https://lunzima.net/tags/%E6%9D%82%E9%A1%B9/"/>
    <content>
      <![CDATA[<p>这批 MIDI 面向三种目标：PC 蜂鸣器、OPL2 声卡，以及早期的通用 MIDI 音源。</p><p>它们的共同点是能力窄：复音数在一到九之间，没有力度曲线，音源严格照谱发声。现代软音源会掩盖的问题，在这些设备上全都听得见，因此只能在写谱阶段处理，回放阶段无法补救。</p><p>parlour arrangement 的旧义就是这个意思：把熟悉的曲子改编到家中那件乐器能演奏的程度。</p><h2 id="复音数是实测出来的"><a href="#复音数是实测出来的" class="headerlink" title="复音数是实测出来的"></a>复音数是实测出来的</h2><p>复音数指同时发音的上限。每首曲子标注的是实测峰值，即一台设备播完整首曲子所需的同时发音数。</p><ul><li>卡农 4 复音，2 分 09 秒，63 bpm。主题在通道 0 上是一条不断的线，单音蜂鸣器只取那一条就能完整演奏。</li><li>时暮的思眷（Le Souvenir avec le crépuscule）7 复音，是这批里最高的一首。复音数不到六的设备够不到它。</li><li>致爱丽丝 4 复音。整个回旋曲与两个插部都在。</li><li>嘎达梅林 6 复音。低音线降到了底八度。</li><li>绿袖子 5 复音，2 分 57 秒，是这批里最长的。</li><li>献给已逝公主的七重奏 6 复音。1815 个音，83 小节，27 个速度变化，是最忙的一份。</li><li>雪城之夜（莫斯科郊外的晚上）5 复音。它是这批里唯一的 2/4 拍曲子。</li><li>Tico-Tico no Fubá 4 复音。190 bpm，是最快的一首。</li><li>成功音效 3 复音，0.5 秒，五个音。</li></ul><h2 id="谱面写成-Python-脚本"><a href="#谱面写成-Python-脚本" class="headerlink" title="谱面写成 Python 脚本"></a>谱面写成 Python 脚本</h2><p>每首曲子是一个 Python 脚本，谱面作为字面数据写在源码里，不用 MIDI 库，也没有编辑器工程。运行脚本会在 <code>output/</code> 下逐字节复现那个文件。</p><p>曲子用于循环播放，配在其他内容底下。音效不循环，半秒左右播完，单独收听，因此另放一个目录。</p><h2 id="硬伤与软伤的分类"><a href="#硬伤与软伤的分类" class="headerlink" title="硬伤与软伤的分类"></a>硬伤与软伤的分类</h2><p><code>midi_check.py</code> 把文件的问题分成两类。</p><ul><li>硬伤：设备做不到。复音数超过预算、音符短于目标能发音的最短长度、同一通道撞上正在响的同音高，都算。文件在修正之前都是错的。</li><li>软伤：文件能播，但写法是否合适属于判断。长时间保持的撞音、低音区浑浊的间距、演奏者够不到的音域与重击、起音偏离网格的距离，都需要逐一复核。</li></ul><p><code>midi_stats.py</code> 只计数与测量，不做判断。</p><p>复音预算和最短音符长度都可以从命令行给定，便于按目标设备复核。</p><p>编曲与两个检查脚本在 <a href="https://github.com/lunzima/parlour-midi">https://github.com/lunzima/parlour-midi</a>。</p>]]>
    </content>
    <id>https://lunzima.net/parlour-midi-voices.html</id>
    <link href="https://lunzima.net/parlour-midi-voices.html"/>
    <published>2026-09-25T05:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<p>这批 MIDI 面向三种目标：PC 蜂鸣器、OPL2 声卡，以及早期的通用 MIDI 音源。</p>
<p>它们的共同点是能力窄：复音数在一到九之间，没有力度曲线，音源严格照谱发声。现代软音源会掩盖的问题，在这些设备上全都听得见，因此只能在写谱阶段处理，回放阶段无法补救。</p>
<p>parlour arrangement 的旧义就是这个意思：把熟悉的曲子改编到家中那件乐器能演奏的程度。</p>
<h2 id="复音数是实测出来的"><a href="#复音数是实测出来的" class="headerlink" title="复音数是实测出来的"></a>复音数是实测出来的</h2><p>复音数指同时发音的上限。每首曲子标注的是实测峰值，即一台设备播完整首曲子所需的同时发音数。</p>
<ul>
<li>卡农 4 复音，2 分 09 秒，63 bpm。主题在通道 0 上是一条不断的线，单音蜂鸣器只取那一条就能完整演奏。</li>
<li>时暮的思眷（Le Souvenir avec le crépuscule）7 复音，是这批里最高的一首。复音数不到六的设备够不到它。</li>
<li>致爱丽丝 4 复音。整个回旋曲与两个插部都在。</li>
<li>嘎达梅林 6 复音。低音线降到了底八度。</li>
<li>绿袖子 5 复音，2 分 57 秒，是这批里最长的。</li>
<li>献给已逝公主的七重奏 6 复音。1815 个音，83 小节，27 个速度变化，是最忙的一份。</li>
<li>雪城之夜（莫斯科郊外的晚上）5 复音。它是这批里唯一的 2/4 拍曲子。</li>
<li>Tico-Tico no Fubá 4 复音。190 bpm，是最快的一首。</li>
<li>成功音效 3 复音，0.5 秒，五个音。</li>
</ul>
<h2 id="谱面写成-Python-脚本"><a href="#谱面写成-Python-脚本" class="headerlink" title="谱面写成 Python 脚本"></a>谱面写成 Python 脚本</h2><p>每首曲子是一个 Python 脚本，谱面作为字面数据写在源码里，不用 MIDI 库，也没有编辑器工程。运行脚本会在 <code>output/</code> 下逐字节复现那个文件。</p>
<p>曲子用于循环播放，配在其他内容底下。音效不循环，半秒左右播完，单独收听，因此另放一个目录。</p>
<h2 id="硬伤与软伤的分类"><a href="#硬伤与软伤的分类" class="headerlink" title="硬伤与软伤的分类"></a>硬伤与软伤的分类</h2><p><code>midi_check.py</code> 把文件的问题分成两类。</p>
<ul>
<li>硬伤：设备做不到。复音数超过预算、音符短于目标能发音的最短长度、同一通道撞上正在响的同音高，都算。文件在修正之前都是错的。</li>
<li>软伤：文件能播，但写法是否合适属于判断。长时间保持的撞音、低音区浑浊的间距、演奏者够不到的音域与重击、起音偏离网格的距离，都需要逐一复核。</li>
</ul>
<p><code>midi_stats.py</code> 只计数与测量，不做判断。</p>
<p>复音预算和最短音符长度都可以从命令行给定，便于按目标设备复核。</p>
<p>编曲与两个检查脚本在 <a href="https://github.com/lunzima/parlour-midi">https://github.com/lunzima/parlour-midi</a>。</p>]]>
    </summary>
    <title>复音数一到九：给旧声卡编曲</title>
    <updated>2026-09-25T13:47:26.255Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="开发" scheme="https://lunzima.net/tags/%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<p>引擎叫 llama98-zh，可执行文件是 <code>llama98</code>，目标机器是奔腾 II / III 级的 Windows 9x 和 DOS。构建由同一个 Makefile 驱动 gcc 与 Open Watcom，产物有四类：</p><ul><li>Win32 的 CLI</li><li>Win32 的 GUI，产物名是 <code>kunkun98-gui</code></li><li>一个 DLL（动态链接库）</li><li>一个 32 位 DOS 扩展器版本</li></ul><p>引擎和桌面端在 llama98-zh 仓库里。它跑的模型在另一个仓库，叫 Kunkun98。</p><p>模型是 134,529,456 个参数，量化后 103.5 MiB。架构叫 KunMoE：8 层里 6 层是 KDA 线性注意力、2 层是完整注意力，16 个专家走 top-2 路由，另加 1 个共享专家。权重部分取自 Qwen/Qwen3.5-0.8B，许可跟着上游走 Apache-2.0。把 <code>MODEL/</code> 目录交给引擎就能对话：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">llama98 MODEL --chat --prompt &quot;你好&quot;</span><br></pre></td></tr></table></figure><p>同一份源码要在两套工具链上编译，在参考实现、SSE2（流式 SIMD 扩展 2）与 AVX2（高级矢量扩展 2）三层内核上运行。如果三层内核给出的结果不同，输出出错时就无法判断问题出在模型还是内核。引擎的目标因此定成逐字节一致。</p><h2 id="三层内核互为对照"><a href="#三层内核互为对照" class="headerlink" title="三层内核互为对照"></a>三层内核互为对照</h2><p>AVX2 与 SSE2 在 kmr20/zh 上跑 603 个 token，逐字节相同。</p><p>浮点语义也统一了。默认改成向零舍入加冲刷到零，ARM 的软浮点与 x86 因此逐字节一致；就近舍入只作为诊断选项保留。</p><p>0.1.6 修掉了一处浮点舍入不一致。Watcom 那条路径只截断了 387 的控制字，没有写 MXCSR。SSE2 内核按 MXCSR 舍入，C 路径在提供的快速浮点模式下截断，只有 SSE2 那一层用就近舍入。结果是 78,905,344 字节里有 59,349,441 与 gcc 构建不一致。两份构建的输出从第一个 token 起就不同。</p><h2 id="构建过程中的校验"><a href="#构建过程中的校验" class="headerlink" title="构建过程中的校验"></a>构建过程中的校验</h2><p><code>make check</code> 构建三个 Watcom 产物，链接通过即证明 DLL 的导出面与引擎一致，不需要另写对照检查。</p><p>桌面端自带一套自检：<code>kunkun98-gui --selftest</code> 跑 421 项检查，0 失败，1 跳过。</p><p><code>-D__MMX__=1</code> 写进构建。手写汇编不会静默消失，少了它链接就过不去。</p><p>源码列表只在一处定义。引擎、服务端、界面与公共部分这四份清单写在 Makefile 顶部，给引擎加一个源文件只需改一个地方。</p><h2 id="DOS-扩展器：一个字节的补丁"><a href="#DOS-扩展器：一个字节的补丁" class="headerlink" title="DOS 扩展器：一个字节的补丁"></a>DOS 扩展器：一个字节的补丁</h2><p>DOS 那个产物要用 DOS/32A 扩展器，启动时会打印版权横幅。</p><p>许可条件 3 允许把声明放在软件自身。补丁因此只清掉 <code>_ID32</code> 配置字节的第 3 位（”show copyright”），启动时不打印横幅，声明移到 <code>--help</code> 输出里。</p><p>出厂产物的底本是官方 9.12 版，与 Open Watcom 2.x 在 <code>binw/</code> 里带的那份逐字节相同。改动只有一处配置位：偏移 <code>0x75</code> 由 <code>0x09</code> 改成 <code>0x01</code>。</p><p>不做源码重建，是因为 TASM 5.0 的 OMF（目标模块格式）目标记录过不了 Open Watcom 的 <code>wlink</code>，入口段会丢。出厂产物因此是打补丁的官方二进制，源码补丁与它并列留在仓库里，供人核对。</p><h2 id="速度、答得对、可复现是三件事"><a href="#速度、答得对、可复现是三件事" class="headerlink" title="速度、答得对、可复现是三件事"></a>速度、答得对、可复现是三件事</h2><p>134M 的模型在奔腾 III 上大约 2.5 token/秒。这个速度不快，但足以证明引擎能在目标机器上运行，不只是在别处能跑。</p><p>答得对不对是另一件事。模型的 README 自己就写着：中文流畅，内容经常是错的。这不是谦虚。仓库里的样例未经编辑，问「1+1等于几」，它答「1 + 2 = 3」；问「你是谁」，它答「我。你是谁？」；问「中国的首都是哪里」，它能写出一段通顺的、带具体年份的、编造的首都史。</p><p>规模不决定这一类表现。同一个问题问它和别人的模型，这一点看得很清楚。</p><p>问「原神的尼可，解锁2命有啥」，Kunkun98 把「尼可」当成古埃及的符号，写了一整篇关于「尼」字的考据。它夹进去的年份是「阿基米德（约公元前 216—-379—580）」。</p><p>同一个问题问小红书的 dots3-note preview，那是 280B 的模型。它把「尼可」当成了另一个角色「妮露」，又在思考段里写下「『解锁2命』中的『命』字被误写为『命』」这样绕回自己的判断。正文最后退化成把问题重复了两遍。</p><p>两边都没答对。134M 那份的错法读起来像模像样，280B 那份的错法不像 280B。一次提问说明不了太多，但「答得对不对」和「多少参数」之间，没有一条能直接画的线。</p><p>逐字节一致管的是可复现，不是答得对。老机器上给出的答案与其他任何机器完全相同，结果可以复现，也可以对照。至于那个答案对不对，是另外一回事。</p><p>引擎在 <a href="https://github.com/lunzima/llama98-zh">https://github.com/lunzima/llama98-zh</a>，模型权重在 <a href="https://github.com/lunzima/kunkun98-models">https://github.com/lunzima/kunkun98-models</a>。</p>]]>
    </content>
    <id>https://lunzima.net/llama98-byte-exact.html</id>
    <link href="https://lunzima.net/llama98-byte-exact.html"/>
    <published>2026-09-25T04:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<p>引擎叫 llama98-zh，可执行文件是 <code>llama98</code>，目标机器是奔腾 II / III 级的 Windows 9x 和 DOS。构建由同一个 Makefile 驱动 gcc 与 Open Watcom，产物有四类：</p>
<ul>
<li>Win32 的 CLI</li>
<li>Win32 的 GUI，产物名是 <code>kunkun98-gui</code></li>
<li>一个 DLL（动态链接库）</li>
<li>一个 32 位 DOS 扩展器版本</li>
</ul>
<p>引擎和桌面端在 llama98-zh 仓库里。它跑的模型在另一个仓库，叫 Kunkun98。</p>
<p>模型是 134,529,456 个参数，量化后 103.5 MiB。架构叫 KunMoE：8 层里 6 层是 KDA 线性注意力、2 层是完整注意力，16 个专家走 top-2 路由，另加 1 个共享专家。权重部分取自 Qwen/Qwen3.5-0.8B，许可跟着上游走 Apache-2.0。把 <code>MODEL/</code> 目录交给引擎就能对话：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">llama98 MODEL --chat --prompt &quot;你好&quot;</span><br></pre></td></tr></table></figure>

<p>同一份源码要在两套工具链上编译，在参考实现、SSE2（流式 SIMD 扩展 2）与 AVX2（高级矢量扩展 2）三层内核上运行。如果三层内核给出的结果不同，输出出错时就无法判断问题出在模型还是内核。引擎的目标因此定成逐字节一致。</p>
<h2 id="三层内核互为对照"><a href="#三层内核互为对照" class="headerlink" title="三层内核互为对照"></a>三层内核互为对照</h2><p>AVX2 与 SSE2 在 kmr20/zh 上跑 603 个 token，逐字节相同。</p>
<p>浮点语义也统一了。默认改成向零舍入加冲刷到零，ARM 的软浮点与 x86 因此逐字节一致；就近舍入只作为诊断选项保留。</p>
<p>0.1.6 修掉了一处浮点舍入不一致。Watcom 那条路径只截断了 387 的控制字，没有写 MXCSR。SSE2 内核按 MXCSR 舍入，C 路径在提供的快速浮点模式下截断，只有 SSE2 那一层用就近舍入。结果是 78,905,344 字节里有 59,349,441 与 gcc 构建不一致。两份构建的输出从第一个 token 起就不同。</p>
<h2 id="构建过程中的校验"><a href="#构建过程中的校验" class="headerlink" title="构建过程中的校验"></a>构建过程中的校验</h2><p><code>make check</code> 构建三个 Watcom 产物，链接通过即证明 DLL 的导出面与引擎一致，不需要另写对照检查。</p>
<p>桌面端自带一套自检：<code>kunkun98-gui --selftest</code> 跑 421 项检查，0 失败，1 跳过。</p>
<p><code>-D__MMX__=1</code> 写进构建。手写汇编不会静默消失，少了它链接就过不去。</p>
<p>源码列表只在一处定义。引擎、服务端、界面与公共部分这四份清单写在 Makefile 顶部，给引擎加一个源文件只需改一个地方。</p>
<h2 id="DOS-扩展器：一个字节的补丁"><a href="#DOS-扩展器：一个字节的补丁" class="headerlink" title="DOS 扩展器：一个字节的补丁"></a>DOS 扩展器：一个字节的补丁</h2><p>DOS 那个产物要用 DOS/32A 扩展器，启动时会打印版权横幅。</p>
<p>许可条件 3 允许把声明放在软件自身。补丁因此只清掉 <code>_ID32</code> 配置字节的第 3 位（”show copyright”），启动时不打印横幅，声明移到 <code>--help</code> 输出里。</p>
<p>出厂产物的底本是官方 9.12 版，与 Open Watcom 2.x 在 <code>binw/</code> 里带的那份逐字节相同。改动只有一处配置位：偏移 <code>0x75</code> 由 <code>0x09</code> 改成 <code>0x01</code>。</p>
<p>不做源码重建，是因为 TASM 5.0 的 OMF（目标模块格式）目标记录过不了 Open Watcom 的 <code>wlink</code>，入口段会丢。出厂产物因此是打补丁的官方二进制，源码补丁与它并列留在仓库里，供人核对。</p>
<h2 id="速度、答得对、可复现是三件事"><a href="#速度、答得对、可复现是三件事" class="headerlink" title="速度、答得对、可复现是三件事"></a>速度、答得对、可复现是三件事</h2><p>134M 的模型在奔腾 III 上大约 2.5 token/秒。这个速度不快，但足以证明引擎能在目标机器上运行，不只是在别处能跑。</p>
<p>答得对不对是另一件事。模型的 README 自己就写着：中文流畅，内容经常是错的。这不是谦虚。仓库里的样例未经编辑，问「1+1等于几」，它答「1 + 2 = 3」；问「你是谁」，它答「我。你是谁？」；问「中国的首都是哪里」，它能写出一段通顺的、带具体年份的、编造的首都史。</p>
<p>规模不决定这一类表现。同一个问题问它和别人的模型，这一点看得很清楚。</p>
<p>问「原神的尼可，解锁2命有啥」，Kunkun98 把「尼可」当成古埃及的符号，写了一整篇关于「尼」字的考据。它夹进去的年份是「阿基米德（约公元前 216—-379—580）」。</p>
<p>同一个问题问小红书的 dots3-note preview，那是 280B 的模型。它把「尼可」当成了另一个角色「妮露」，又在思考段里写下「『解锁2命』中的『命』字被误写为『命』」这样绕回自己的判断。正文最后退化成把问题重复了两遍。</p>
<p>两边都没答对。134M 那份的错法读起来像模像样，280B 那份的错法不像 280B。一次提问说明不了太多，但「答得对不对」和「多少参数」之间，没有一条能直接画的线。</p>
<p>逐字节一致管的是可复现，不是答得对。老机器上给出的答案与其他任何机器完全相同，结果可以复现，也可以对照。至于那个答案对不对，是另外一回事。</p>
<p>引擎在 <a href="https://github.com/lunzima/llama98-zh">https://github.com/lunzima/llama98-zh</a>，模型权重在 <a href="https://github.com/lunzima/kunkun98-models">https://github.com/lunzima/kunkun98-models</a>。</p>]]>
    </summary>
    <title>让 gcc 与 Open Watcom 给出同一个答案</title>
    <updated>2026-09-25T15:33:45.960Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="开发" scheme="https://lunzima.net/tags/%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<p>星霞宋体分成两份发布：轮廓一份，点阵一份，另附一个把两者拼起来的小工具。</p><p>分开交付由许可决定。轮廓那半来自 SIL 开放字体许可 1.1 的字体，点阵那半来自 GNU 通用公共许可证第 2 版、附字体嵌入例外的 BDF 源码。两套许可不能合成一个文件再分发，只能在本地拼。</p><p>拼合因此是产品的一部分，工具需要可靠，也需要在输入不合格时拒绝写出文件。</p><h2 id="移植的是表结构"><a href="#移植的是表结构" class="headerlink" title="移植的是表结构"></a>移植的是表结构</h2><p>工具叫 <code>sbitgraft</code>，一个 C 文件，无外部依赖，随两个字体仓库的发行版一起发布（<code>sbitgraft-&lt;版本&gt;.tar.xz</code>）。它把点阵那侧的 <code>EBDT</code>、<code>EBLC</code>、<code>EBSC</code> 三张表搬到轮廓那侧，随后重算各表校验和与 <code>head.checkSumAdjustment</code>。</p><p>接着按 tag 排序重建表目录，改写 <code>name</code> 表的变长字符串池，写出 1.0 版的字体集合头。</p><p>这种移植按表结构做，不依赖写死的字节偏移，对任意一对满足前提的字体都成立。</p><h2 id="前提不合格就不写文件"><a href="#前提不合格就不写文件" class="headerlink" title="前提不合格就不写文件"></a>前提不合格就不写文件</h2><p>内嵌点阵按 glyph ID 索引，两个输入必须对同一个码点指向同一个 glyph ID。</p><p>工具会先逐码点比对两侧的字符映射表。不一致时打印出前几个冲突的码点并退出，不写任何文件。拼接的产物会被装进系统，损坏的字体往往要到某个具体页面才现形，因此校验必须在写出文件之前完成。</p><h2 id="hinting-相关的三处处理"><a href="#hinting-相关的三处处理" class="headerlink" title="hinting 相关的三处处理"></a>hinting 相关的三处处理</h2><p>移除 hinting 时，<code>fpgm</code>、<code>prep</code>、<code>cvt </code> 三张表与每个字形的指令块都要删。<code>maxp</code> 里为解释器保留的字段也必须一并清零，<code>maxZones</code> 置 1，其余置 0。光栅器按那些数字分配栈与存储空间；若它们仍描述一个已不存在的解释器，校验工具会报错。</p><p><code>gasp</code> 不在删除之列。它描述渲染方式，与网格对齐无关，没有 hinting 的字体同样需要它。</p><p>复合字形也要处理。它的指令块位于最后一个组件之后，必须逐个跨过组件才能定位到。</p><h2 id="打包成字体集合"><a href="#打包成字体集合" class="headerlink" title="打包成字体集合"></a>打包成字体集合</h2><p>同一个家族的几个 face 只在 <code>name</code>、<code>post</code>、<code>OS/2</code> 这几张小表上有差别。逐字节相同的表只存一份，字体集合的体积因此接近单个 face，而不是各 face 体积之和。</p><p><code>head</code> 不参与共享，每个 face 需要自己的 <code>checkSumAdjustment</code>。</p><h2 id="工具的能力边界"><a href="#工具的能力边界" class="headerlink" title="工具的能力边界"></a>工具的能力边界</h2><p>会做：</p><ul><li>解析 SFNT 表目录</li><li>按 tag 读写表</li><li>重算校验和</li><li>重建表目录</li><li>改写 <code>name</code> 表</li><li>写字体集合头</li></ul><p>不做：</p><ul><li>重映射字符映射表</li><li>子集化</li><li>增删字形</li></ul><p><code>glyf</code> 只在移除 hinting 时解析，只读到定位指令块所需的深度，不解析坐标。</p><p>分开交付是许可要求的；工具在输入不一致时不写文件。</p><p>工具与它拼接的两份字体在 <a href="https://github.com/lunzima/GlowSong">https://github.com/lunzima/GlowSong</a> 与 <a href="https://github.com/lunzima/GlowHei">https://github.com/lunzima/GlowHei</a>。</p>]]>
    </content>
    <id>https://lunzima.net/sbitgraft-license.html</id>
    <link href="https://lunzima.net/sbitgraft-license.html"/>
    <published>2026-09-25T03:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<p>星霞宋体分成两份发布：轮廓一份，点阵一份，另附一个把两者拼起来的小工具。</p>
<p>分开交付由许可决定。轮廓那半来自 SIL 开放字体许可 1.1 的字体，点阵那半来自 GNU 通用公共许可证第 2 版、附字体嵌入例外的 BDF 源码。两套许可不能合成一个文件再分发，只能在本地拼。</p>
<p>拼合因此是产品的一部分，工具需要可靠，也需要在输入不合格时拒绝写出文件。</p>
<h2 id="移植的是表结构"><a href="#移植的是表结构" class="headerlink" title="移植的是表结构"></a>移植的是表结构</h2><p>工具叫 <code>sbitgraft</code>，一个 C 文件，无外部依赖，随两个字体仓库的发行版一起发布（<code>sbitgraft-&lt;版本&gt;.tar.xz</code>）。它把点阵那侧的 <code>EBDT</code>、<code>EBLC</code>、<code>EBSC</code> 三张表搬到轮廓那侧，随后重算各表校验和与 <code>head.checkSumAdjustment</code>。</p>
<p>接着按 tag 排序重建表目录，改写 <code>name</code> 表的变长字符串池，写出 1.0 版的字体集合头。</p>
<p>这种移植按表结构做，不依赖写死的字节偏移，对任意一对满足前提的字体都成立。</p>
<h2 id="前提不合格就不写文件"><a href="#前提不合格就不写文件" class="headerlink" title="前提不合格就不写文件"></a>前提不合格就不写文件</h2><p>内嵌点阵按 glyph ID 索引，两个输入必须对同一个码点指向同一个 glyph ID。</p>
<p>工具会先逐码点比对两侧的字符映射表。不一致时打印出前几个冲突的码点并退出，不写任何文件。拼接的产物会被装进系统，损坏的字体往往要到某个具体页面才现形，因此校验必须在写出文件之前完成。</p>
<h2 id="hinting-相关的三处处理"><a href="#hinting-相关的三处处理" class="headerlink" title="hinting 相关的三处处理"></a>hinting 相关的三处处理</h2><p>移除 hinting 时，<code>fpgm</code>、<code>prep</code>、<code>cvt </code> 三张表与每个字形的指令块都要删。<code>maxp</code> 里为解释器保留的字段也必须一并清零，<code>maxZones</code> 置 1，其余置 0。光栅器按那些数字分配栈与存储空间；若它们仍描述一个已不存在的解释器，校验工具会报错。</p>
<p><code>gasp</code> 不在删除之列。它描述渲染方式，与网格对齐无关，没有 hinting 的字体同样需要它。</p>
<p>复合字形也要处理。它的指令块位于最后一个组件之后，必须逐个跨过组件才能定位到。</p>
<h2 id="打包成字体集合"><a href="#打包成字体集合" class="headerlink" title="打包成字体集合"></a>打包成字体集合</h2><p>同一个家族的几个 face 只在 <code>name</code>、<code>post</code>、<code>OS/2</code> 这几张小表上有差别。逐字节相同的表只存一份，字体集合的体积因此接近单个 face，而不是各 face 体积之和。</p>
<p><code>head</code> 不参与共享，每个 face 需要自己的 <code>checkSumAdjustment</code>。</p>
<h2 id="工具的能力边界"><a href="#工具的能力边界" class="headerlink" title="工具的能力边界"></a>工具的能力边界</h2><p>会做：</p>
<ul>
<li>解析 SFNT 表目录</li>
<li>按 tag 读写表</li>
<li>重算校验和</li>
<li>重建表目录</li>
<li>改写 <code>name</code> 表</li>
<li>写字体集合头</li>
</ul>
<p>不做：</p>
<ul>
<li>重映射字符映射表</li>
<li>子集化</li>
<li>增删字形</li>
</ul>
<p><code>glyf</code> 只在移除 hinting 时解析，只读到定位指令块所需的深度，不解析坐标。</p>
<p>分开交付是许可要求的；工具在输入不一致时不写文件。</p>
<p>工具与它拼接的两份字体在 <a href="https://github.com/lunzima/GlowSong">https://github.com/lunzima/GlowSong</a> 与 <a href="https://github.com/lunzima/GlowHei">https://github.com/lunzima/GlowHei</a>。</p>]]>
    </summary>
    <title>sbitgraft：把点阵表搬进轮廓字体</title>
    <updated>2026-09-25T15:27:32.146Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="开发" scheme="https://lunzima.net/tags/%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<p>中文屏幕字体在 12 到 16 像素这一段要锐利，传统做法是做内嵌点阵。轮廓与点阵并存之后，两边的度量必须一致，否则同一个字在点阵档与轮廓档之间切换时会出现跳动。</p><p>做这份字体，先定度量，再画字形。</p><h2 id="unitsPerEm-取-256"><a href="#unitsPerEm-取-256" class="headerlink" title="unitsPerEm 取 256"></a>unitsPerEm 取 256</h2><p>这个值与 16×16 的点阵档对齐，所有坐标因此都是小整数，点阵与轮廓共用同一套格线。</p><p>宽度只有两种：ASCII 一律半角 128 单位（0.5 em），全角一律 256 单位。拉丁字形原本是比例宽度，这里重塑为半角，并施加逐边的笔画粗细补偿。不做补偿的话，横向压缩会让竖笔变细，整段拉丁比汉字轻一档。</p><p>单倍行距 1.141 em，与星霞黑体相同，两款可以混排，也可以互换。十个阿拉伯数字在同一字号下底边与高度一致。11 到 28px 逐字号量过，与 Arial、Times New Roman、Consolas、Tahoma 一致。</p><p>系统里的中文也逐字号量过：度量与 Windows 预装的中易宋体、中易黑体一致。所以这两款可以顶替系统那两款，混排时行高不会因为一行里有没有星霞的字而改变。</p><p>制表符与方块元素的笔画由 39/千单位加粗到 70 与 66，端点与相邻字形对齐。拼起来后笔画连成一条不断的线，htop、mc、ncurses 对话框这类全框界面不再断口，也不再粗细跳变。</p><h2 id="点阵只有五档，但请求区间更宽"><a href="#点阵只有五档，但请求区间更宽" class="headerlink" title="点阵只有五档，但请求区间更宽"></a>点阵只有五档，但请求区间更宽</h2><p>内嵌点阵只有 12、13、14、15、16 五个档位。FreeType 按半像素取整，因此真正落到点阵的请求范围是 11.5 至 16.5。区间只写一侧的话，区间外的字号会被套上为点阵准备的那套反锯齿设置。</p><p>区间之外走轮廓。轮廓含 Chlorophytum 生成的 TrueType 指令，收益集中在 12px 及以下的密集汉字：横画不粘连，笔画数可辨。如果只用在大字号，或者在意体积，可以用拼合工具里的 <code>--strip</code> 去掉指令，轮廓、度量、字符映射与命名都不受影响。</p><h2 id="轮廓简化的逐字验收"><a href="#轮廓简化的逐字验收" class="headerlink" title="轮廓简化的逐字验收"></a>轮廓简化的逐字验收</h2><p>轮廓经过大幅几何简化以压缩体积：曲线降精度，共线点消除，浅弧压平。</p><p>简化后的形状要逐字比对。每个字形在简化前后各渲染一次，逐像素比对；形状偏差超过 1% 的拟合退回更保守的一档，三档都不合格就保留未简化的原轮廓。这一步依赖 FontForge 与两个 Python 库，缺了就整步跳过，不放宽标准。</p><p>来源是思源宋体 SC Regular 与 Liberation Serif，两者都是 SIL 开放字体许可 1.1。字体、源码与发行版在 <a href="https://github.com/lunzima/GlowSong">https://github.com/lunzima/GlowSong</a>，配套的黑体在 <a href="https://github.com/lunzima/GlowHei">https://github.com/lunzima/GlowHei</a>。</p><h2 id="等宽版是派生的独立文件"><a href="#等宽版是派生的独立文件" class="headerlink" title="等宽版是派生的独立文件"></a>等宽版是派生的独立文件</h2><p>比例版与等宽版的字形与度量完全相同，差别只有三处：族名、<code>post.isFixedPitch</code>、PANOSE。</p><p>等宽版的做法是从比例版派生出独立文件，而不是让应用把拼合后的族名认作等宽变体。字体选择框读的是文件本身列出的 family，fontconfig 事后加的别名不在其列。<code>fc-list</code> 看得到那些别名，但选择框里没有。</p><h2 id="先定规格，再造字形"><a href="#先定规格，再造字形" class="headerlink" title="先定规格，再造字形"></a>先定规格，再造字形</h2><p><code>unitsPerEm</code>、两种宽度、行距、数字的底边与高度、接缝的笔画宽度，都先有规格，再让字形去满足。</p><p>代价是前期工作量大：点阵每一档要重画，轮廓简化每一字要渲染比对，拉丁重塑半角要逐边补笔画。好处是拼合关系在造字阶段就对齐。字形要与另一款字体、相邻字形、拉丁数字拼在一起，规格不对就无法事后修正。</p>]]>
    </content>
    <id>https://lunzima.net/glowsong-metrics.html</id>
    <link href="https://lunzima.net/glowsong-metrics.html"/>
    <published>2026-09-25T02:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<p>中文屏幕字体在 12 到 16 像素这一段要锐利，传统做法是做内嵌点阵。轮廓与点阵并存之后，两边的度量必须一致，否则同一个字在点阵档与轮廓档之间切换时会出现跳动。</p>
<p>做这份字体，先定度量，再画字形。</p>
<h2 id="unitsPerEm-取-256"><a href="#unitsPerEm-取-256" class="headerlink" title="unitsPerEm 取 256"></a>unitsPerEm 取 256</h2><p>这个值与 16×16 的点阵档对齐，所有坐标因此都是小整数，点阵与轮廓共用同一套格线。</p>
<p>宽度只有两种：ASCII 一律半角 128 单位（0.5 em），全角一律 256 单位。拉丁字形原本是比例宽度，这里重塑为半角，并施加逐边的笔画粗细补偿。不做补偿的话，横向压缩会让竖笔变细，整段拉丁比汉字轻一档。</p>
<p>单倍行距 1.141 em，与星霞黑体相同，两款可以混排，也可以互换。十个阿拉伯数字在同一字号下底边与高度一致。11 到 28px 逐字号量过，与 Arial、Times New Roman、Consolas、Tahoma 一致。</p>
<p>系统里的中文也逐字号量过：度量与 Windows 预装的中易宋体、中易黑体一致。所以这两款可以顶替系统那两款，混排时行高不会因为一行里有没有星霞的字而改变。</p>
<p>制表符与方块元素的笔画由 39/千单位加粗到 70 与 66，端点与相邻字形对齐。拼起来后笔画连成一条不断的线，htop、mc、ncurses 对话框这类全框界面不再断口，也不再粗细跳变。</p>
<h2 id="点阵只有五档，但请求区间更宽"><a href="#点阵只有五档，但请求区间更宽" class="headerlink" title="点阵只有五档，但请求区间更宽"></a>点阵只有五档，但请求区间更宽</h2><p>内嵌点阵只有 12、13、14、15、16 五个档位。FreeType 按半像素取整，因此真正落到点阵的请求范围是 11.5 至 16.5。区间只写一侧的话，区间外的字号会被套上为点阵准备的那套反锯齿设置。</p>
<p>区间之外走轮廓。轮廓含 Chlorophytum 生成的 TrueType 指令，收益集中在 12px 及以下的密集汉字：横画不粘连，笔画数可辨。如果只用在大字号，或者在意体积，可以用拼合工具里的 <code>--strip</code> 去掉指令，轮廓、度量、字符映射与命名都不受影响。</p>
<h2 id="轮廓简化的逐字验收"><a href="#轮廓简化的逐字验收" class="headerlink" title="轮廓简化的逐字验收"></a>轮廓简化的逐字验收</h2><p>轮廓经过大幅几何简化以压缩体积：曲线降精度，共线点消除，浅弧压平。</p>
<p>简化后的形状要逐字比对。每个字形在简化前后各渲染一次，逐像素比对；形状偏差超过 1% 的拟合退回更保守的一档，三档都不合格就保留未简化的原轮廓。这一步依赖 FontForge 与两个 Python 库，缺了就整步跳过，不放宽标准。</p>
<p>来源是思源宋体 SC Regular 与 Liberation Serif，两者都是 SIL 开放字体许可 1.1。字体、源码与发行版在 <a href="https://github.com/lunzima/GlowSong">https://github.com/lunzima/GlowSong</a>，配套的黑体在 <a href="https://github.com/lunzima/GlowHei">https://github.com/lunzima/GlowHei</a>。</p>
<h2 id="等宽版是派生的独立文件"><a href="#等宽版是派生的独立文件" class="headerlink" title="等宽版是派生的独立文件"></a>等宽版是派生的独立文件</h2><p>比例版与等宽版的字形与度量完全相同，差别只有三处：族名、<code>post.isFixedPitch</code>、PANOSE。</p>
<p>等宽版的做法是从比例版派生出独立文件，而不是让应用把拼合后的族名认作等宽变体。字体选择框读的是文件本身列出的 family，fontconfig 事后加的别名不在其列。<code>fc-list</code> 看得到那些别名，但选择框里没有。</p>
<h2 id="先定规格，再造字形"><a href="#先定规格，再造字形" class="headerlink" title="先定规格，再造字形"></a>先定规格，再造字形</h2><p><code>unitsPerEm</code>、两种宽度、行距、数字的底边与高度、接缝的笔画宽度，都先有规格，再让字形去满足。</p>
<p>代价是前期工作量大：点阵每一档要重画，轮廓简化每一字要渲染比对，拉丁重塑半角要逐边补笔画。好处是拼合关系在造字阶段就对齐。字形要与另一款字体、相邻字形、拉丁数字拼在一起，规格不对就无法事后修正。</p>]]>
    </summary>
    <title>星霞宋体：把度量定在点阵上</title>
    <updated>2026-09-25T15:36:00.101Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="原神" scheme="https://lunzima.net/tags/%E5%8E%9F%E7%A5%9E/"/>
    <content>
      <![CDATA[<img src="/img/mavuika-birthday-2026.webp" alt="《原神》2026 年生贺图：玛薇卡戴着墨镜，一手握着立式话筒，另一手握拳，身周是火焰与音符；左上角有一个绿色的小角色在唱" width="750" height="750"><p>（贺图来自《原神》官方。）</p><p>八月二十八。今天是《原神》里玛薇卡的生日。</p><p>这篇文章我起了几次笔，每次都停在半途。并非无话可说，只是无论怎么说，都嫌轻。</p><p>她的称号是「焚夜以炎」，官方的介绍里还称她为「纳塔的骄阳」，末尾写着「我们的太阳永燃不息」。这两句把我想说的都说完了。纳塔有火神的神座，可她让人记住的不是坐在那上面 —— 是太阳每天照常升起，把光平等地铺在每个人身上。</p><p>她的武器是双手剑。这一点我总记得。她用火焰，也骑着一辆「驰轮车」，站在最前面。</p><p>五百年前，她公开了拯救纳塔的计划，而代价是她自己：她把生命存进圣火，在火里独自走了五百年，换来五百年后的第二次生命。她没有说自己是救世主。她从不与谁结算，也从没打算被谁偿还 —— 她只是让火继续烧下去。</p><p>后来我才明白，太阳最不讲道理的地方，是它让人以为光是不用代价的。纳塔的人晒了很多年，习惯了抬头就有，就觉得那是天经地义。等到有一天有人想问那光是从哪里烧出来的，问题已经问晚了。</p><p>我也不知道该怎么给一个太阳过生日。蜡烛对它没有意义，蛋糕也没有。所以最后我只写下这一句：</p><p>Mavuika, kiongozi kikuu! Hatutambui mtu mwingine yeyote isipokuwa wewe.</p><p>玛薇卡，伟大的领袖！除了您，我们谁都不认。</p><p>生日快乐，玛薇卡。</p><p>今天纳塔的太阳也很好。</p>]]>
    </content>
    <id>https://lunzima.net/mavuika-birthday.html</id>
    <link href="https://lunzima.net/mavuika-birthday.html"/>
    <published>2026-08-28T12:00:00.000Z</published>
    <summary type="html">
      <![CDATA[<img src="/img/mavuika-birthday-2026.webp" alt="《原神》2026 年生贺图：玛薇卡戴着墨镜，一手握着立式话筒，另一手握拳，身周是火焰与音符；左上角有一个绿色的小角色在唱" width="750" height="750">

<p>（贺图来自《原神》官方。）</p>
<p>八月二十八。今天是《原神》里玛薇卡的生日。</p>
<p>这篇文章我起了几次笔，每次都停在半途。并非无话可说，只是无论怎么说，都嫌轻。</p>
<p>她的称号是「焚夜以炎」，官方的介绍里还称她为「纳塔的骄阳」，末尾写着「我们的太阳永燃不息」。这两句把我想说的都说完了。纳塔有火神的神座，可她让人记住的不是坐在那上面 —— 是太阳每天照常升起，把光平等地铺在每个人身上。</p>
<p>她的武器是双手剑。这一点我总记得。她用火焰，也骑着一辆「驰轮车」，站在最前面。</p>
<p>五百年前，她公开了拯救纳塔的计划，而代价是她自己：她把生命存进圣火，在火里独自走了五百年，换来五百年后的第二次生命。她没有说自己是救世主。她从不与谁结算，也从没打算被谁偿还 —— 她只是让火继续烧下去。</p>
<p>后来我才明白，太阳最不讲道理的地方，是它让人以为光是不用代价的。纳塔的人晒了很多年，习惯了抬头就有，就觉得那是天经地义。等到有一天有人想问那光是从哪里烧出来的，问题已经问晚了。</p>
<p>我也不知道该怎么给一个太阳过生日。蜡烛对它没有意义，蛋糕也没有。所以最后我只写下这一句：</p>
<p>Mavuika, kiongozi kikuu! Hatutambui mtu mwingine yeyote isipokuwa wewe.</p>
<p>玛薇卡，伟大的领袖！除了您，我们谁都不认。</p>
<p>生日快乐，玛薇卡。</p>
<p>今天纳塔的太阳也很好。</p>]]>
    </summary>
    <title>生日快乐，纳塔的太阳</title>
    <updated>2026-09-25T15:40:04.785Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="杂项" scheme="https://lunzima.net/tags/%E6%9D%82%E9%A1%B9/"/>
    <content>
      <![CDATA[<p>密钥查询地址：<br><a href="https://docs.microsoft.com/en-us/windows-server/get-started/kmsclientkeys">KMS Client Setup Keys</a></p><p>设置方法：以管理员身份运行 <code>cmd</code>，输入以下命令，并按自己的环境修改。<br>【1】Office激活<br>cd C:\Program Files\Microsoft Office\Office19<br>cscript ospp.vbs /sethst:kr.lunzi.ma<br>cscript ospp.vbs /act<br>cscript ospp.vbs /dstatus</p><p>【2】操作系统激活<br>slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX<br>slmgr /skms kr.lunzi.ma<br>slmgr /ato</p>]]>
    </content>
    <id>https://lunzima.net/kms-client-keys.html</id>
    <link href="https://lunzima.net/kms-client-keys.html"/>
    <published>2024-07-10T10:09:28.000Z</published>
    <summary type="html">
      <![CDATA[<p>密钥查询地址：<br><a href="https://docs.microsoft.com/en-us/windows-server/get-started/kmsclientkeys">KMS Client Setup Keys</a></p>
<p>设置方法：以管理员身份运行 <code>cmd</code>，输入以下命令，并按自己的环境修改。<br>【1】Office激活<br>cd C:\Program Files\Microsoft Office\Office19<br>cscript ospp.vbs /sethst:kr.lunzi.ma<br>cscript ospp.vbs /act<br>cscript ospp.vbs /dstatus</p>
<p>【2】操作系统激活<br>slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX<br>slmgr /skms kr.lunzi.ma<br>slmgr /ato</p>]]>
    </summary>
    <title>KMS客户端密钥设置</title>
    <updated>2026-09-25T12:58:31.121Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <category term="资源" scheme="https://lunzima.net/tags/%E8%B5%84%E6%BA%90/"/>
    <content>
      <![CDATA[<p>本表情包由爱好者制作，不作为商业用途使用。</p><p>更新不定期。<br><a href="https://github.com/lunzima/nqlsg_sticker">https://github.com/lunzima/nqlsg_sticker</a></p>]]>
    </content>
    <id>https://lunzima.net/nqlsg-sticker.html</id>
    <link href="https://lunzima.net/nqlsg-sticker.html"/>
    <published>2023-11-15T03:50:15.000Z</published>
    <summary type="html">
      <![CDATA[<p>本表情包由爱好者制作，不作为商业用途使用。</p>
<p>更新不定期。<br><a href="https://github.com/lunzima/nqlsg_sticker">https://github.com/lunzima/nqlsg_sticker</a></p>]]>
    </summary>
    <title>农企临时工表情包</title>
    <updated>2026-09-25T12:58:31.136Z</updated>
  </entry>
  <entry>
    <author>
      <name>nqlsg_tanoshi</name>
    </author>
    <content>
      <![CDATA[<p>本网站的博客功能自即日起重新开通，博主不定期发布内容。<br>本域名此前提供的其他服务继续可用。</p><p>本网站不是农企临时工的网站。页面右下角的农企临时工看板娘形象不作为商业用途使用。</p>]]>
    </content>
    <id>https://lunzima.net/from-lunzima-to-nqlsg-tanoshi.html</id>
    <link href="https://lunzima.net/from-lunzima-to-nqlsg-tanoshi.html"/>
    <published>2021-12-28T06:05:31.000Z</published>
    <summary type="html">
      <![CDATA[<p>本网站的博客功能自即日起重新开通，博主不定期发布内容。<br>本域名此前提供的其他服务继续可用。</p>
<p>本网站不是农企临时工的网站。页面右下角的农企临时工看板娘形象不作为商业用途使用。</p>]]>
    </summary>
    <title>从轮子妈到农企临时工单推人</title>
    <updated>2026-09-25T12:58:31.138Z</updated>
  </entry>
</feed>
