aboutsummaryrefslogtreecommitdiff
path: root/docs/encoding.html
diff options
context:
space:
mode:
authorAlexander Kassouni <alex@kassouni.net>2026-09-05 17:51:13 -0700
committerAlexander Kassouni <alex@kassouni.net>2026-09-05 17:51:13 -0700
commit46227e2003de28202cc4e82bca4f7f4745476c4e (patch)
treefe450ac33ffa13747613c3cfdf36ea24b3036c46 /docs/encoding.html
init
Diffstat (limited to 'docs/encoding.html')
-rw-r--r--docs/encoding.html136
1 files changed, 136 insertions, 0 deletions
diff --git a/docs/encoding.html b/docs/encoding.html
new file mode 100644
index 0000000..8b57261
--- /dev/null
+++ b/docs/encoding.html
@@ -0,0 +1,136 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>2. Text → voice code — Animalese</title>
+<link rel="stylesheet" href="style.css">
+</head>
+<body>
+
+<nav class="crumb"><a href="index.html">Animalese pipeline</a> / 2. Text → voice code</nav>
+
+<h1>2. Text → voice code</h1>
+<p class="lede">A flat 256-entry table turns the game's own character encoding into a
+34-value "voice code" alphabet. This is the only place text is looked at.</p>
+
+<h2>The game's character set</h2>
+
+<p>Messages are stored one byte per character in a custom encoding
+(<code>include/m_font.h</code>). It is ASCII-compatible over exactly three ranges —
+<code>0-9</code> at 48–57, <code>A-Z</code> at 65–90, <code>a-z</code> at 97–122 — and
+diverges everywhere else. Notable non-ASCII placements:</p>
+
+<table>
+<tr><th>Code</th><th>Character</th><th>Note</th></tr>
+<tr><td>0–31</td><td>uppercase accented Latin, <code>¡ ¿ Ç Ð Ñ ß Þ</code></td><td>where ASCII control codes would be</td></tr>
+<tr><td>43</td><td><code>♥</code></td><td>where ASCII has <code>+</code></td></tr>
+<tr><td>47</td><td><code>♪</code></td><td>where ASCII has <code>/</code></td></tr>
+<tr><td>59</td><td>droplet symbol</td><td>where ASCII has <code>;</code></td></tr>
+<tr><td>92</td><td>annoyed symbol</td><td>where ASCII has <code>\</code></td></tr>
+<tr><td>127</td><td><code>CHAR_CONTROL_CODE</code></td><td>escape byte; a control code follows</td></tr>
+<tr><td>128</td><td><code>CHAR_MESSAGE_TAG</code></td><td>escape byte</td></tr>
+<tr><td>210, 211</td><td>short space, wide space</td><td>layout-only spaces</td></tr>
+</table>
+
+<h2>The voice table</h2>
+
+<p><code>mMsg_sound_voice_get(code)</code> (<code>src/game/m_msg_sound.c_inc:1</code>) is a
+256-byte lookup with two hard-coded exceptions:</p>
+
+<pre>if (code == CHAR_CONTROL_CODE || code == CHAR_MESSAGE_TAG)
+ return -1; // no sound, and no state change
+return voice_array[code];</pre>
+
+<p>Those are the only two entries in the array holding <code>0xFF</code>, so
+<code>0xFF</code> is never actually returned from this function — it arrives at
+<code>Na_VoiceSe</code> only as a padding value for the unused <code>b</code>/<code>c</code>
+slots.</p>
+
+<h3>Voice code space</h3>
+
+<table>
+<tr><th>Voice code</th><th>Meaning</th></tr>
+<tr><td><code>0x53</code>–<code>0x5C</code></td><td>digits <code>0</code>–<code>9</code></td></tr>
+<tr><td><code>0x5D</code>–<code>0x76</code></td><td>letters <code>a</code>–<code>z</code></td></tr>
+<tr><td><code>0x80</code></td><td><code>♪</code> — plays the speaker's melody, bypasses everything</td></tr>
+<tr><td><code>0x81</code></td><td>space</td></tr>
+<tr><td><code>0x82</code></td><td><code>?</code> — raises pitch</td></tr>
+<tr><td><code>0x83</code></td><td><code>!</code> — raises volume</td></tr>
+<tr><td><code>0x84</code></td><td>newline (injected, never in the table)</td></tr>
+<tr><td><code>0x85</code></td><td>mute — the catch-all for punctuation and symbols</td></tr>
+<tr><td><code>0x86</code></td><td>click — plays <code>NA_SE_BEBE</code></td></tr>
+<tr><td><code>0xFF</code></td><td>empty slot in a window</td></tr>
+</table>
+
+<p>The split at <code>0x77</code> matters: <code>Sou_BoinShiinCheck</code> and several rules in
+<code>Na_VoiceSe</code> test <code>&gt; 0x77</code> to mean "this is punctuation, not a letter".</p>
+
+<h3>Case and accent folding</h3>
+
+<p>Upper and lower case map to the same code, and every accented Latin letter folds onto
+its base letter. Some of the folds are worth knowing about:</p>
+
+<table>
+<tr><th>Character(s)</th><th>Folds to</th></tr>
+<tr><td><code>À Á Â Ã Å à á â ã ä å</code>, <code>ª</code> (feminine ordinal)</td><td><code>a</code></td></tr>
+<tr><td><code>ß</code> (eszett)</td><td><code>b</code> — a glyph-shape fold, not a phonetic one</td></tr>
+<tr><td><code>Ç ç</code></td><td><code>c</code></td></tr>
+<tr><td><code>Ð ð</code></td><td><code>d</code></td></tr>
+<tr><td><code>È É Ê Ë è é ê ë</code></td><td><code>e</code></td></tr>
+<tr><td><code>Ì Í Î Ï ì í î ï</code></td><td><code>i</code></td></tr>
+<tr><td><code>Ñ ñ</code></td><td><code>n</code></td></tr>
+<tr><td><code>Ò Ó Ô Õ Ö ò ó ô õ ö</code>, <code>º</code> (masculine ordinal)</td><td><code>o</code></td></tr>
+<tr><td><code>Þ þ</code> (thorn)</td><td><code>p</code></td></tr>
+<tr><td><code>Ù Ú Û Ü ù ú û ü</code></td><td><code>u</code></td></tr>
+<tr><td><code>Ý ý ÿ</code></td><td><code>y</code></td></tr>
+<tr><td><code>Œ œ</code></td><td><em>mute</em> — the only accented Latin pair that is not folded</td></tr>
+</table>
+
+<h3>Everything else</h3>
+
+<ul>
+ <li>Space (32), short space (210), wide space (211), and code 255 → <code>0x81</code>.</li>
+ <li>Interpunct (133) → <code>0x86</code>, the click. It is the only character in the whole
+ table that maps to the click code.</li>
+ <li>All other punctuation, symbols, and the entire block from 155 upward →
+ <code>0x85</code>, mute.</li>
+</ul>
+
+<h2>Two wrappers</h2>
+
+<h3>mMsg_sound_voice_get2</h3>
+
+<pre>if (code == CHAR_NEW_LINE) return 0x84;
+return mMsg_sound_voice_get(code);</pre>
+
+<p>Newline has no entry in the table; the wrapper injects <code>0x84</code>. This is the
+function the scheduler actually calls, so <code>0x84</code> reaches the engine while the
+raw table never produces it. <span class="src"><code>m_msg_sound.c_inc:44</code></span></p>
+
+<h3>mMsg_sound_voice_get_for_editor</h3>
+
+<pre>sound = mMsg_sound_voice_get(code);
+switch (sound) {
+ case 0x80: case 0x82: case 0x83: case 0x85: return 0x86;
+ default: return sound;
+}</pre>
+
+<p>In the text editor, ♪, <code>?</code>, <code>!</code> and every mute character all become
+the click. See <a href="editor.html">the editor path</a>.
+<span class="src"><code>m_msg_sound.c_inc:26</code></span></p>
+
+<div class="note">
+<strong>Only these 34 values leave the text layer.</strong> Word boundaries, capitalisation,
+sentence structure and the actual letters beyond <code>a-z0-9</code> are all gone by
+the time audio sees anything. Whatever "understanding of English" the animalese has,
+it lives entirely in the digraph table on the next page.
+</div>
+
+<div class="pager">
+ <span><a href="gating.html">← 1. Gating</a></span>
+ <span><a href="scheduling.html">3. Windowing &amp; timing →</a></span>
+</div>
+
+</body>
+</html>