diff options
Diffstat (limited to 'docs/quirks.html')
| -rw-r--r-- | docs/quirks.html | 157 |
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 & corrections — Animalese</title> +<link rel="stylesheet" href="style.css"> +</head> +<body> + +<nav class="crumb"><a href="index.html">Animalese pipeline</a> / 8. Quirks & corrections</nav> + +<h1>8. Quirks, dead code & 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 > 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 && 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 e and magic 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 && a <= 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> |