aboutsummaryrefslogtreecommitdiff
path: root/docs/quirks.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/quirks.html
init
Diffstat (limited to 'docs/quirks.html')
-rw-r--r--docs/quirks.html157
1 files changed, 157 insertions, 0 deletions
diff --git a/docs/quirks.html b/docs/quirks.html
new file mode 100644
index 0000000..bb860c9
--- /dev/null
+++ b/docs/quirks.html
@@ -0,0 +1,157 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>8. Quirks, dead code &amp; corrections — Animalese</title>
+<link rel="stylesheet" href="style.css">
+</head>
+<body>
+
+<nav class="crumb"><a href="index.html">Animalese pipeline</a> / 8. Quirks &amp; corrections</nav>
+
+<h1>8. Quirks, dead code &amp; corrections</h1>
+<p class="lede">Things a faithful port has to reproduce, things a clean reimplementation
+should drop, and where earlier notes were wrong.</p>
+
+<h2>Bugs that shipped</h2>
+
+<h3>Glyph scale → volume is a dead store</h3>
+<pre>if (scale &gt; 0x20) {
+ scale = scale - 0x20;
+ f31 = scale;
+ f31 = f31 * 0.15f;
+ f30 = sou_voice_se[0]._08 + f31; // computed…
+ f30 = 4.f; // …and immediately discarded
+ sou_voice_se[0]._08 = f30;
+} else {
+ scale = 0x1f - scale;
+ f31 = scale * 0.02f;
+ f30 = sou_voice_se[0]._08 - f31; // computed…
+ f30 = 0.4f; // …and immediately discarded
+ sou_voice_se[0]._08 = f30;
+}</pre>
+<p class="src"><code>game64.c_inc:2994</code></p>
+<p>The intent was clearly a smooth scale-to-volume ramp. What ships is a step function:
+any glyph larger than normal slams volume to <strong>4.0</strong> (a 6× jump over the
+0.65 baseline), anything smaller to <strong>0.4</strong>. Normal-size text passes
+<code>scale == 32</code> and skips the block entirely, so this is only audible on
+deliberately scaled dialogue.</p>
+<p>This is in the matching decomp, so retail does it. A faithful port needs it; a clean
+library does not.</p>
+
+<h3>The mode-2 dedup check is tautological</h3>
+<pre>case 2: {
+ if (sou_num_org == sou_num_org &amp;&amp; sou_num_org == sou_last_num2) i = 1;
+}</pre>
+<p class="src"><code>game64.c_inc:2711</code></p>
+<p>The first comparison is always true. The intent was presumably a three-in-a-row check
+mirroring mode 0; the effect is a two-in-a-row check. Deferred syllables therefore
+trigger the random substitution more often than intended.</p>
+
+<h3>SHIIN2BOIN is dead, and would read out of bounds</h3>
+<pre>const u8 SHIIN2BOIN[] = { 0, 1, 2, 3, 4, 5, 1, 2, … }; // 86 entries
+…
+sou_now_boin = SHIIN2BOIN[a]; // a can be up to 0x77 = 119</pre>
+<p class="src"><code>game64.c_inc:236</code>, written at <code>:2863</code></p>
+<p><code>sou_now_boin</code> is written here and initialised in <code>Sou_InitAudio</code>, and
+<strong>never read anywhere in the tree</strong>. Same for <code>sou_nobasu_count</code>
+("elongation count"), which is only ever set to 0. Both are vestigial mouth-shape /
+vowel tracking, presumably live in the N64 <em>Dōbutsu no Mori</em> code this was ported
+from. Because the table is 86 entries and <code>a</code> can reach 119, the write reads
+past the array — harmless only because the result is discarded.</p>
+<p>The table also has an obvious data oddity: entry 45 is the value <code>45</code>, where
+every neighbour is 0–5.</p>
+
+<h2>Design choices that surprise</h2>
+
+<ul>
+ <li><strong><code>n</code> is a vowel.</strong> <code>Sou_BoinShiinCheck</code> groups it with
+ <code>a e i o u</code>. This changes when silent&nbsp;e and magic&nbsp;e fire, so
+ <em>done</em>, <em>gone</em> and <em>none</em> get the silent-<em>e</em> drop.</li>
+
+ <li><strong><code>h</code> is silent.</strong> <code>Sou_TanboinHenkan('h')</code> returns
+ <code>0x00</code>, and code 0 has no instrument (guarded by
+ <code>if (a != 0 &amp;&amp; a &lt;= 0x77)</code>). Standalone <code>h</code> makes no sound;
+ it only speaks through <code>ch sh th ha he hi ho oh</code>.</li>
+
+ <li><strong>A space can never open a window.</strong> <code>mMsg_sound_voice_entry</code>
+ refuses <code>voice_no == 0x81</code> in the "first character" branch, so word-initial
+ pairing always starts on a letter.</li>
+
+ <li><strong>Animalese is never panned or attenuated.</strong> <code>pan</code> stays at
+ <code>0x3F</code> and <code>reverbVolume</code> at 0 for the life of the program. A
+ villager at the far edge of the screen is exactly as loud and as centred as one in
+ your face — the only positional SE in the game that ignores position.</li>
+
+ <li><strong>The two subtracks share one parameter set.</strong> <code>sou_voice_se[0]</code>
+ and <code>[1]</code> are always written together, and <code>Sou_VoiceMake</code> pushes
+ both every frame — so a new syllable retroactively changes the volume and pitch
+ of the one still ringing.</li>
+
+ <li><strong>Voice mode is not the config setting.</strong> Animalese only ever plays for
+ messages that pass <code>mMsg_check_sound_animal</code>; and the submenu forces
+ <code>sAdo_SetVoiceMode(0)</code> regardless of config, so letter-writing always
+ speaks even for players who set the game to silent.</li>
+
+ <li><strong>Sequence-load races drop characters.</strong> After a spec change,
+ <code>Na_VoiceSe</code> returns early until
+ <code>Nap_ReadSubPort(VOICE_GROUP, 0, 1) == sou_now_voice_seq</code>. The first
+ syllables of the first line with a new speaker can be silently lost.</li>
+</ul>
+
+<h2>Corrections to earlier notes</h2>
+
+<p>The following supersede statements in <code>../extraction.md</code> as originally written.</p>
+
+<table>
+<tr><th>Was</th><th>Actually</th></tr>
+
+<tr><td>"Bank 157 has only 83 instruments … that voice collapses every digraph sample
+above <code>0x53</code> onto one sound."</td>
+<td>Nothing above <code>0x53</code> is a digraph. The phonology engine's maximum output is
+<code>0x52</code>, plus <code>0x77</code>. Bank 157 covers the phonology range exactly; the
+missing block is the <a href="editor.html">editor's letter names</a>, unreachable at spec 3.
+The only real loss is <code>you</code>.</td></tr>
+
+<tr><td>"43 of the 119 codes are unreachable through normal text … only ever hit by the
+anti-repetition random substitution."</td>
+<td>36 of them (<code>0x53</code>–<code>0x76</code>) are reachable — through the mode-1
+editor path — and the random substitution can <em>not</em> reach them, since it only
+produces <code>0x00</code>–<code>0x3F</code>.</td></tr>
+
+<tr><td>"<code>Sou_SpecialRoutine10</code> replays it on the next <em>audio</em> frame."</td>
+<td>It runs inside <code>Na_GameFrame</code>, which <code>graph.c</code> calls once per
+<em>game</em> frame. Same ~17 ms at 60 fps, but the mechanism is the game loop, and it
+halves under <code>SetGameFrame(2)</code>.</td></tr>
+
+<tr><td>"♪ … calls <code>Na_MelodyVoice(npc_id)</code> — the villager's tune."</td>
+<td>It plays a fixed 16-note pattern <code>{13, 14 × 15}</code>, not a tune. The per-NPC
+part is <em>which instrument</em>: <code>npc_voice_id</code> selects a chunk of sequence
+248 to copy in. It is the speaker's timbre playing a constant figure.</td></tr>
+
+<tr><td>Specs 5 and 6 listed only in the spec→sequence table, unattributed.</td>
+<td>They are the <strong>player's own voice</strong> in text-entry menus — 5 male, 6
+female — set from <code>m_submenu.c:364</code>, and are special-cased throughout
+<code>Na_VoiceSe</code>.</td></tr>
+
+<tr><td>Voice mode not covered.</td>
+<td>Three modes gate the whole pipeline, and the gate depends on the message, not just
+the config. See <a href="gating.html">gating</a>.</td></tr>
+
+<tr><td>Mode <code>f == 1</code> not covered.</td>
+<td>A third calling convention that bypasses phonology entirely. See
+<a href="editor.html">the editor path</a>.</td></tr>
+</table>
+
+<p>Everything else in <code>extraction.md</code> §1 was re-verified against the current tree
+and holds: the voice table, the 55 digraph entries, both vowel tables, the per-spec
+timbre values, and the spec→sequence→bank mapping.</p>
+
+<div class="pager">
+ <span><a href="editor.html">← 7. The editor path</a></span>
+ <span><a href="reference.html">9. Reference tables →</a></span>
+</div>
+
+</body>
+</html>