1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>9. Quirks, dead code & corrections — Animalese</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav class="crumb"><a href="index.html">Animalese pipeline</a> / 9. Quirks & corrections</nav>
<h1>9. 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 from earlier working notes
(<code>extraction.md</code>, since retired — its surviving content lives on
<a href="disc.html">the disc page</a>).</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 with one
instrument to spare, the missing block is the <a href="editor.html">editor's letter
names</a> (unreachable at spec 3), and nothing is lost: sequence 244 explicitly maps
<code>you</code> to that 83rd instrument — a dedicated substitute, not a clamp.</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 the retired notes was re-verified against the current tree and
holds: the voice table, the 53 digraph entries, both vowel tables, the per-spec timbre
values, and the spec→sequence→bank mapping. The table contents, bank layouts and
sequence bytecode were additionally verified byte-for-byte against a dumped disc image
(see <a href="disc.html">the disc page</a>).</p>
<div class="pager">
<span><a href="disc.html">← 8. Walking the disc image</a></span>
<span><a href="reference.html">10. Reference tables →</a></span>
</div>
</body>
</html>
|