<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>piney_apples</title>
<subtitle>Reverse engineering the .hack PS2 tetralogy (CyberConnect2 / Bandai, 2002-2003), starting with .hack//Infection, using tools written for the job. No game data included.</subtitle>
<id>/</id>
<link rel="self" href="/feed.xml"/>
<link rel="alternate" href="/"/>
<link rel="alternate" type="application/json" href="/log.json"/>
<updated>2026-10-02T00:00:00Z</updated>
<generator>cairns 0.5.1</generator>
<entry>
<title>&quot;Field 28&#39;s held battle flag is event 50&#39;s game of tag; Infection&#39;s story without god, and what the pilot lacked there&quot;</title>
<id>/360-field-28-s-held-battle-flag-is-event-50-s-game-of-tag</id>
<link rel="alternate" href="/360-field-28-s-held-battle-flag-is-event-50-s-game-of-tag"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="test"/>
<category term="battle"/>
<category term="script"/>
<summary>[[358]] and [[359]] left two questions open: why in_battle held at 1 in field 28 with no fight, and how the story plays without the survey&#39;s aids.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/358-infection-s-whole-story-under-the-autopilot-the-pilot-s/&quot; title=&quot;&amp;#39;Infection&amp;#39;s whole story under the autopilot: the pilot&amp;#39;s gaps up to event 21&amp;#39;&quot;&gt;358&lt;/a&gt; and &lt;a href=&quot;/359-infection-s-whole-story-ends-under-the-autopilot-event-22-s/&quot; title=&quot;&amp;#39;Infection&amp;#39;s whole story ends under the autopilot: event 22&amp;#39;s cure boxes, a ghost Kite under god, board posts first&amp;#39;&quot;&gt;359&lt;/a&gt; left two questions open: why &lt;code&gt;in_battle&lt;/code&gt; held at 1 in
field 28 with no fight, and how the story plays without the survey&#39;s
aids. Both are answered here; neither found a fault in the port.&lt;/p&gt;
&lt;h2 id=&quot;field-28-stehoney-s-tag&quot;&gt;Field 28: Stehoney&#39;s tag&lt;/h2&gt;
&lt;p&gt;The hold was reproduced by letting the pilot skip the closed-event rule
for events 13 and 15 (a switch since removed). It came back at frame
38,400: Kite alone at field 28&#39;s start, one foe 1,928 away. That foe is
enemy row 131, put there by an event (&lt;code&gt;ent_root&lt;/code&gt; 0), standing (act 0)
with Kite as its target.&lt;/p&gt;
&lt;p&gt;INF event 50 (&lt;code&gt;SB0020 GOB1-1&lt;/code&gt;) is a side event. Once board thread 9&#39;s
post is read it gate-marks area 28. In field 28 its block 5 sets the
place and makes enemy 131 (&lt;code&gt;entry 5 131&lt;/code&gt;). Block 7 opens a game of tag,
and block 8 ends it once the foe is absent, with a reward (item 6/60).
The tagged foe keeps clear of Kite. &lt;code&gt;ccThGameCtrl&lt;/code&gt; (gcmn
0x00517c50-0x00517d2c, &lt;code&gt;battle_now&lt;/code&gt;) puts the game in battle while an
enemy is within &lt;code&gt;inBattleDist&lt;/code&gt; (2,200) of a member, so the flag holds as
long as Stehoney does. That is the game&#39;s own rule. The pilot only went
there because it took the last marked area; the closed-event rule of
&lt;a href=&quot;/358-infection-s-whole-story-under-the-autopilot-the-pilot-s/&quot; title=&quot;&amp;#39;Infection&amp;#39;s whole story under the autopilot: the pilot&amp;#39;s gaps up to event 21&amp;#39;&quot;&gt;358&lt;/a&gt; keeps it away. Kite alone, sent straight into field 28 from story
start 16 and left idle for 20,000 frames, meets no foe at all; that start&#39;s
flags for event 50 were not looked at.&lt;/p&gt;
&lt;p&gt;On the way, &lt;code&gt;enemy_ai::Out::InBattleDist&lt;/code&gt;&#39;s comment had the gold goblin&#39;s
-2.0 backwards. The same function (0x00517c98) sets battle mode when the
distance is exactly -2.0, so a goblin shaking off a hold forces battle
mode for a frame. The comment now says so; the code was right.&lt;/p&gt;
&lt;h2 id=&quot;the-story-without-god&quot;&gt;The story without god&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;PINEY_SURVEY_AIDS&lt;/code&gt; picks the aids (&lt;code&gt;Aids&lt;/code&gt; in &lt;code&gt;survey.rs&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;unset&lt;/strong&gt; keeps them all: god, the infection held at 0, the hacks&#39;
cores, the levels for the bosses;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;0&lt;/code&gt;&lt;/strong&gt; turns them all off;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;levels&lt;/code&gt;&lt;/strong&gt; turns off god but levels the party to the foes it meets
and stocks Kite in town, as a player grinds and shops (below).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A run without god ends at the first game over, which takes the game to
the title. The run then fails, printing the place and the last 20
seconds of the fight (&lt;code&gt;FIGHT&lt;/code&gt; lines). &lt;code&gt;PINEY_TRACE_BOSS&lt;/code&gt; prints a boss
fight a line a second.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No aids.&lt;/strong&gt; The game is over at frame 40,564, in area 17&#39;s dungeon
(event 14). Kite (63 HP) and BlackRose (70 HP) are both still level 1;
they fall to a level-4 Chicken Hand. The pilot walks straight to each
goal and fights only what holds the walk, so nothing levels it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;levels&lt;/code&gt;.&lt;/strong&gt; Each member below the strongest foe standing (bosses
excepted: Skeith&#39;s row says 99) plus three gets the experience to that
level (&lt;code&gt;levels_for_foes&lt;/code&gt;, the game&#39;s own level-ups). In town, Kite&#39;s
Healing Potions and Mage&#39;s Souls are topped up to five each (&lt;code&gt;supplies&lt;/code&gt;).
The run ends events 3, 4 and 10-28, then the game is over at frame
242,740 in area 26&#39;s dungeon (event 29). Three Armor Shoguns (level 24,
1,010 HP) and a Grand Mage (level 23) wipe Kite (level 75: the
boss-level aid&#39;s lone-dungeon level, 75, had lifted him) with BlackRose and Mia (27-28).&lt;/p&gt;
&lt;p&gt;Earlier settings told the same story:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Matching the foes&#39; level exactly, the party dies to event 14&#39;s Data
Bug at level 5.&lt;/li&gt;
&lt;li&gt;Levelled by the lowest member (Kite overshooting to 99), it reached
event 30 and fell to Skeith, whose own Data Drain (act 5, &lt;code&gt;affect 13&lt;/code&gt;)
fells members with HP left.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-the-pilot-lacked-without-god&quot;&gt;What the pilot lacked without god&lt;/h2&gt;
&lt;p&gt;God&#39;s endless SP had hidden three gaps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A refused skill.&lt;/strong&gt; With too little SP (or Data Drain barred) the
Skills menu opens its info window (process 2-3), which takes OK, not
cancel. The pilot gave cancel for ever (80,000 frames in event 14&#39;s
dungeon).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Drain&#39;s cost.&lt;/strong&gt; It cast Data Drain whatever its SP. It now
drains only with the SP for it, else a Mage&#39;s Soul on Kite first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No plain blow.&lt;/strong&gt; With no skill it could pay for, it stood still while
the foes hit it. In the last 20 seconds of the area-26 wipe, no foe
lost a point. It now gives the action button&#39;s blow when the command
target is an enemy. With that, one Armor Shogun falls and another
drops to 200 of 1,010 HP, but the room still wins.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;where-that-leaves-the-measurement&quot;&gt;Where that leaves the measurement&lt;/h2&gt;
&lt;p&gt;With the aids on, the story runs to its end (&lt;a href=&quot;/359-infection-s-whole-story-ends-under-the-autopilot-event-22-s/&quot; title=&quot;&amp;#39;Infection&amp;#39;s whole story ends under the autopilot: event 22&amp;#39;s cure boxes, a ghost Kite under god, board posts first&amp;#39;&quot;&gt;359&lt;/a&gt;). Without them, the
first walls are the pilot&#39;s, not the port&#39;s: it does not grind, plan its
SP, flee or use magic against armour. The damage rules it meets were
checked against the game&#39;s code earlier (the battle harnesses), and
nothing here contradicts them. Area 26&#39;s monster room is where a better
pilot would have to start.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Infection&#39;s whole story ends under the autopilot: event 22&#39;s cure boxes, a ghost Kite under god, board posts first&quot;</title>
<id>/359-infection-s-whole-story-ends-under-the-autopilot-event-22-s</id>
<link rel="alternate" href="/359-infection-s-whole-story-ends-under-the-autopilot-event-22-s"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="test"/>
<category term="script"/>
<category term="world"/>
<summary>[[358]] took infection_whole_story to event 21.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/358-infection-s-whole-story-under-the-autopilot-the-pilot-s/&quot; title=&quot;&amp;#39;Infection&amp;#39;s whole story under the autopilot: the pilot&amp;#39;s gaps up to event 21&amp;#39;&quot;&gt;358&lt;/a&gt; took &lt;code&gt;infection_whole_story&lt;/code&gt; to event 21. It now runs INF&#39;s story
from its first start to event 31&#39;s end: 401,400 frames, 379 seconds
headless. Every event from 3 to 31 ends in order, the last on the staff
roll. Three more gaps were the pilot&#39;s; one was the harness&#39;s god.&lt;/p&gt;
&lt;h2 id=&quot;event-22-s-cures-are-story-boxes&quot;&gt;Event 22&#39;s cures are story boxes&lt;/h2&gt;
&lt;p&gt;Event 22&#39;s blocks 21-24 wait on Kite holding items 15/54-57 (Remedy,
Custom Remedy, True Remedy, First Remedy). No script gives them; no story
room&#39;s statue holds them (&lt;code&gt;ROOMDATA.itemID&lt;/code&gt; names no &lt;code&gt;E05x&lt;/code&gt;). They are in
area 31&#39;s dungeon (&lt;code&gt;D0171&lt;/code&gt;), in four story boxes, one a floor.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;D0171_gim  floor room  x      y      type  flag
row 3      0     9     6000   21562  0     0x450036  E054 Remedy
row 4      1     11    18750  12843  0     0x450037  E055 Custom Remedy
row 9      2     7     21046  18093  0     0x450038  E056 True Remedy
row 2      3     11    21000  24890  0     0x450039  E057 First Remedy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SetItemBox&lt;/code&gt; gives a type-0 row with a &lt;code&gt;flag&lt;/code&gt; that item
(&lt;code&gt;box_item_code(flag)&lt;/code&gt;). The flag is the item&#39;s code as &lt;code&gt;0x45_0000 | id&lt;/code&gt;
(&lt;code&gt;E&lt;/code&gt; and the id; &lt;code&gt;itemTblE&lt;/code&gt;&#39;s &lt;code&gt;code&lt;/code&gt; for 54 is the text &lt;code&gt;E054&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The pilot gets them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Want::Item(id)&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;story_wants&lt;/code&gt; gives it for a block short only of
an important item (category 15, Kite).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;item_spot&lt;/code&gt;.&lt;/strong&gt; In a story dungeon it finds the box rows holding a
wanted item, nearest floor at or below Kite&#39;s first (the walker does
not climb). Else a room whose row gives its statue an item, else the
statue room (&lt;code&gt;DungeonArea::statue_room&lt;/code&gt;, the room built from the type&#39;s
&lt;code&gt;symroom&lt;/code&gt; model).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;open_item&lt;/code&gt;.&lt;/strong&gt; In the room it walks to the unopened gimmick nearest the
row&#39;s x, y (&lt;code&gt;FieldWorld::unopened_gimmick_near&lt;/code&gt;: switched on, still on
the command list) and gives OK. The box&#39;s menus are &lt;code&gt;story_player&lt;/code&gt;&#39;s. A
room counts as done only after 300 frames there with nothing to open,
since the boxes switch on with the room.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;FieldWorld::unopened_statue&lt;/code&gt; (a statue not yet opened) is now its own
function; &lt;code&gt;no_active_object&lt;/code&gt; uses it.&lt;/p&gt;
&lt;h2 id=&quot;a-ghost-walks-through-boxes-god-left-kite-one&quot;&gt;A ghost walks through boxes; god left Kite one&lt;/h2&gt;
&lt;p&gt;The pilot reached the First Remedy&#39;s box and stood in it. Kite came
within 0.63 of the box&#39;s centre (radius 40). The box&#39;s body was in the
list where it stood, and Kite&#39;s was not. Traced room by room, Kite&#39;s body
went at room (0, 10) and in every room after: there &lt;code&gt;hp&lt;/code&gt; reached 0 and his
&lt;code&gt;cond[DEAD]&lt;/code&gt; went 2, 3, 4. With dead 4, &lt;code&gt;ccSpcChar::HitCheck&lt;/code&gt; takes the
body off the list (&lt;code&gt;kite_hit_check&lt;/code&gt;), as in the game: a ghost goes
through.&lt;/p&gt;
&lt;p&gt;The god console heals the party at the end of each frame, so a hit worth
more than Kite&#39;s HP still fells him. &lt;code&gt;FieldWorld::revive_party&lt;/code&gt; got the
members up but not Kite. It now revives Kite too (affect 20, by a member
still standing). &lt;code&gt;kite_stops_at_area_31_s_cure_box&lt;/code&gt; (with
&lt;code&gt;kite_stops_at_a_story_box_on_b2&lt;/code&gt;, now sharing &lt;code&gt;kite_stops_at_the_story_box&lt;/code&gt;
and &lt;code&gt;in_story_dungeon&lt;/code&gt;) walks to floor 3, room 11 under god and runs at the
box: he stops against it. The game&#39;s rule is untouched; only the harness&#39;s
god changed.&lt;/p&gt;
&lt;h2 id=&quot;the-other-two&quot;&gt;The other two&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The cure line.&lt;/strong&gt; &lt;code&gt;info_lines&lt;/code&gt; (the window &lt;a href=&quot;/355-event-22-s-cures-and-finale-piros-colour-s-line-waits-for/&quot; title=&quot;&amp;#39;Event 22&amp;#39;s cures and finale: piros_colour&amp;#39;s line waits for its window, and a room change disables the event task at once&amp;#39;&quot;&gt;355&lt;/a&gt; gave &lt;code&gt;piros_colour&lt;/code&gt;&#39;s
line) is a window every pilot now answers, as it does &lt;code&gt;message_open&lt;/code&gt;
and &lt;code&gt;announce&lt;/code&gt;. Without it the pilot waited on &quot;Piros used the Remedy&quot;
for ever.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event 23&#39;s posts.&lt;/strong&gt; Block 1 waits on four new posts read on the board,
and block 2 on the mail they bring, on the desktop. The top page pilot
left for the desktop first and never opened the board. New posts (and
a post to write) now come before leaving.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;checked&quot;&gt;Checked&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;infection_whole_story&lt;/code&gt; (ignored, a diagnostic: &lt;code&gt;--ignored --nocapture&lt;/code&gt;)
passes, events 3, 4 and 10-31 each ending once. The game suite (220) and
the world suite (92) pass.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Infection&#39;s whole story under the autopilot: the pilot&#39;s gaps up to event 21&quot;</title>
<id>/358-infection-s-whole-story-under-the-autopilot-the-pilot-s</id>
<link rel="alternate" href="/358-infection-s-whole-story-under-the-autopilot-the-pilot-s"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="test"/>
<category term="script"/>
<summary>[[276]] ran Mutation&#39;s story from its new game to its end under the story autopilot (mutation_whole_story), and Outbreak&#39;s followed.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/276-mutation-s-whole-story-under-the-autopilot-101-to-116/&quot; title=&quot;Mutation&amp;#39;s whole story under the autopilot, 101 to 116&quot;&gt;276&lt;/a&gt; ran Mutation&#39;s story from its new game to its end under the story
autopilot (&lt;code&gt;mutation_whole_story&lt;/code&gt;), and Outbreak&#39;s followed. Infection,
the volume being finished first, had only &lt;code&gt;story_survey&lt;/code&gt;: one story start
at a time, 4,000 frames each. &lt;code&gt;infection_whole_story&lt;/code&gt; now runs INF from its
first story start (event 3; 1 and 2 are the desktop&#39;s) to event 31&#39;s end.
Its first runs stopped at once. Each stop was the pilot&#39;s, not the port&#39;s.&lt;/p&gt;
&lt;h2 id=&quot;what-the-pilot-lacked&quot;&gt;What the pilot lacked&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Event 10: Kite&#39;s own post.&lt;/strong&gt; &lt;code&gt;bbs_post7&lt;/code&gt; leaves a post in state 7,
which the board writes out on the next visit. The top page player read
only new posts (state 1), so it went between the desktop and the top
page for ever. It now opens the board first when
&lt;code&gt;piney_toppage::bbs::check_write_bbs&lt;/code&gt; finds a post waiting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event 11: targets that are not town NPCs.&lt;/strong&gt; The town talk looked
every &lt;code&gt;add_target&lt;/code&gt; up as an NPC. Event 11&#39;s are BlackRose (type 2, a
party character) and the Chaos Gate (13). &lt;code&gt;GateGoal::Talk&lt;/code&gt; now carries
the kind: 2 a party character, 3, 4 and 8-12 an NPC (8-12 are event
16&#39;s five merchants), 13 the gate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Events 12 and 14: a closed event.&lt;/strong&gt; An event&#39;s &lt;code&gt;end_event&lt;/code&gt; closes it
(bit 63), and it turns done (bit 62) only at the next mode&#39;s
&lt;code&gt;ccStartThEvent&lt;/code&gt;. The next story event opens on done. With the story
wanting nothing in town, the pilot took the last marked area (field
28, an old mark) instead. It now logs out whenever a story event is
closed and not yet done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event 13: a town marker.&lt;/strong&gt; Block 2 waits on &lt;code&gt;near_marker 31&lt;/code&gt; in Mac
Anu. The pilot walked to markers only in fields. &lt;code&gt;GateGoal::Marker&lt;/code&gt;
walks to &lt;code&gt;World::marker&lt;/code&gt;&#39;s place first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event 21: Elk alone.&lt;/strong&gt; Block 9 refuses the gate while anyone but Elk
is along (&lt;code&gt;in_party 10&lt;/code&gt;, &lt;code&gt;party_other 10&lt;/code&gt;). The pilot had filled the
free slot with Mistral. &lt;code&gt;story_wants&lt;/code&gt; now gives &lt;code&gt;Want::Only(pc)&lt;/code&gt; for a
&lt;code&gt;party_other&lt;/code&gt; block; the pilot then sends the others away, re-invites
the member, and fills no slots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event 22: two towns.&lt;/strong&gt; Event 22 wants both Mac Anu and Dun Loireag.
Each town&#39;s goal was the other, so Kite went back and forth. A town
that is itself wanted now keeps him.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;whole_story&lt;/code&gt; now starts at the volume&#39;s first story start
(&lt;code&gt;crate::start::POINTS&lt;/code&gt;). It gives up after &lt;code&gt;PINEY_SURVEY_STALL&lt;/code&gt; frames
(200,000) with no event ending, and says where. The stall report
(&lt;code&gt;whole_state&lt;/code&gt;) adds, in a field or dungeon, the event targets, the foes
and the nearest one&#39;s distance, &lt;code&gt;in_battle&lt;/code&gt;, the menu, the phase and the
playing block.&lt;/p&gt;
&lt;h2 id=&quot;where-it-stands&quot;&gt;Where it stands&lt;/h2&gt;
&lt;p&gt;Events 3, 4 and 10-21 end, the last at frame 222,600 (with god, the
infection held at 0, cores given for the hacks, as the survey&#39;s aids do).
The run then stops in area 31&#39;s dungeon with Piros in the party. Event
22&#39;s blocks 21-24 wait on Kite holding the cures (items 15/54-57). In a
story dungeon the idol of a story room holds that room&#39;s &lt;code&gt;itemID&lt;/code&gt;
(&lt;code&gt;SetIDOL&lt;/code&gt;, &lt;code&gt;ep.param[1]&lt;/code&gt;), and the pilot never opens idols.&lt;/p&gt;
&lt;p&gt;The stall in field 28 showed one more thing to look at: &lt;code&gt;in_battle&lt;/code&gt;
stayed 1 for 60,000 frames with Kite idle at the field&#39;s start. The
pilot&#39;s &lt;code&gt;gate_out&lt;/code&gt; takes &lt;code&gt;in_battle&lt;/code&gt; as busy, so it never left. That run
predates the closed-event rule, which keeps the pilot out of field 28;
the &lt;code&gt;in_battle&lt;/code&gt; reading itself was not explained.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;The port&#39;s messages go through tracing: PINEY_LOG filters them, and the console shows warnings and errors&quot;</title>
<id>/357-the-port-s-messages-go-through-tracing-piney-log-filters</id>
<link rel="alternate" href="/357-the-port-s-messages-go-through-tracing-piney-log-filters"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="build"/>
<category term="tooling"/>
<summary>Not the game&#39;s.</summary>
<content type="html">&lt;p&gt;Not the game&#39;s. The user asked for a better logger. The port&#39;s
diagnostics were 125 bare &lt;code&gt;eprintln!&lt;/code&gt;s outside the tests, in nine crates.
They had no level, no source and no way to quiet them. A Data Drain trace
sat behind its own variable (&lt;code&gt;DBG_DRAIN&lt;/code&gt;). Warnings never reached anyone
playing without a terminal.&lt;/p&gt;
&lt;h2 id=&quot;now&quot;&gt;Now&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;tracing&lt;/code&gt; crate (0.1.44) logs everywhere, and &lt;code&gt;tracing-subscriber&lt;/code&gt;
(0.3.23, &lt;code&gt;env-filter&lt;/code&gt;) prints it. Both are workspace dependencies. Each
site took a level by what it reports:&lt;/p&gt;
&lt;div class=&quot;table-scroll&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;level&lt;/th&gt;&lt;th&gt;what&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;error&lt;/td&gt;&lt;td&gt;the run cannot go on: bad arguments, no GPU, an unreadable disc or build, a failed &lt;code&gt;--shot&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;warn&lt;/td&gt;&lt;td&gt;a part is missing and play goes on without it: a map, effects, a stream or movie counted as played, a set-up pass that did not settle, an event-host method not ported, a texture not found&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;info&lt;/td&gt;&lt;td&gt;a replay&#39;s progress, the pad log&#39;s path, the viewer&#39;s model count&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;debug&lt;/td&gt;&lt;td&gt;the desktop&#39;s and top page&#39;s script trace, a set-up pass&#39;s frame count, a mode change the session did not expect&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;trace&lt;/td&gt;&lt;td&gt;Kite&#39;s skill and act around a field frame (was &lt;code&gt;DBG_DRAIN&lt;/code&gt;; now &lt;code&gt;tracing::enabled!&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;piney-game&lt;/code&gt; installs the subscriber first thing in &lt;code&gt;main&lt;/code&gt;
(&lt;code&gt;logging::init&lt;/code&gt;). It prints compact lines to stderr, coloured only on a
terminal. &lt;code&gt;PINEY_LOG&lt;/code&gt; is read as an &lt;code&gt;EnvFilter&lt;/code&gt;. The default,
&lt;code&gt;warn,piney=info&lt;/code&gt;, keeps the port&#39;s crates at info (targets are matched by
prefix) and wgpu and naga, whose &lt;code&gt;log&lt;/code&gt; records now arrive through
&lt;code&gt;tracing-log&lt;/code&gt;, at warn. The viewer sets up the same filter.&lt;/p&gt;
&lt;p&gt;A third layer, &lt;code&gt;ToConsole&lt;/code&gt;, keeps every warning and error as one line
(&lt;code&gt;WARN: message field=value&lt;/code&gt;). The window&#39;s frame loop hands those lines to
the console&#39;s &lt;code&gt;say&lt;/code&gt;, so they show in the console (F1) without a terminal.
&lt;code&gt;warnings_reach_the_console&lt;/code&gt; checks it under a scoped subscriber (info is
left out, fields are kept).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;piney-gen&lt;/code&gt; and &lt;code&gt;piney-build&lt;/code&gt; keep &lt;code&gt;eprintln!&lt;/code&gt;: their output is a command
line tool&#39;s answer, not a log. Tests keep theirs too.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Each arrival in a town draws its own walking PCs: the session counts frames from power-on&quot;</title>
<id>/356-each-arrival-in-a-town-draws-its-own-walking-pcs-the</id>
<link rel="alternate" href="/356-each-arrival-in-a-town-draws-its-own-walking-pcs-the"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="world"/>
<category term="engine"/>
<summary>Issue #32: the PCs walking a Root Town were the same on every visit.</summary>
<content type="html">&lt;p&gt;Issue #32: the PCs walking a Root Town were the same on every visit. In
the game they change each time the party comes back.&lt;/p&gt;
&lt;p&gt;A town&#39;s set-up runs &lt;code&gt;ccInitRand&lt;/code&gt; (INF SLUS_202.67:0x001d9900). It seeds
the game&#39;s MT19937 with 4352 and draws it &lt;code&gt;ccSys+0x358&lt;/code&gt; times.
&lt;code&gt;ccRegisterRandomNpc&lt;/code&gt; then picks the walking PCs&#39; &lt;code&gt;npcTbl&lt;/code&gt; rows from that
generator (&lt;a href=&quot;/65-mac-anu-s-people-the-five-merchants-the-walking-pcs-the/&quot; title=&quot;Mac Anu&amp;#39;s people: the five merchants, the walking PCs, the character layer and talking&quot;&gt;65&lt;/a&gt;, &lt;code&gt;docs/engine/field-game.md&lt;/code&gt;). The port had the count
as an input, &lt;code&gt;World::set_rand_count&lt;/code&gt;, but nothing ever set it. So every
town drew from a generator drawn 0 times, and picked the same rows.&lt;/p&gt;
&lt;p&gt;The count is a frame counter. In &lt;code&gt;ccSystem::Ctrl&lt;/code&gt; (0x0010a6bc), after the
wait on &lt;code&gt;sceGsSyncV&lt;/code&gt; for the frame rate, the game adds one to &lt;code&gt;ccSys+0x358&lt;/code&gt;
once per game frame. &lt;code&gt;ccSystem&lt;/code&gt;&#39;s set-up (0x0010a944) zeroes it at power-on.
&lt;code&gt;Session&lt;/code&gt; now keeps the same count (&lt;code&gt;sys_frames&lt;/code&gt;, one a &lt;code&gt;step&lt;/code&gt; from power-on).
It hands the count to every town it sets up: on Log in, and on each
&lt;code&gt;enter_world&lt;/code&gt; into a town. The port&#39;s frames cannot equal the game&#39;s (its
load times differ), but two arrivals now differ as they do in the game.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;each_arrival_in_town_has_its_own_pcs&lt;/code&gt; goes from event 3&#39;s field to Mac
Anu, then Dun Loireag, then Mac Anu again, by the console&#39;s &lt;code&gt;town&lt;/code&gt;. It
compares the rows of the walking PCs on the two arrivals. With the count
left at 0 the rows are the same and the test fails.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Event 22&#39;s cures and finale: piros_colour&#39;s line waits for its window, and a room change disables the event task at once&quot;</title>
<id>/355-event-22-s-cures-and-finale-piros-colour-s-line-waits-for</id>
<link rel="alternate" href="/355-event-22-s-cures-and-finale-piros-colour-s-line-waits-for"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="script"/>
<category term="ui"/>
<category term="world"/>
<summary>Two reports on INF event 22 (PIRO02, the Cursed Piros quest), both fixed.</summary>
<content type="html">&lt;p&gt;Two reports on INF event 22 (PIRO02, the Cursed Piros quest), both fixed.&lt;/p&gt;
&lt;h2 id=&quot;issue-30-the-cure-s-line-went-by-unseen&quot;&gt;Issue #30: the cure&#39;s line went by unseen&lt;/h2&gt;
&lt;p&gt;When Kite takes a cure (items 15/54-57), blocks 21-24 delete it, add one
to &lt;code&gt;eventStatus[1]&lt;/code&gt; and set &lt;code&gt;eventStatus[24]&lt;/code&gt; (the save&#39;s +0x6510) to the
message of the line: 16, 20, 24 or 35. Blocks 25-27 then run
&lt;code&gt;piros_colour 0&lt;/code&gt;. On statuses 3, 5, 7 (and 9 with operand 0) the case
plays sound 74 and shows that message&#39;s first line through
&lt;code&gt;ccEvent::DispInfo&lt;/code&gt; (INF SLUS_202.67:0x001b27b0). In the field,
&lt;code&gt;DispInfo&lt;/code&gt; blocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it waits for &lt;code&gt;ccMenuCtrl::CheckMenuType&lt;/code&gt; to be -1;&lt;/li&gt;
&lt;li&gt;it calls &lt;code&gt;ChangeInfo&lt;/code&gt;, then &lt;code&gt;ccBreathThread(5)&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;it polls &lt;code&gt;Check(0)&lt;/code&gt; a frame at a time until the window is answered;&lt;/li&gt;
&lt;li&gt;it calls &lt;code&gt;Close&lt;/code&gt;, then &lt;code&gt;ccBreathThread(10)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The case&#39;s flash and tint come after. &lt;a href=&quot;/99-piros-s-colour-the-event-instruction-piros-colour-and-a/&quot; title=&quot;Piros&amp;#39;s colour: the event instruction piros_colour and a character&amp;#39;s affect tint in town&quot;&gt;99&lt;/a&gt; ported the case from an eemu
run that stubbed &lt;code&gt;DispInfo&lt;/code&gt; as a plain call, so the port opened the window
and went on. Five frames later message 17 replaced it.&lt;/p&gt;
&lt;p&gt;Now &lt;code&gt;Host::piros_colour(code)&lt;/code&gt; sets the case up and answers whether it
tells the line (&lt;code&gt;piros::Sequence::tells&lt;/code&gt;). If so, the interpreter plays
sound 74 and runs &lt;code&gt;DispInfo&lt;/code&gt;&#39;s wait itself. It reuses the announcements&#39;
code (&lt;code&gt;announce_from&lt;/code&gt;, which now takes the window&#39;s opener), and
&lt;code&gt;Host::info_lines&lt;/code&gt; opens the window. Then comes the host&#39;s
&lt;code&gt;Wait::PirosColour&lt;/code&gt;. &lt;code&gt;Sequence&lt;/code&gt; no longer carries the sound or the line.
Its first &lt;code&gt;tick&lt;/code&gt; is the instruction&#39;s own frame. The fixture test
(&lt;code&gt;piros_colour_is_the_games&lt;/code&gt;, 66 records) adds the sound and the line back
when the case tells one, and still matches.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;event_22_tells_piros_used_the_cure&lt;/code&gt; (area 31&#39;s dungeon, the first cure
given) checks the order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the line (message 16) opens at frame 134, after sound 74;&lt;/li&gt;
&lt;li&gt;it is answered at 171;&lt;/li&gt;
&lt;li&gt;message 17 opens at 186: 10 frames of &lt;code&gt;DispInfo&lt;/code&gt;&#39;s tail, then the
case&#39;s breath of 5.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;issue-31-the-finale-played-on-the-loading-screen-and-gave&quot;&gt;Issue #31: the finale played on the loading screen, and gave no diary&lt;/h2&gt;
&lt;p&gt;The fourth cure takes the status to 9. Block 28 then sets
&lt;code&gt;eventStatus[0]&lt;/code&gt; to 2 and runs &lt;code&gt;room_point 5&lt;/code&gt;. Block 29 (&lt;code&gt;eventStatus[0]&lt;/code&gt;
2, in point 5, phase 4 or more) is the finale. It ends with &lt;code&gt;item_add_menu 0 12 24&lt;/code&gt; (Piros&#39; Diary) and &lt;code&gt;scene&lt;/code&gt; to Mac Anu.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;room_point&lt;/code&gt; calls &lt;code&gt;WORLD_MAN::RoomSelect&lt;/code&gt; (main 0x0019dca0). It always
ends in &lt;code&gt;ccGame::ChangeScene&lt;/code&gt;, whose &lt;code&gt;ChangeRequest(6, 7)&lt;/code&gt; calls
&lt;code&gt;ccDisableThEvent&lt;/code&gt; at once (phase -1). So in the game, the rest of that
pass cannot start block 29. Block 29 waits for the new room&#39;s set-up and
runs once play begins, in view.&lt;/p&gt;
&lt;p&gt;The port&#39;s host made the scene change, but the task was disabled only
when the area took the request, after the interpreter&#39;s frame. In between,
block 29 started in the old room, where the scene had already moved to
point 5&#39;s room. The new room&#39;s set-up then held its loading frame
(&lt;code&gt;Phase::Hold&lt;/code&gt;) through the whole cutscene. Its windows showed over the
black screen with the logo, as in the report.&lt;/p&gt;
&lt;p&gt;The diary was lost the same way. &lt;code&gt;item_add_menu&lt;/code&gt; asks for menu 29, but the
held set-up never runs the menus, so &lt;code&gt;field_menu&lt;/code&gt; read -1 the next frame.
The interpreter took that as the menu closed, and nothing gave the item.&lt;/p&gt;
&lt;p&gt;The interpreter now disables itself after &lt;code&gt;room&lt;/code&gt; and &lt;code&gt;room_point&lt;/code&gt;, as it
does at &lt;code&gt;scene&lt;/code&gt;. Both run only while playing (level 2 or more; the
cases check &lt;code&gt;$s5 &amp;lt; 2&lt;/code&gt;), and &lt;code&gt;room_point&lt;/code&gt; with no such point does nothing,
as the game&#39;s loop does. &lt;code&gt;prev_room&lt;/code&gt; (127, &lt;code&gt;WORLD_MAN::GoPrevRoom&lt;/code&gt;
0x001a40b0) also ends in &lt;code&gt;ChangeScene&lt;/code&gt; and is treated the same, though no
script on any of the four discs uses it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tools/test_event_vm.py&lt;/code&gt; had stubbed &lt;code&gt;RoomSelect&lt;/code&gt; and &lt;code&gt;GoPrevRoom&lt;/code&gt; whole,
so its run of the game never reached their &lt;code&gt;ChangeScene&lt;/code&gt;, and the fixture
kept the phase at 4 after a room change. Both are now hooked to disable
the task, as its &lt;code&gt;ChangeScene&lt;/code&gt; hook already does. The fixture was written
again: 5 of its 252 &lt;code&gt;play&lt;/code&gt; records changed, all to the port&#39;s new counts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Event 22 block 29 takes 365 frames (was 299) once block 28&#39;s &lt;code&gt;room_point&lt;/code&gt;
has disabled the task: its windows take the set-up screen&#39;s shape.&lt;/li&gt;
&lt;li&gt;Event 58 blocks 6-8 and event 162 block 8 change the same way, after an
earlier block&#39;s &lt;code&gt;room&lt;/code&gt;.
&lt;code&gt;every_block_played_matches_the_game&lt;/code&gt; passes on it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;event_22_finale_plays_in_the_room_and_gives_the_diary&lt;/code&gt; gives the fourth
cure at status 8 and checks four things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;block 29&#39;s first line opens in the new room&#39;s area, in play;&lt;/li&gt;
&lt;li&gt;menu 29 opens;&lt;/li&gt;
&lt;li&gt;Piros&#39; Diary (12/24) is in Kite&#39;s items back in Mac Anu;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eventStatus[1]&lt;/code&gt; ends at 10.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without the fix, the line opens in the old room and menu 29 never opens.
The test helper &lt;code&gt;board_done&lt;/code&gt; must stop rewriting &lt;code&gt;eventStatus[0]&lt;/code&gt; once
the cure is given, or it undoes block 28&#39;s 2 in the new room&#39;s set-up.&lt;/p&gt;
&lt;p&gt;Shots of the fixed finale: the room lit with Kite and Piros under message
32, then &quot;You now have Piros&#39; Diary!&quot; over the same scene.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;A HUD scale: each HUD part shrinks toward its own corner&quot;</title>
<id>/354-a-hud-scale-each-hud-part-shrinks-toward-its-own-corner</id>
<link rel="alternate" href="/354-a-hud-scale-each-hud-part-shrinks-toward-its-own-corner"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="ui"/>
<category term="render"/>
<summary>Not the game&#39;s.</summary>
<content type="html">&lt;p&gt;Not the game&#39;s. The user expected render scale to make the HUD smaller,
as a PC game&#39;s does at a higher resolution. Render scale adds detail
(&lt;a href=&quot;/350-the-console-wraps-scrolls-with-the-mouse-and-lists-its-help/&quot; title=&quot;&amp;#39;The console wraps, scrolls with the mouse and lists its help in columns; render scale, vsync and an FPS cap&amp;#39;&quot;&gt;350&lt;/a&gt;) to a picture that always fills the window, so a separate
&lt;code&gt;hud_scale&lt;/code&gt; (0.5 to 1; console &lt;code&gt;hud_scale&lt;/code&gt;, &lt;code&gt;--hud-scale&lt;/code&gt;) now does it.&lt;/p&gt;
&lt;h2 id=&quot;anchors&quot;&gt;Anchors&lt;/h2&gt;
&lt;p&gt;One sprite queue can carry several HUD parts. &lt;code&gt;MenuFont&lt;/code&gt;, for one, holds
the party panels&#39; HP and SP digits and the target window&#39;s numbers in a
single send. So an anchor has to come from the code that draws each part,
not from the queue. Each packet now carries &lt;code&gt;spr::Anchor&lt;/code&gt;, a point per axis
in the menu&#39;s 512 x 448 space, or none. &lt;code&gt;Disp&lt;/code&gt; sets it on every sprite
before each part:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;party panels: bottom left;&lt;/li&gt;
&lt;li&gt;target window and new-mail mark: top left;&lt;/li&gt;
&lt;li&gt;bracelet gauge: top right;&lt;/li&gt;
&lt;li&gt;battle band: its height alone, toward the top;&lt;/li&gt;
&lt;li&gt;battle announcement: the centre.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything else keeps its size: the menus, the message window,
world-anchored marks (enemy bars, the target cursor, balloons, damage
numbers) and the dim. The renderer scales a packet&#39;s position (its offset
folded in) and its size about the anchor. At 1 it leaves the packet
untouched, so the game&#39;s frames, and the 65 harness tests, are unchanged.&lt;/p&gt;
&lt;h2 id=&quot;the-minimap-and-two-seams&quot;&gt;The minimap and two seams&lt;/h2&gt;
&lt;p&gt;The field&#39;s minimap (&lt;code&gt;piney_world::map&lt;/code&gt;) draws whole into the layers.
&lt;code&gt;Layers::mark&lt;/code&gt; and &lt;code&gt;map_since&lt;/code&gt; let &lt;code&gt;area.rs&lt;/code&gt; shrink just its commands
toward the top right: vertices, scissors (the inclusive ends widened to
whole pixels) and texture coordinates.&lt;/p&gt;
&lt;p&gt;At 0.6 a light line crossed the map. 0.5 and 0.75 were clean, and the
line also showed at render scale 1. A dump of the map&#39;s primitives found
it. The terrain is four tiles, and each stops 1/16 pixel short of the
next (the top tile ends at 121.625, the bottom one starts at 121.6875). On
whole-pixel positions that gap never covers a pixel&#39;s centre. Shrunk to
fractional ones it can, and the sky showed through. A shrunk sprite&#39;s far
edges are now taken from the next tile&#39;s start (+1/16 before scaling), so
neighbouring tiles share one edge. Clean at 0.55, 0.6 and 0.8.&lt;/p&gt;
&lt;p&gt;A shrunk cell&#39;s texture coordinates are pulled in by half a texel
(&lt;code&gt;Sprite::uv_inset&lt;/code&gt;, &lt;code&gt;sprite::inset_uv&lt;/code&gt;). A bilinear sample at a
fractional edge then stays inside its cell of the atlas.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;the_hud_scale_shrinks_the_panels_into_their_corner&lt;/code&gt; (piney-fieldui)
checks the frame at 0.5: the same primitives in the same order, every
panel primitive half as wide and moved toward the corner, and the frame
back to the game&#39;s at 1. A field shot at render scale 2 with HUD scale
1.0 and 0.6 shows the panels in the bottom left and the map in the top
right.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;A Ryu Book reward&#39;s flicker is the game&#39;s own; the port draws the book once a frame&quot;</title>
<id>/353-a-ryu-book-reward-s-flicker-is-the-game-s-own-the-port</id>
<link rel="alternate" href="/353-a-ryu-book-reward-s-flicker-is-the-game-s-own-the-port"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="ui"/>
<summary>The user saw a Ryu Book &quot;glitch out and in&quot; for an instant when it gives a reward.</summary>
<content type="html">&lt;p&gt;The user saw a Ryu Book &quot;glitch out and in&quot; for an instant when it gives
a reward. The cause is the game&#39;s own loop. &lt;code&gt;ccThBook&lt;/code&gt; (INF gcmn
0x0041a990) runs &lt;code&gt;Breath; BOOK::Draw; BOOK::CheckItemGet;&lt;/code&gt; and then
&lt;code&gt;PadControl&lt;/code&gt; unless a reward ran. A reward&#39;s windows breathe inside
&lt;code&gt;CheckItemGet&lt;/code&gt;, each &lt;code&gt;Draw; Breath&lt;/code&gt; until &lt;code&gt;Check(0)&lt;/code&gt; answers. So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the frame a reward starts draws the book twice: the loop&#39;s &lt;code&gt;Draw&lt;/code&gt;, then
the first window&#39;s;&lt;/li&gt;
&lt;li&gt;the frame one ends draws no book: the last &lt;code&gt;Check&lt;/code&gt; answers, the window
closes, the reward is added, and the loop goes on to its next &lt;code&gt;Breath&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Measured with &lt;code&gt;tools/test_fieldui_rs.py&lt;/code&gt;&#39;s game run (Book I, 15 areas
visited, the scenario of &lt;code&gt;test_book_1&lt;/code&gt;). Frame 36 sends 36 &lt;code&gt;bookWin&lt;/code&gt; and 2
&lt;code&gt;bookBg&lt;/code&gt; packets, against 18 and 1 on the frames around it. Frame 102
sends none of either. The port matched that exactly; that is why the
harness passed. On screen the book&#39;s translucent background flashes
darker on the first frame and vanishes on the last.&lt;/p&gt;
&lt;p&gt;Smoothed, by the user&#39;s choice: &lt;code&gt;Book::frame&lt;/code&gt; draws the book exactly once
a frame. It keeps the first frame&#39;s first draw (skipping a window step&#39;s
second) and adds one on the frame a reward ends. Over a whole reward the
draw calls are the game&#39;s count, one taken away and one added, so the
book&#39;s state keeps the game&#39;s timing.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Book::as_the_game&lt;/code&gt; (on the book task, &lt;code&gt;Task::as_the_game&lt;/code&gt;) restores the
game&#39;s draws. &lt;code&gt;fieldui_probe&lt;/code&gt; sets it, and the 65 harness tests still
match the game frame for frame.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a_ryu_book_reward_draws_the_book_once_a_frame&lt;/code&gt; (piney-fieldui) opens
Book I with its reward due, dismisses the windows, and requires one
background on every frame from the book&#39;s first draw. With the game&#39;s
draws it fails at exactly frames 35 (2) and 102 (0). The book&#39;s first
frame (&lt;code&gt;BOOK::BOOK&lt;/code&gt;, drawn after the loop&#39;s first breath) is the game&#39;s
appearance, not a flicker, and is left out.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Mutation&#39;s recall fades are smooth: its boards are rigid and key on their boxes&quot;</title>
<id>/352-mutation-s-recall-fades-are-smooth-its-boards-are-rigid-and</id>
<link rel="alternate" href="/352-mutation-s-recall-fades-are-smooth-its-boards-are-rigid-and"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="render"/>
<category term="video"/>
<category term="volumes"/>
<summary>[[226]] described the recall in MUT stream 23 (str0695e).</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/226-mutation-s-saves-its-voices-and-the-recall-s-black-fades/&quot; title=&quot;Mutation&amp;#39;s saves, its voices, and the recall&amp;#39;s black fades&quot;&gt;226&lt;/a&gt; described the recall in MUT stream 23 (&lt;code&gt;str0695e&lt;/code&gt;). Its pages
cross-fade through a black board (&lt;code&gt;MDL_blackbord_a01&lt;/code&gt;) one unit in front
of them, or a white one for flashes. 226 took both as translucent models
without a box and keyed them with the w-row formula. That key put the
board first; the page behind it then failed the depth test, and every
fade came out as a cut to black. 226 left open whether the PS2 shows them
that way, since the page layout suggests smooth fades.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/351-the-top-page-s-key-of-the-twilight-a-rigid-model-keys-the/&quot; title=&quot;&amp;#39;The top page&amp;#39;s Key of the Twilight: a rigid model keys the sorted group on its own vertex box, not a Bbox chunk&amp;#39;&quot;&gt;351&lt;/a&gt; found the game&#39;s rule. Every model without &lt;code&gt;mtype &amp;amp; 6&lt;/code&gt; keys the
sorted group on its own vertex box (&lt;code&gt;ccModel::Init&lt;/code&gt; 0x0013a550,
&lt;code&gt;ccBbox_SetBox&lt;/code&gt;). Read from &lt;code&gt;STREAM/STR2E.BIN::str0695e.tmp&lt;/code&gt;, every board
is rigid: &lt;code&gt;MDL_blackbord_a01&lt;/code&gt;, &lt;code&gt;MDL_whitebord_a01&lt;/code&gt; and all the
&lt;code&gt;MDL_bord_*&lt;/code&gt; are mtype 0, flag 0. So in the game the page, which is
farther, is drawn first and the board over it.&lt;/p&gt;
&lt;p&gt;The port now keys them that way (the change of 351). Stream 23 was shot
again (English, frames 1000-1144, every 8): the WORLD BOARD page fades
smoothly to black over about 32 frames (1096-1128). The fades 226 saw as
cuts are smooth, as the layout meant. &lt;code&gt;exact_key&lt;/code&gt; stays, now only for bone
and skin models, and its comment no longer cites the recall.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;The top page&#39;s Key of the Twilight: a rigid model keys the sorted group on its own vertex box, not a Bbox chunk&quot;</title>
<id>/351-the-top-page-s-key-of-the-twilight-a-rigid-model-keys-the</id>
<link rel="alternate" href="/351-the-top-page-s-key-of-the-twilight-a-rigid-model-keys-the"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="render"/>
<category term="ui"/>
<summary>Issue #25 and the user&#39;s reference: on the top page (INF xdttopen0) the sword carries &quot;Key of the Twilight&quot; in yellow down its blade, lit by an orange glow.</summary>
<content type="html">&lt;p&gt;Issue #25 and the user&#39;s reference: on the top page (INF &lt;code&gt;xdttopen0&lt;/code&gt;) the
sword carries &quot;Key of the Twilight&quot; in yellow down its blade, lit by an
orange glow. The port showed a bare blade.&lt;/p&gt;
&lt;h2 id=&quot;what-the-port-drew&quot;&gt;What the port drew&lt;/h2&gt;
&lt;p&gt;The text (&lt;code&gt;MDL_xdtworld1&lt;/code&gt;, alpha 0.6-0.9) and its glow (&lt;code&gt;MDL_xdthotw&lt;/code&gt;,
alpha 0.2-0.4, scale 1.0-1.2) are children of the sword (&lt;code&gt;ExtObj&lt;/code&gt; parent
&lt;code&gt;OBJ_xdtsword&lt;/code&gt;). The text sits 2000 units in front of the blade, the glow
3000. Both are translucent (texture flag 0x08) and so go to the layer&#39;s
sorted group. Both alpha-test GEQUAL &lt;code&gt;trunc(aref * t)&lt;/code&gt; with AFAIL
FB_ONLY, so each writes Z where its alpha passes. The port keyed both
with the w-row formula, &lt;code&gt;M[2][3] / M[3][3]&lt;/code&gt;, because no Bbox chunk names
them. That key is the model&#39;s z scale over its depth: the glow (scale
over 1) came first, wrote Z over its bright core, and the text behind it
failed the depth test. In the opening the text showed at frame 110 and
went when the glow came in. Drawn alone, each landed in the right place.&lt;/p&gt;
&lt;h2 id=&quot;the-game-s-rule&quot;&gt;The game&#39;s rule&lt;/h2&gt;
&lt;p&gt;Every rule on the way was checked and agreed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the w-row formula itself (&lt;code&gt;ccModel::Draw&lt;/code&gt; 0x0013ebbc reads offsets 12,
28, 44 over 60);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ccDLSort::Add&lt;/code&gt; and the in-order walk (&lt;code&gt;StoreDLNode&lt;/code&gt; 0x00108100,
ascending);&lt;/li&gt;
&lt;li&gt;AREF (&lt;code&gt;ccSetMaterialPacket&lt;/code&gt; 0x0013e770, &lt;code&gt;trunc(aref * t)&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;ZBUF never masked (set once in &lt;code&gt;SetScreenModeMain&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;the blend type (&lt;code&gt;flag &amp;amp; 3&lt;/code&gt;; bit 3 dropped);&lt;/li&gt;
&lt;li&gt;vertex alpha 0x80.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the game itself was run. &lt;code&gt;tools/test_effect_draw_rs.py&lt;/code&gt;&#39;s
&lt;code&gt;SceneMachine&lt;/code&gt; decodes &lt;code&gt;xdttopen0&lt;/code&gt; with the game&#39;s own &lt;code&gt;DecodeSetup&lt;/code&gt; and
builds &lt;code&gt;ANM_xdttop1a&lt;/code&gt; with the game&#39;s &lt;code&gt;ccAnm&lt;/code&gt;. Its matrices match the
port&#39;s at every step tried (1, 30, 60, 120). Letting &lt;code&gt;ccModel::Draw&lt;/code&gt; run
natively under the camera&#39;s view then stopped in
&lt;code&gt;_ccCheckBoundingBoxEx&lt;/code&gt;. The game had taken the bounding-box branch.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ccModel::Init&lt;/code&gt; (0x0013a550) sets &lt;code&gt;ccModel&lt;/code&gt; +4, the box pointer, to the
model chunk&#39;s own box (&lt;code&gt;ccModelChunk&lt;/code&gt; +0x10) for every model without
&lt;code&gt;mtype &amp;amp; 6&lt;/code&gt;, and to null for a bone or skin model. &lt;code&gt;Decode_Model&lt;/code&gt; fills
that box for every model through &lt;code&gt;ccBbox_SetBox&lt;/code&gt; (0x001388c0). Per axis
it takes the integer min and max over all vertices, starting at 0x10000
and -0x10000, doubled about the centre only for &lt;code&gt;mtype &amp;amp; 6&lt;/code&gt;. The centre
at +0x30 is &lt;code&gt;((min + max) &amp;gt;&amp;gt; 1) * vertexScale / 4096&lt;/code&gt;. &lt;code&gt;Decode_Bbox&lt;/code&gt;
fills a list of its own and never reaches it.&lt;/p&gt;
&lt;p&gt;So a rigid model always keys on the screen Z of its vertex box&#39;s centre.
The w-row key is only for bone and skin models. The desktop page and the
port had it the other way round: a key from a Bbox chunk, and the w row
for everything without one. Read from the chunks the game decoded, the
centre matches the port&#39;s new &lt;code&gt;vertex_box_centre&lt;/code&gt; on all 41 models of
&lt;code&gt;xdttopen0&lt;/code&gt; and all 138 of &lt;code&gt;xddesk01&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With the real key the text (screen Z 5342) is drawn before the nearer
glow (5368). The glow lays its orange over the lettering, as in the
reference.&lt;/p&gt;
&lt;h2 id=&quot;tests&quot;&gt;Tests&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;the_key_of_the_twilight_is_drawn_before_its_glow&lt;/code&gt; (piney-toppage) at
frames 150, 300 and 421. It fails with the old keying (&quot;the glow drawn
before the text&quot;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model_box_centres_are_the_games&lt;/code&gt;: the sword, text and glow centres as
the game&#39;s decoder gives them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The desktop, demo, stream, world and game suites pass unchanged.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;The console wraps, scrolls with the mouse and lists its help in columns; render scale, vsync and an FPS cap&quot;</title>
<id>/350-the-console-wraps-scrolls-with-the-mouse-and-lists-its-help</id>
<link rel="alternate" href="/350-the-console-wraps-scrolls-with-the-mouse-and-lists-its-help"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="ui"/>
<category term="render"/>
<category term="tooling"/>
<summary>None of this is the game&#39;s.</summary>
<content type="html">&lt;p&gt;None of this is the game&#39;s. These are player requests for the port&#39;s
window: issues #27, #28 and #29, and the user&#39;s asks for a scroll bar and a
fixed &lt;code&gt;help&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-console&quot;&gt;The console&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wrapping (#27).&lt;/strong&gt; An answer wider than the window ran off its edge.
&lt;code&gt;Console::wrapped&lt;/code&gt; now breaks each answer into rows at its spaces, and a
word wider than a row at its letters. A help line&#39;s second column wraps
under itself. The typed line scrolls sideways to keep the cursor in view.
Page Up / Down count rows as drawn.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Help columns.&lt;/strong&gt; The description column sat at a fixed 140 pixels.
&lt;code&gt;pad_log [FILE|stop]&lt;/code&gt; is wider than that and ran into its own text, and
&lt;code&gt;deflicker [on|off]&lt;/code&gt; had one space where the split needs two, so it
never split. The column now starts past the widest command, up to half
the window. The window&#39;s commands are one list, &lt;code&gt;APP_HELP&lt;/code&gt;, each line
split by two spaces.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tab.&lt;/strong&gt; It took the first word of every &lt;code&gt;help&lt;/code&gt; line as a command. At the
title, where there are none, the line &quot;no console commands here&quot; made
&lt;code&gt;no&lt;/code&gt; one. Commands now come only from lines that split. &lt;code&gt;help&lt;/code&gt; also left
the recorded path: each F1 press used to write a &lt;code&gt;help&lt;/code&gt; line into the pad
log.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scroll bar.&lt;/strong&gt; A track on the band&#39;s right edge, with a thumb as tall as
the visible share of the history. The mouse wheel scrolls three rows a
notch, the thumb drags, and a click on the track pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;History.&lt;/strong&gt; Picking a part on the four-part selector (&lt;code&gt;App::boot&lt;/code&gt;)
made a new &lt;code&gt;Console&lt;/code&gt;, so the answers and the lines typed went with the
scene. It now keeps the console and only swaps the fonts
(&lt;code&gt;set_fonts&lt;/code&gt;). The lines typed are also kept between runs, in
&lt;code&gt;console_history.txt&lt;/code&gt; in the build&#39;s folder (the last 200).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;long_answers_wrap&lt;/code&gt;, &lt;code&gt;tab_completes_the_commands&lt;/code&gt;,
&lt;code&gt;the_scroll_bar_scrolls&lt;/code&gt; and &lt;code&gt;the_history_is_kept&lt;/code&gt; hold these. The by-hand &lt;code&gt;console_picture&lt;/code&gt; now
draws a long line and enough history for the bar.&lt;/p&gt;
&lt;h2 id=&quot;the-window&quot;&gt;The window&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;vsync [on|off]&lt;/code&gt; (#29)&lt;/strong&gt;, &lt;code&gt;--no-vsync&lt;/code&gt;: the surface&#39;s present mode,
&lt;code&gt;AutoVsync&lt;/code&gt; or &lt;code&gt;AutoNoVsync&lt;/code&gt;, reconfigured on the spot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;fps_cap N&lt;/code&gt; (#29)&lt;/strong&gt;, &lt;code&gt;--fps-cap N&lt;/code&gt;, 0 for none: &lt;code&gt;about_to_wait&lt;/code&gt;
holds the next redraw until 1/N s after the last picture
(&lt;code&gt;ControlFlow::WaitUntil&lt;/code&gt;). The game&#39;s frames keep their own clock
(&lt;code&gt;tick&lt;/code&gt;&#39;s vblank count), so the cap and vsync change only how often the
picture is shown.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;render_scale N&lt;/code&gt; (#28)&lt;/strong&gt;, &lt;code&gt;--render-scale N&lt;/code&gt;, 1 to 8:
&lt;code&gt;Gs::set_scale&lt;/code&gt;. The frame buffer is N times 512 x 448. The draws keep
the frame&#39;s coordinates, since &lt;code&gt;convert&lt;/code&gt; maps them to clip space by the
frame&#39;s size and normalizes texel coordinates by the logical size. Five
things had to follow the scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the scissor rectangles;&lt;/li&gt;
&lt;li&gt;the frame-buffer copies, both their regions and their textures, and
the previous frame;&lt;/li&gt;
&lt;li&gt;the shadow packet&#39;s Z copy and composite, which read frame pixels (the
uniform&#39;s spare slot now carries the scale);&lt;/li&gt;
&lt;li&gt;the composite&#39;s scissor;&lt;/li&gt;
&lt;li&gt;the half-pixel move that makes the GS&#39;s corner rule meet the GPU&#39;s
centre rule (&lt;code&gt;PIXEL_CENTRE&lt;/code&gt;, now &lt;code&gt;0.5 / scale&lt;/code&gt;). Without it every
sprite edge fell one pixel out at scale 2, which the first run of the
new test caught.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;a_scaled_frame_buffer_draws_the_frame_bigger&lt;/code&gt; draws a frame at scale 1
and 2: flat sprites, a scissored one, a frame-buffer copy drawn back and
a shadow packet. At 2 every pixel is the scale-1 pixel doubled, within 2
of 255, except along the shadow&#39;s bilinear edge, which is checked inside.
Story 19&#39;s start shot at both scales lines up, sharper at 2. The other 22
GS tests are unchanged at scale 1.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;the_tutorial_statue_s_glow_ends&lt;/code&gt; read DATA.BIN a second time for its GS.
Under the full suite&#39;s 8 GB cap, in parallel, that read failed now and
then: a &lt;code&gt;read_path&lt;/code&gt; unwrap, later a 76 MB allocation abort. It now uses the
session&#39;s archive.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;A revived member who ignores orders: not reproduced; what was ruled out&quot;</title>
<id>/349-a-revived-member-who-ignores-orders-not-reproduced-what-was</id>
<link rel="alternate" href="/349-a-revived-member-who-ignores-orders-not-reproduced-what-was"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="battle"/>
<category term="test"/>
<summary>Two reports describe one bug.</summary>
<content type="html">&lt;p&gt;Two reports describe one bug. The user&#39;s run (INF, a dungeon&#39;s B3, Kite and
Elk, a fight with six or more Water Witches, &lt;code&gt;enemyTbl&lt;/code&gt; row 37) and issue
#26: a member revived after dying several times ignores orders, both
strategies and direct ones. Per #26 it lasts only in the room of the fight.
In the user&#39;s video (&lt;code&gt;work/video_of_bugs/20261002-0701-22.4608026.mp4&lt;/code&gt;) Elk
is out of battle at 47/190 HP with full SP. He stands still, and says
nothing to &quot;Everyone, First Aid!&quot; (16) or to &quot;Elk, use La Repth!&quot; (5 on
Kite). His SP regenerates, so he is alive: &lt;code&gt;condition_time_count&lt;/code&gt; runs only
for &lt;code&gt;dead&lt;/code&gt; 0. The run was not reproduced. This entry records what was
checked, so the next look starts past it.&lt;/p&gt;
&lt;p&gt;Every way an order is refused makes the member speak. &lt;code&gt;ChatCommandFulfilCheck&lt;/code&gt;
and the same-order branch of &lt;code&gt;ChatCommand&lt;/code&gt; each say a line, and the accept
lines for 5 and 16 are templates with no chance roll. A silent member
means one of these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The order never reached &lt;code&gt;ChatCommand&lt;/code&gt;. Brains calls it only for
&lt;code&gt;party_flag&lt;/code&gt; 1. &lt;code&gt;ccFellow::Main&lt;/code&gt; calls Brains only with an AI and without
&lt;code&gt;ghoFlag&lt;/code&gt;. A new order waits at &lt;code&gt;chatCmdFlag&lt;/code&gt; -2 while &lt;code&gt;goBackFlag&lt;/code&gt; is set
and &lt;code&gt;distPL&lt;/code&gt; is over 1750.&lt;/li&gt;
&lt;li&gt;Its lines were dropped. &lt;code&gt;chat_say&lt;/code&gt; drops them under &lt;code&gt;manualSW&lt;/code&gt;,
&lt;code&gt;chat_gate&lt;/code&gt; needs &lt;code&gt;party_flag&lt;/code&gt; 1, and &lt;code&gt;chat_sender&lt;/code&gt; opens nothing while
the party reads as wiped out.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;manualSW&lt;/code&gt; fits the video best. Brains then runs &lt;code&gt;ManualControl&lt;/code&gt;: the
member stands still, never acts, and its lines are dropped. Its only
writers are the registry&#39;s boot bit 2 at an area&#39;s set-up, events&#39;
&lt;code&gt;ManualModeAI&lt;/code&gt;, and the tutorial. No death or revival path reaches any of
them.&lt;/p&gt;
&lt;p&gt;Also ruled out by reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dead&lt;/code&gt; 5 stuck: the revival count runs in &lt;code&gt;fade&lt;/code&gt; every frame, and the
member&#39;s SP would not regenerate.&lt;/li&gt;
&lt;li&gt;A skill status or act left set: death and revival both clear
&lt;code&gt;skillID&lt;/code&gt;/&lt;code&gt;skillStatus&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PAUSE&lt;/code&gt;: no AI decision reads it.&lt;/li&gt;
&lt;li&gt;The message bus: entries are withdrawn only for &lt;code&gt;party_flag&lt;/code&gt; -2.
Down sends 0x1000c and up deletes 0x1000d. That is the game&#39;s own
pairing, copied as found.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ocarinaUseFlag&lt;/code&gt;: set only by order 19 and never cleared in the port,
which is worth a check of its own (it blocks messages and targets).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spcBattleCondition&lt;/code&gt;: 3 lasts one frame.&lt;/li&gt;
&lt;li&gt;The UI&#39;s handles map to the right scene index.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simulated (probes kept out of the tree, the diff in the scratch area),
INF, story 19&#39;s field:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elk killed and revived nine times, at the down act, lying and as a
ghost, then ordered: he answered and healed each time.&lt;/li&gt;
&lt;li&gt;Killed mid-cast, then revived and ordered.&lt;/li&gt;
&lt;li&gt;A 40000-frame fight against two Water Witches with 56 deaths. Every
order was taken. One was not carried out, for want of SP.&lt;/li&gt;
&lt;li&gt;Seven Water Witches with Kite and Elk alone, random revive delays and
random orders. All taken.&lt;/li&gt;
&lt;li&gt;A monitor over eight seeds of 12000 frames, about 150 deaths, waves of
3-7 witches, random orders 0-18. It watched, while Elk was alive:
&lt;code&gt;manualSW&lt;/code&gt;, &lt;code&gt;party_flag&lt;/code&gt; other than 1, an order parked at -2 over 120
frames, hold over 600, &lt;code&gt;skillStatus&lt;/code&gt; over 900, an act of 7 or more over
900, no movement over 3000. Nothing fired.&lt;/li&gt;
&lt;/ul&gt;
</content>
</entry>
<entry>
<title>&quot;A spell holds its targets for its whole run: the effects&#39; copy of holdFlag was never set&quot;</title>
<id>/348-a-spell-holds-its-targets-for-its-whole-run-the-effects</id>
<link rel="alternate" href="/348-a-spell-holds-its-targets-for-its-whole-run-the-effects"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="battle"/>
<category term="render"/>
<summary>The user&#39;s video (work/video_of_bugs/20261002-0643-11.2822681.mp4, B3 of a dungeon, a Water Witch): its ice spell lands on Kite and he runs on under it.</summary>
<content type="html">&lt;p&gt;The user&#39;s video (&lt;code&gt;work/video_of_bugs/20261002-0643-11.2822681.mp4&lt;/code&gt;, B3 of
a dungeon, a Water Witch): its ice spell lands on Kite and he runs on under
it. In the game a spell or art used against a character holds it where it
stands while the skill runs. &lt;code&gt;_ccSkillRequest&lt;/code&gt; (INF gcmn 0x00572860) sets
the new &lt;code&gt;ccSkill&lt;/code&gt;&#39;s &lt;code&gt;holdFlag&lt;/code&gt; (+0x0d bit 0) for anything but the normal
attacks (sids below 6), buffs, debuffs and heals. Each frame
&lt;code&gt;ccSkill::Main&lt;/code&gt; (0x005731d0) then runs &lt;code&gt;ccSkillHold&lt;/code&gt; (0x005752b0), which
gives the targets &lt;code&gt;EntryAffect(5)&lt;/code&gt;, condition &lt;code&gt;hold&lt;/code&gt;. Kite&#39;s
&lt;code&gt;ControlMove&lt;/code&gt;, &lt;code&gt;ccFellow&lt;/code&gt;&#39;s and the enemies&#39; frames all stop on it. The
battle crate had all of this.&lt;/p&gt;
&lt;p&gt;An attack spell&#39;s run lives twice in the port: the battle crate&#39;s
&lt;code&gt;SkillRun&lt;/code&gt; and the effects crate&#39;s &lt;code&gt;Spell&lt;/code&gt;, the copy its element system
(Fall, Tornado, Convergence, Upheaval, Summons) runs on. The systems clear
&lt;code&gt;holdFlag&lt;/code&gt; at their own ends. So the world takes &lt;code&gt;hold&lt;/code&gt; back from the
effects&#39; copy after each system frame (&lt;code&gt;combat/mod.rs&lt;/code&gt;, &lt;code&gt;r.hold = out.hold&lt;/code&gt;). But the copy was made by &lt;code&gt;Spell::new&lt;/code&gt; (&lt;code&gt;hold&lt;/code&gt; false) and
&lt;code&gt;Spell::sync&lt;/code&gt; did not carry the flag. The first system frame handed back
false, and the hold lasted one frame. A probe showed it: Kite&#39;s &lt;code&gt;hold&lt;/code&gt;
condition 1 on frame 0, then 0, with the run&#39;s flag false from frame 1.
Arts were not affected: they run no system. A probe of Kite&#39;s art 6 on a
Mimic held it for its whole run until the art&#39;s note 6.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SpellRun&lt;/code&gt; now carries &lt;code&gt;hold&lt;/code&gt;, and &lt;code&gt;Spell::sync&lt;/code&gt; sets it before the system
runs. With the fix, a foe&#39;s Convergence (213) on Kite holds him for 48
frames, until the system lets go.&lt;/p&gt;
&lt;p&gt;Tests (&lt;code&gt;session/tests/spell_hold.rs&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a_foes_spell_holds_kite&lt;/code&gt;: Kite runs with the stick held, then is held
still under the spell for its whole hold, then runs again.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kites_spell_and_art_hold_the_foe&lt;/code&gt;: the foe&#39;s &lt;code&gt;hold&lt;/code&gt; is 1 every frame
of Kite&#39;s spell and of his art.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both fail with the flag left out of the sync (&quot;held only 0 frames&quot;).&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Ryu Books IV to VIII: their pages, lists and keys, each run against the game&quot;</title>
<id>/347-ryu-books-iv-to-viii-their-pages-lists-and-keys-each-run</id>
<link rel="alternate" href="/347-ryu-books-iv-to-viii-their-pages-lists-and-keys-each-run"/>
<updated>2026-10-02T00:00:00Z</updated>
<category term="ui"/>
<summary>[[317]] ported Books I-III and left IV-VIII&#39;s pages: their DispNN, CheckItemGetNN, and PadControl04, 05 and 08.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/317-ryu-books-begun-a-voice-row-outside-its-table-condition/&quot; title=&quot;Ryu Books begun, a voice row outside its table, condition effects on the flute and on a foe struck with the party gone&quot;&gt;317&lt;/a&gt; ported Books I-III and left IV-VIII&#39;s pages: their &lt;code&gt;DispNN&lt;/code&gt;,
&lt;code&gt;CheckItemGetNN&lt;/code&gt;, and &lt;code&gt;PadControl04&lt;/code&gt;, &lt;code&gt;05&lt;/code&gt; and &lt;code&gt;08&lt;/code&gt;. The rewards
(&lt;code&gt;GetBookNNItem&lt;/code&gt;) were already in. A player opened Book IV (issue #23,
and the user&#39;s own run): an empty window, and the game stuck.
&lt;code&gt;BOOK::PadControl&lt;/code&gt; closes the book on cancel only once &lt;code&gt;wait&lt;/code&gt; is 0, and
&lt;code&gt;wait&lt;/code&gt; (15 at the start) is counted down only by the page&#39;s own keys,
which IV-VIII did not have.&lt;/p&gt;
&lt;p&gt;All five are now ported (INF gcmn, the reference in
&lt;a href=&quot;/docs/engine/field-ui/#the-ryu-books-book-gcmn-bookcpp&quot;&gt;field-ui.md&lt;/a&gt;).
&lt;code&gt;tools/test_fieldui_rs.py&lt;/code&gt; gains &lt;code&gt;test_book_4&lt;/code&gt; to &lt;code&gt;test_book_8&lt;/code&gt;. Each runs
the game&#39;s own &lt;code&gt;ccThBook&lt;/code&gt; in eemu beside the port, frame by frame, over
every sprite packet, kanji row, message state and sound. Every one
passes, along with the harness&#39;s other 60 tests. A one-pixel change to
Book VI&#39;s count column fails its test at once.&lt;/p&gt;
&lt;p&gt;What the code showed, where it was not what one would guess:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Book IV&lt;/strong&gt; (&lt;code&gt;Disp04&lt;/code&gt; 0x00415790): the list&#39;s search for the next enemy
slain runs to 304, one past the 303 rows. Row 303&#39;s kill count is then
&lt;code&gt;BOOK.checkValue[2]&lt;/code&gt;, which &lt;code&gt;Draw&lt;/code&gt; zeroes each frame, so it is never
taken. The sub-window shows the enemy&#39;s &lt;code&gt;enemyTbl&lt;/code&gt; row: &lt;code&gt;base.level&lt;/code&gt;,
&lt;code&gt;max_hp&lt;/code&gt; (10000 or more spelled out of &lt;code&gt;cheatHpStr&lt;/code&gt;), &lt;code&gt;max_sp&lt;/code&gt;, the
spells &lt;code&gt;mag0&lt;/code&gt;/&lt;code&gt;mag1&lt;/code&gt; (&lt;code&gt;skill&lt;/code&gt; +0x70, +0x74), the three &lt;code&gt;item&lt;/code&gt;s, and a
weakness from the protect points (&lt;code&gt;max_pp&lt;/code&gt; -1, else &lt;code&gt;p_def_pp&lt;/code&gt; against
&lt;code&gt;m_def_pp&lt;/code&gt;). Its help line names where one was last slain:
&lt;code&gt;enemyKillArea&lt;/code&gt; (+0x69f0, server and three words) between &lt;code&gt;#B&lt;/code&gt; and &lt;code&gt;#W&lt;/code&gt;.
The sub-window&#39;s column starts 40 higher than the list, which a first
try got wrong and the harness caught.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Book V&lt;/strong&gt; (&lt;code&gt;Disp05&lt;/code&gt;, &lt;code&gt;PadControl05&lt;/code&gt;): off the list the cursor moves with
the &lt;em&gt;held&lt;/em&gt; buttons (&lt;code&gt;ccSys&lt;/code&gt; +0x2cc), not the pushed ones, &lt;code&gt;keyWait&lt;/code&gt;
letting a hold through every third frame. The harness&#39;s first scenario
pushed DOWN and the game did not move; the emulator&#39;s &lt;code&gt;BOOK&lt;/code&gt; fields
showed every gate open, which led to the read of +0x2cc. Book V&#39;s help
sets &lt;code&gt;BookHelp50&lt;/code&gt;&#39;s second and third strings to NULL in place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Book VI, VII&lt;/strong&gt; (&lt;code&gt;Disp06&lt;/code&gt;, &lt;code&gt;Disp07&lt;/code&gt;): counter rows like Book II&#39;s, the
count at a fixed 250 or 182. VII&#39;s first two rows say &quot;encountered&quot;
(&lt;code&gt;bookEncountMsg&lt;/code&gt;), and its third row stands a line lower.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Book VIII&lt;/strong&gt; (&lt;code&gt;Disp08&lt;/code&gt;, &lt;code&gt;PadControl08&lt;/code&gt;): a menu with the Grunties
(&lt;code&gt;ccGetNpcParam(145 + i)&lt;/code&gt;) and their food (&lt;code&gt;gimmickTbl[22 + i]&lt;/code&gt;); each
page resizes the window by writing the global &lt;code&gt;BookOfs&lt;/code&gt;. In the food
list&#39;s help the count&#39;s &lt;code&gt;sprintf&lt;/code&gt; is overwritten by the &lt;code&gt;strcpy&lt;/code&gt; after
it, so the line is the food&#39;s name and &lt;code&gt;bookPuchiFood&lt;/code&gt;. On the food page
the total moves down by repeat and up by the held buttons, and the
list&#39;s &quot;none above&quot; sets &lt;code&gt;keyWait&lt;/code&gt; where IV&#39;s and V&#39;s do not.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;the_later_ryu_books_open_and_close&lt;/code&gt; opens each of IV-VIII in Mac Anu
through PERSONAL&#39;s Key Items and closes it with cancel.&lt;/p&gt;
</content>
</entry>
<entry>
<title>A lone slot file imports with its index record</title>
<id>/346-a-lone-slot-file-imports-with-its-index-record</id>
<link rel="alternate" href="/346-a-lone-slot-file-imports-with-its-index-record"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="save"/>
<category term="tooling"/>
<summary>The user&#39;s repro save for [[345]] is one slot file, work/savecards/repro/dhdata12 (0x8530 bytes, INF).</summary>
<content type="html">&lt;p&gt;The user&#39;s repro save for &lt;a href=&quot;/345-three-player-reports-a-node-keeps-its-own-transparency-the/&quot; title=&quot;&amp;#39;Three player reports: a node keeps its own transparency, the book&amp;#39;s cover every frame, one area a gate trip&amp;#39;&quot;&gt;345&lt;/a&gt; is one slot file,
&lt;code&gt;work/savecards/repro/dhdata12&lt;/code&gt; (0x8530 bytes, INF). Put on the card, the
load screen showed the slot unused. That is the game&#39;s rule. A slot is used
only through its record in the save directory&#39;s index file
(&lt;code&gt;BASLUS-20267DOTHACK/BASLUS-20267DOTHACK&lt;/code&gt;, twelve 28-byte
&lt;code&gt;ccSaveDataInfo&lt;/code&gt;s; &lt;a href=&quot;/docs/formats/save/&quot;&gt;the save page&lt;/a&gt;): status 1, and
a sum the load checks. A slot file copied in without that record reads as
empty. &lt;code&gt;--import-card&lt;/code&gt; (&lt;a href=&quot;/338-saves-from-pcsx2-a-ps2-card-image-reader-the-import-and/&quot; title=&quot;&amp;#39;Saves from PCSX2: a PS2 card image reader, the import, and Infection slots of 0x8d84&amp;#39;&quot;&gt;338&lt;/a&gt;) took only whole save directories, so it
refused the file.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;card::import&lt;/code&gt; now also takes a lone slot file (&lt;code&gt;dhdata01&lt;/code&gt;-&lt;code&gt;dhdata12&lt;/code&gt;, or a
folder holding only such files). &lt;code&gt;import_slots&lt;/code&gt; writes each into its
volume&#39;s directory and rebuilds its record as &lt;code&gt;ccSaveSys&lt;/code&gt;&#39;s save does
(&lt;code&gt;SaveSys::write&lt;/code&gt;, 0x00173cf8): status 1, &lt;code&gt;level&lt;/code&gt;, &lt;code&gt;clearFlag&lt;/code&gt;,
&lt;code&gt;parodyFlag&lt;/code&gt;, the name from &lt;code&gt;plName&lt;/code&gt;, the 16-bit sum of the file&#39;s bytes,
&lt;code&gt;playTime&lt;/code&gt;. A missing directory is first made as &lt;code&gt;make_dir&lt;/code&gt; makes it,
twelve zeroed slots and a zeroed index. An existing one is copied to the
&lt;code&gt;backup-&lt;/code&gt; folder first, and the other slots&#39; records are kept.&lt;/p&gt;
&lt;p&gt;The size picks the volume. 0x8530 is Infection&#39;s. 0x8d84 is any later
volume&#39;s, and also an Infection slot as a PCSX2 export can hold it
(&lt;a href=&quot;/338-saves-from-pcsx2-a-ps2-card-image-reader-the-import-and/&quot; title=&quot;&amp;#39;Saves from PCSX2: a PS2 card image reader, the import, and Infection slots of 0x8d84&amp;#39;&quot;&gt;338&lt;/a&gt;), so it takes the volume of the running disc (the console) or
&lt;code&gt;--volume&lt;/code&gt; (the command line) and is refused without one.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a_lone_slot_imports_with_its_record&lt;/code&gt; checks the record (&lt;code&gt;check_right_info&lt;/code&gt;
passes, name, sum), the other slots left empty, the slot read back, the
0x8d84 case with and without a volume, and a size of no save. The user&#39;s
&lt;code&gt;dhdata12&lt;/code&gt; imported into an empty card: slot 12 used, Kite, level 11,
clear 0, sum 0xd4f7 (the file&#39;s own), 6.28 hours.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Three player reports: a node keeps its own transparency, the book&#39;s cover every frame, one area a gate trip&quot;</title>
<id>/345-three-player-reports-a-node-keeps-its-own-transparency-the</id>
<link rel="alternate" href="/345-three-player-reports-a-node-keeps-its-own-transparency-the"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="render"/>
<category term="ui"/>
<category term="save"/>
<summary>Three issues from the same player, all INF.</summary>
<content type="html">&lt;p&gt;Three issues from the same player, all INF.&lt;/p&gt;
&lt;h2 id=&quot;20-the-gott-statue-vanished-once-its-box-was-looted&quot;&gt;#20: the Gott statue vanished once its box was looted&lt;/h2&gt;
&lt;p&gt;The screenshot shows a dungeon&#39;s B3 with the statue gone after &quot;You now have
Rainbow Card!&quot;. &lt;a href=&quot;/319-the-gott-statue-s-ring-stayed-a-character-s-draw-ignored/&quot; title=&quot;&amp;#39;The Gott statue&amp;#39;s ring stayed: a character&amp;#39;s draw ignored its objects&amp;#39; animated transparency&amp;#39;&quot;&gt;319&lt;/a&gt; made &lt;code&gt;Body::draw&lt;/code&gt; multiply each node&#39;s animated
transparency by its parents&#39;, as it read &lt;code&gt;ccCoord::_GetTransparency&lt;/code&gt; (main
0x00138490). That reading was wrong.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_GetTransparency&lt;/code&gt; walks to a node&#39;s parent (&lt;code&gt;+0x80&lt;/code&gt;) only while the node&#39;s
flag bit 0 (&lt;code&gt;+0x8c&lt;/code&gt;) is set. &lt;code&gt;SetAnmCtrlWork&lt;/code&gt; (0x001507c8) writes the
animated value to the node&#39;s local (&lt;code&gt;+0x88&lt;/code&gt;). With bit 0 set it marks the
node for that walk (bit 2); without it, it writes the value straight to the
world transparency (&lt;code&gt;+0x84&lt;/code&gt;). Bit 0 comes from the object&#39;s Obj2 chunk
(&lt;code&gt;flags&lt;/code&gt;, &quot;transparency inherits the parent&#39;s&quot;): &lt;code&gt;Decode_Obj&lt;/code&gt; (0x0014cb30)
copies it to &lt;code&gt;ccObjChunk+0xc&lt;/code&gt;, and &lt;code&gt;ccClump::Init&lt;/code&gt; (0x0013c718) tests it. A
node with no Obj2 chunk has flags 0. Every Obj2 chunk in the four volumes&#39;
&lt;code&gt;DATA.BIN&lt;/code&gt; was read: about 219,000 to 221,000 a volume, and none has bit 0.
So a node&#39;s transparency is its own animated value and nothing else.
&lt;code&gt;ccClump::Init&lt;/code&gt; does put the root under the clump itself (0x0013c944), but
with bit 0 clear that link is never walked for transparency.&lt;/p&gt;
&lt;p&gt;The seven statues (&lt;code&gt;xgs?bod1&lt;/code&gt;) show what the product broke. In the d, f, l
and w statues, the fall &lt;code&gt;ANM_xgs?dwn0&lt;/code&gt; and &lt;code&gt;ANM_xgs?nut1&lt;/code&gt; key the root
&lt;code&gt;OBJ_trall&lt;/code&gt; to 0 from frame 0. Multiplied down, that hid the whole statue
the moment it began to fall. In a, e and t the root stays at 1, which is why
&lt;a href=&quot;/319-the-gott-statue-s-ring-stayed-a-character-s-draw-ignored/&quot; title=&quot;&amp;#39;The Gott statue&amp;#39;s ring stayed: a character&amp;#39;s draw ignored its objects&amp;#39; animated transparency&amp;#39;&quot;&gt;319&lt;/a&gt;&#39;s tutorial statue (a) looked right. &lt;code&gt;Body::node_alphas&lt;/code&gt; now returns
each node&#39;s own value. &lt;code&gt;a_fallen_gott_statue_stays_drawn&lt;/code&gt; poses all seven at
the fall&#39;s last frame and at &lt;code&gt;nut1&lt;/code&gt;: &lt;code&gt;OBJ_o_god_m0_&lt;/code&gt; at 1, the ring at 0.
The enemies&#39; models share the function.&lt;/p&gt;
&lt;h2 id=&quot;21-the-ryu-book-s-cover-strobed&quot;&gt;#21: the Ryu Book&#39;s cover strobed&lt;/h2&gt;
&lt;p&gt;The player&#39;s video (&lt;code&gt;work/video_of_bugs&lt;/code&gt;, not in the repository) shows the
cover stream every other frame, with black between. Worklog 336 moved the
town&#39;s menu task after the event task. The menu task steps the book&#39;s
stream (&lt;code&gt;ccThBook&lt;/code&gt;&#39;s &lt;code&gt;ccThExecuteStream&lt;/code&gt;) and leaves its frame in
&lt;code&gt;st.stream.frame&lt;/code&gt;. But the step reads that slot only before the menu task
runs, so the frame showed one game frame late. On that later frame the step
returned at once, skipped the menu task, and so stepped no stream; the next
frame showed the town&#39;s own picture, black in the video. The stream thus advanced every
other frame. Half the cancel presses landed on frames that never stepped
it, which is the &quot;sometimes the button doesn&#39;t register&quot; of the report.
The town now shows the book&#39;s frame as soon as the menu task has made it.
&lt;code&gt;read_a_ryu_book&lt;/code&gt; (behind &lt;code&gt;a_ryu_book_opens_in_town&lt;/code&gt;) checks that the
cover&#39;s frame moves on every frame it plays. Without the fix, 360 frames
stood still.&lt;/p&gt;
&lt;h2 id=&quot;22-number-of-areas-visited-141&quot;&gt;#22: &quot;Number of areas visited&quot; 141&lt;/h2&gt;
&lt;p&gt;The player&#39;s save (&lt;code&gt;work/savecards/repro/dhdata12&lt;/code&gt;) holds 141 at +0x6862,
the counter Book I shows (&lt;code&gt;BOOK::Disp01&lt;/code&gt;, gcmn 0x00410640). A scan of the
code for stores to +0x6862 finds five pairs in GCMN.PRG (a store and its
clamp to 10000, at file offsets 0x15d5ec, 0x15e9ec, 0x162708, 0x163770 and
0x16644c; the first is &lt;code&gt;GtRandomMenu&lt;/code&gt;&#39;s, gcmn 0x0055dde4). &lt;code&gt;MainProccess&lt;/code&gt; copies it on load and
save, and &lt;code&gt;ccSaveData::Init&lt;/code&gt; zeroes it. The port has the same five.&lt;/p&gt;
&lt;p&gt;But a gate&#39;s leave state (Random&#39;s 5, New Keyword&#39;s, the lists&#39; 7) stays put
once &lt;code&gt;leave&lt;/code&gt; returns true. Every frame the menu runs after that, it counts
again and asks &lt;code&gt;GoToArea&lt;/code&gt; again. In the game the menu task sleeps in the
&lt;code&gt;ChangeRequest&lt;/code&gt; its &lt;code&gt;SetGenerateCode&lt;/code&gt; makes. The port&#39;s session runs the
town for the 10 frames of the leave&#39;s fade, and the town ran its menu task
through them. &lt;code&gt;a_random_warp_counts_one_area&lt;/code&gt; takes a new game&#39;s Random warp
from Mac Anu: 10 counted without the fix, 1 with it. The town now skips its
menu task once it has asked to leave (&lt;code&gt;leaving&lt;/code&gt;). The 141 is about 14 trips.
A save already counted stays as it is.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;sound 8 0 256: five uses, and port 0 always takes the SE option&quot;</title>
<id>/344-sound-8-0-256-five-uses-and-port-0-always-takes-the-se</id>
<link rel="alternate" href="/344-sound-8-0-256-five-uses-and-port-0-always-takes-the-se"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="audio"/>
<category term="script"/>
<summary>[[176]] left open &quot;whether the SE port&#39;s volume is ever below full when sound 8 0 256 runs&quot;.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/176-every-host-call-the-scripts-reach-and-the-sound-instruction/&quot; title=&quot;&amp;#39;Every host call the scripts reach, and the sound instruction&amp;#39;s eleven cases&amp;#39;&quot;&gt;176&lt;/a&gt; left open &quot;whether the SE port&#39;s volume is ever below full when
&lt;code&gt;sound 8 0 256&lt;/code&gt; runs&quot;. The answer does not change what the instruction
does. &lt;code&gt;ccPortVolSet(port, vol)&lt;/code&gt; (INF main 0x0017ad70) gives port 0 &lt;code&gt;seVol&lt;/code&gt;
whatever &lt;code&gt;vol&lt;/code&gt; is, so &lt;code&gt;sound 8 0 256&lt;/code&gt; puts the SE port back to the
player&#39;s SE option from any value. The driver&#39;s &lt;code&gt;port_vol_set&lt;/code&gt;
(&lt;code&gt;crates/piney-audio/src/driver.rs&lt;/code&gt;) does the same (&lt;code&gt;0 =&amp;gt; se_vol&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The uses, from &lt;code&gt;tools/evscript.py dump&lt;/code&gt; of each volume&#39;s executable:&lt;/p&gt;
&lt;div class=&quot;table-scroll&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;volume&lt;/th&gt;&lt;th&gt;tables with &lt;code&gt;sound 8&lt;/code&gt;&lt;/th&gt;&lt;th&gt;all&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;INF&lt;/td&gt;&lt;td&gt;M110, M131, M216, M319, M414&lt;/td&gt;&lt;td&gt;&lt;code&gt;8 0 256&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;MUT&lt;/td&gt;&lt;td&gt;the same five&lt;/td&gt;&lt;td&gt;&lt;code&gt;8 0 256&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;OUT&lt;/td&gt;&lt;td&gt;the same five, and S410&lt;/td&gt;&lt;td&gt;&lt;code&gt;8 0 256&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;QUA&lt;/td&gt;&lt;td&gt;the same five, and S410&lt;/td&gt;&lt;td&gt;&lt;code&gt;8 0 256&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;M110&#39;s is the first instruction of its block 0. M131, M216, M319 and M414
each run it right after their ending streams and &lt;code&gt;overlay 1&lt;/code&gt;. S410 (Mia&#39;s
event 360) runs it after stream 138 and &lt;code&gt;call_on 1&lt;/code&gt;. All but M110&#39;s
follow a stream. What lowers port 0 before it would be one of
&lt;code&gt;ccPortVolSet&lt;/code&gt;&#39;s 47 calls (&lt;code&gt;ccSndChangeOption&lt;/code&gt;,
&lt;code&gt;ccSqPlay&lt;/code&gt;, &lt;code&gt;ccSqPlayVol&lt;/code&gt;, &lt;code&gt;ccSound::ccFade&lt;/code&gt;, &lt;code&gt;ccSceneFade&lt;/code&gt;,
&lt;code&gt;ccSndSQLoad&lt;/code&gt;, &lt;code&gt;ccSndChangeData&lt;/code&gt;). None of those calls was followed to
port 0 here, since each lands on &lt;code&gt;seVol&lt;/code&gt; the same way.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sound.md&lt;/code&gt; now lists the six places.&lt;/p&gt;
</content>
</entry>
<entry>
<title>A lake&#39;s dungeons: every entry goes with the scene, and entryFlag places them again</title>
<id>/343-a-lake-s-dungeons-every-entry-goes-with-the-scene-and</id>
<link rel="alternate" href="/343-a-lake-s-dungeons-every-entry-goes-with-the-scene-and"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="world"/>
<category term="battle"/>
<summary>[[191]] left two questions about a lake&#39;s way between its dungeons.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/191-a-lake-s-two-dungeons-each-kept-by-its-number-lastroom-and/&quot; title=&quot;&amp;#39;A lake&amp;#39;s two dungeons: each kept by its number, lastRoom, and the way back up (GoField for field type 4)&amp;#39;&quot;&gt;191&lt;/a&gt; left two questions about a lake&#39;s way between its dungeons. The
first: &lt;code&gt;come_back&lt;/code&gt; builds the room&#39;s doors open, where the game asks
&lt;code&gt;ccCheckActiveObject&lt;/code&gt;. The second: what reads &lt;code&gt;WORLD_MAN+0x58[n]&lt;/code&gt;, which
&lt;code&gt;Enter&lt;/code&gt; and &lt;code&gt;GoField&lt;/code&gt; clear. Both answers came out of &lt;code&gt;ccThEntryCtrlDelete&lt;/code&gt;
and &lt;code&gt;WORLD_MAN::EntryGimmick&lt;/code&gt;. The port had the lake&#39;s portals, boxes and
idols wrong on the way back: carried over where the game deletes them,
and never placed again.&lt;/p&gt;
&lt;h2 id=&quot;0x58-n-is-entrygimmick-s-entryflag&quot;&gt;+0x58[n] is EntryGimmick&#39;s entryFlag&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WORLD_MAN::EntryGimmick&lt;/code&gt; (INF main 0x001a1f20), which &lt;code&gt;ccThEntryCtrl&lt;/code&gt;&#39;s
set-up calls:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WORLD_MAN.flag 1 (a field):   game.field 0: entryFlag[0] (+0x54) 0 -&amp;gt;
                                  WORLD::SetFood, SetMagicCircle, SetSpecialObj
                              else: +0x124 0 and +0x54 0 -&amp;gt; the same
WORLD_MAN.flag 2 (a dungeon): entryFlag[1 + game.dungeon] (+0x58 + 4 n) 0 -&amp;gt;
                                  DUNGEON::SetItemBox,
                                  SetMagicCircle (not with volumeNum 2 and game.field 27),
                                  SetIDOL; then the flag = 1
                              EntryBreakObject every time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A scan of every &lt;code&gt;WORLD_MAN&lt;/code&gt; method and every main function that loads
&lt;code&gt;worldman&lt;/code&gt; finds three other stores to +0x58, all clears of the dungeon
being left: &lt;code&gt;Enter&lt;/code&gt; on a lake&#39;s stairs down (0x0019e240) and on dungeon
1&#39;s floor-0 stairs up (0x0019e104), and &lt;code&gt;GoField&lt;/code&gt; from dungeon 1
(0x0019e45c). The &lt;code&gt;sw $zero, 0x58&lt;/code&gt; in &lt;code&gt;ccSetupGameCtrl&lt;/code&gt; (0x00168c3c) is
&lt;code&gt;game&lt;/code&gt;&#39;s, &lt;code&gt;inBattle&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;a-new-scene-keeps-nothing&quot;&gt;A new scene keeps nothing&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ccThEntryCtrlDelete&lt;/code&gt; (gcmn 0x00431d10) keeps the circles and the objects
not owned by a circle or a corpse in &lt;code&gt;g_entryList&lt;/code&gt; only when
&lt;code&gt;ccGame::CheckSceneReplace()&lt;/code&gt; (main 0x00167580) is false. That function
returns 1 when any of &lt;code&gt;CompArea&lt;/code&gt;, &lt;code&gt;CompTown&lt;/code&gt;, &lt;code&gt;CompField&lt;/code&gt;, &lt;code&gt;CompDungeon&lt;/code&gt;
finds the value differs from its previous one (+0x14/+0x18,
+0x20/+0x38, +0x24/+0x3c, +0x28/+0x40). Otherwise it deletes every
object. A room of the same dungeon is not a new scene. A lake&#39;s other
dungeon is, since &lt;code&gt;game.dungeon&lt;/code&gt; changes. So at that change the lists are
emptied, and on the way back &lt;code&gt;EntryGimmick&lt;/code&gt; sees the cleared flag and
places the lake&#39;s boxes, portals and idols again.&lt;/p&gt;
&lt;p&gt;That also answers the doors. &lt;code&gt;GO(2)&lt;/code&gt;&#39;s &lt;code&gt;SetRoom(0, lastRoom)&lt;/code&gt; runs before
&lt;code&gt;ccThEntryCtrl&lt;/code&gt; starts, with the lists empty, so &lt;code&gt;SetDoor&lt;/code&gt;&#39;s
&lt;code&gt;ccCheckActiveObject(0, lastRoom)&lt;/code&gt; (gcmn 0x0042e010: 0 when an entry of
either list is at that floor and block) answers 1 and builds them open, as
&lt;code&gt;come_back&lt;/code&gt; does. &lt;code&gt;MoveDoor&lt;/code&gt; then shuts them each frame
&lt;code&gt;ccCheckActiveObject()&lt;/code&gt; finds an entry switched on in the room.&lt;/p&gt;
&lt;h2 id=&quot;the-port&quot;&gt;The port&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;FieldWorld::keep_entries&lt;/code&gt; called &lt;code&gt;EntryCtrl::keep(cx, true)&lt;/code&gt; (the &quot;left
for a moment&quot; case) on every way out of a dungeon. It now passes
&lt;code&gt;!Scene::changed()&lt;/code&gt;. That function is &lt;code&gt;ccSetupGameCtrl&lt;/code&gt;&#39;s test and the
same four comparisons. &lt;code&gt;DungeonArea::enter&lt;/code&gt; clears &lt;code&gt;gimmicks_placed&lt;/code&gt; on the
lake&#39;s two stairs, and &lt;code&gt;FieldWorld::go_field&lt;/code&gt; clears it in dungeon 1.&lt;/p&gt;
&lt;p&gt;Before, coming back up the lake kept its entries from the first visit and
did not place them again. &lt;code&gt;a_lake_keeps_both_its_dungeons&lt;/code&gt; now plays each
visit of area 33 to &lt;code&gt;Play(3)&lt;/code&gt;. With the flag cleared as &lt;code&gt;Enter&lt;/code&gt; clears it,
the lake holds 3 portals and 10 gimmicks on both visits. With the clear
but without the replace rule, the second visit had 6 and 20.
&lt;code&gt;a_type_4_area_goes_down_into_its_second_dungeon_and_back&lt;/code&gt; checks the two
stairs&#39; clears.&lt;/p&gt;
</content>
</entry>
<entry>
<title>The opening stream&#39;s flashes over the stream: the cancel&#39;s and the end&#39;s</title>
<id>/342-the-opening-stream-s-flashes-over-the-stream-the-cancel-s</id>
<link rel="alternate" href="/342-the-opening-stream-s-flashes-over-the-stream-the-cancel-s"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="ui"/>
<category term="video"/>
<summary>The title&#39;s intro stream has two white flashes, and the port got both wrong.</summary>
<content type="html">&lt;p&gt;The title&#39;s intro stream has two white flashes, and the port got both
wrong. A cancel push&#39;s flash was not modelled. The end flash was entered
only once the stream had finished, then advanced 20 frames at once. So the
stream&#39;s last 20 frames never faded to white, and the menu came in partway
through the fade-out.&lt;/p&gt;
&lt;h2 id=&quot;the-game&quot;&gt;The game&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;PlayOpeningStream&lt;/code&gt; (INF demo.prg:0x004066b0) starts &lt;code&gt;ccThExecuteStream&lt;/code&gt;
at priority 33 with the stream number in its param +0x14. The count is
410 for volume 1&#39;s plain stream and 450 with &lt;code&gt;m_ParoFLG&lt;/code&gt;. INF&#39;s switch on
&lt;code&gt;m_NowVol&lt;/code&gt; gives volumes 2-4 450 and 490. It waits for &lt;code&gt;ccGetStreamAdrs()&lt;/code&gt; and then the stream&#39;s
+0x17e bit 3. Then, each frame (0x00406838), before the stream&#39;s own task
(the same priority, started later):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pad push &amp;amp; saveData.assignPADcancel (+0x8410) and s1   EntryFlash(50, 0x80ffffff), s1 = 0
the stream task&#39;s +0x14 == -1                           return
ccGetStreamFrame() == count - 20 and s1                 EntryFlash3(20, 20, 5, 0x80ffffff), s1 = 0
ccBreathThread(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The flashes go to &lt;code&gt;scFadeDef&lt;/code&gt; over (0, 0, 512, 384). A cancel does not end
the stream; the stream&#39;s own skip rule does that. It only flashes, and it
spends &lt;code&gt;s1&lt;/code&gt;, so the end flash never follows it. MUT (demo.prg 0x00419720),
OUT (0x004153e0) and QUA (0x00307d40) run the same loop. OUT and QUA keep
the count and the flag in swapped registers and load the rectangle&#39;s 512.0
and 384.0 from constants (OUT 0x0041db58, 0x0041db50).&lt;/p&gt;
&lt;h2 id=&quot;the-port&quot;&gt;The port&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Demo&lt;/code&gt; keeps the count from its &lt;code&gt;Request::Stream&lt;/code&gt; as &lt;code&gt;stream_flash&lt;/code&gt;, the
port&#39;s &lt;code&gt;s1&lt;/code&gt;. While &lt;code&gt;PlayOpeningStream&lt;/code&gt; holds the title, the session calls
&lt;code&gt;Demo::stream_tick(pad, frame)&lt;/code&gt; before each of the stream&#39;s steps, from its
first step on, with &lt;code&gt;StreamPlayer::frame()&lt;/code&gt;, the frame the last step drew.
After the step, &lt;code&gt;Demo::stream_fade&lt;/code&gt; draws &lt;code&gt;scFadeDef&lt;/code&gt; over the stream&#39;s
picture. &lt;code&gt;Phase::Stream&lt;/code&gt; now just goes on to the title. The demo&#39;s own
frame keeps sending the fade, so the flash runs on over the menu.
&lt;code&gt;advance_one&lt;/code&gt; is gone.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;the_opening_stream_flashes&lt;/code&gt;: played through, the flash starts on the step
that draws the stream&#39;s frame 391 (the check saw 390). It lies over the
stream&#39;s last 20 frames (391-410). The stream then shows frame 0 twice as
it ends, and the menu comes out of the white, 47 frames lit in all. With
cancel pushed on the 100th step, white starts on that step and lasts 50
frames, the stream ends two steps later, and no second flash comes.&lt;/p&gt;
</content>
</entry>
<entry>
<title>The map button behind ccThGameCtrl&#39;s waits, and no map in a fight</title>
<id>/341-the-map-button-behind-ccthgamectrl-s-waits-and-no-map-in-a</id>
<link rel="alternate" href="/341-the-map-button-behind-ccthgamectrl-s-waits-and-no-map-in-a"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="ui"/>
<category term="world"/>
<summary>[[77]] left two map gaps open.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/77-the-minimap-mac-anu-s-plan-the-field-s-and-the-dungeon-s/&quot; title=&quot;The minimap: Mac Anu&amp;#39;s plan, the field&amp;#39;s and the dungeon&amp;#39;s maps, SELECT and event 3&amp;#39;s map lesson&quot;&gt;77&lt;/a&gt; left two map gaps open. The map button was taken whatever held
&lt;code&gt;ccThGameCtrl&lt;/code&gt;&#39;s other buttons. The field&#39;s and dungeon&#39;s map was drawn
through a battle. Both now follow the game.&lt;/p&gt;
&lt;h2 id=&quot;where-the-map-button-s-test-is&quot;&gt;Where the map button&#39;s test is&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ccThGameCtrl&lt;/code&gt;&#39;s loop (INF gcmn 0x005178f0) runs, each frame, in this order:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Breath(1)
checkPartyAnnihilation() or compulsionGameOver   the game over: the task
                                                 never comes back to its loop
ccCtrlRecoveryReq, ccSortCmnd x3
ccMenu+6 != 66: SetInBattle(...) or inBattle = 0
ccCheckGtHackAnm()   (0x00517d58)               non-zero: back to Breath
menuClrWait &amp;gt; 0      (0x00517d68)               menuClrWait - 1, back to Breath
the map button       (0x00517d84)               assignPADmap and CheckOperate(13, 0)
ccPlayerMenuCheck ... the other buttons
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The port&#39;s &lt;code&gt;Targeting::frame&lt;/code&gt; (piney-world &lt;code&gt;talk.rs&lt;/code&gt;) already ran the game
over, &lt;code&gt;ghoFlag&lt;/code&gt; and &lt;code&gt;menuClrWait&lt;/code&gt; in this order for the other buttons. The
map button was &lt;code&gt;piney-game&lt;/code&gt;&#39;s, and it ran before the task with no gate. Now
&lt;code&gt;Targeting::map_test&lt;/code&gt; is set when a frame gets past &lt;code&gt;menuClrWait&lt;/code&gt;. Both
worlds clear it at each &lt;code&gt;step_game_ctrl&lt;/code&gt; and expose it as &lt;code&gt;map_test()&lt;/code&gt;. The
town (&lt;code&gt;world.rs&lt;/code&gt;) and the area (&lt;code&gt;area.rs&lt;/code&gt;) press the map button right after
&lt;code&gt;step_game_ctrl&lt;/code&gt;, only when it is set. The mode change still lands before
the menu task and the draw, as in the game. Nothing between the old place
and the new reads the mode.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;menu_clear_holds_the_map_button_two_frames&lt;/code&gt; checks the gate in a town&#39;s
&lt;code&gt;Targeting::step&lt;/code&gt;. A &lt;code&gt;menu_clear&lt;/code&gt; before the second frame holds the test for
two frames (&lt;code&gt;true, false, false, true, true&lt;/code&gt;). The task&#39;s first five frames,
which only sort, do not hold it.&lt;/p&gt;
&lt;h2 id=&quot;no-map-in-a-fight&quot;&gt;No map in a fight&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WORLD::Draw&lt;/code&gt; calls &lt;code&gt;DrawMiniMap&lt;/code&gt; only while &lt;code&gt;ccGame.inBattle&lt;/code&gt; (+0x58) is 0
(INF gcmn 0x005a9890-0x005a98a4). &lt;code&gt;DUNGEON::Draw&lt;/code&gt; calls &lt;code&gt;MakeMiniMap&lt;/code&gt; for
the room under the player every frame, and &lt;code&gt;DrawMap&lt;/code&gt; only while
&lt;code&gt;inBattle&lt;/code&gt; is 0 (0x005cef64-0x005cef78). MUT, OUT and QUA test &lt;code&gt;game&lt;/code&gt;
+0x58 before &lt;code&gt;DrawMiniMap&lt;/code&gt; the same way, and step &lt;code&gt;menuClrWait&lt;/code&gt; the same
way in &lt;code&gt;ccThGameCtrl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;map::area_frame&lt;/code&gt; takes &lt;code&gt;in_battle&lt;/code&gt;. In a field it draws nothing then. In a
dungeon it still enters the room (&lt;code&gt;MakeMiniMap&lt;/code&gt;) and draws nothing. Its
&lt;code&gt;last&lt;/code&gt; is emptied, so a sleeping frame after it draws nothing again, as no
packets were made. &lt;code&gt;the_map_is_away_in_a_fight&lt;/code&gt; walks to event 3&#39;s east
portal, fighting its goblins: 237 fight frames, no map in any of them, and
the map on the walk before. With &lt;code&gt;in_battle&lt;/code&gt; forced false the same run has
the map&#39;s packets in all 237. In a dungeon, &lt;code&gt;the_minimap_stays_across_floors&lt;/code&gt;
(area 23&#39;s floors down to the shrine) now leaves the fights out of its
per-room count and checks them apart: 1,424 fight frames, no map in any.
Before, it had counted the fight frames as frames the map must draw in.&lt;/p&gt;
</content>
</entry>
<entry>
<title>The frame-mode deflicker: read circuit 1 a line lower, merged at ALP 127</title>
<id>/340-the-frame-mode-deflicker-read-circuit-1-a-line-lower-merged</id>
<link rel="alternate" href="/340-the-frame-mode-deflicker-read-circuit-1-a-line-lower-merged"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="render"/>
<category term="engine"/>
<summary>While working out SetDisplayOffset&#39;s units ([[339]]), a second pair of DISPLAY values turned up at ccSystem +0xe10 / +0xe20.</summary>
<content type="html">&lt;p&gt;While working out &lt;code&gt;SetDisplayOffset&lt;/code&gt;&#39;s units (&lt;a href=&quot;/339-adjust-screen-moves-the-picture-setdisplayoffset-s-units/&quot; title=&quot;Adjust Screen moves the picture: SetDisplayOffset&amp;#39;s units, and the presenter&amp;#39;s shift&quot;&gt;339&lt;/a&gt;), a second pair of
DISPLAY values turned up at ccSystem +0xe10 / +0xe20. It is the game&#39;s
deflicker. In frame mode, the mode the game runs in outside the movies, the
GS shows the frame buffer through both read circuits, one a line lower,
merged half and half.&lt;/p&gt;
&lt;h2 id=&quot;the-game&quot;&gt;The game&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SetScreenModeMain&lt;/code&gt; (INF SLUS_202.67:0x0010ad40), when the field flag
(+0xbd6) is 0, after &lt;code&gt;sceGsSetDefDBuff&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x0010b4e8  both buffers&#39; PMODE (+0xbe0, +0xc08): bit 0 (EN1) set
0x0010b51c  their ALP byte (+0xbe1, +0xc09) = 127
0x0010b524  +0xe10 = buffer 0&#39;s DISPLAY (+0xbf8), +0xe20 = buffer 1&#39;s (+0xc20)
0x0010b534  each copy&#39;s DY + 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;libgraph&#39;s PMODE is 0x66 (&lt;code&gt;sceGsSetDefDispEnv&lt;/code&gt; 0x0010dea0): EN2, CRTMD 1,
MMOD 1 (the merge&#39;s alpha is ALP), AMOD 1, SLBG 0 (circuit 2 is the
background). Each frame &lt;code&gt;SwapDoubleBuffer&lt;/code&gt; (0x0010ac40) writes, in frame
mode, the shown buffer&#39;s DISPFB (+0xbf0 + 40 n) to DISPFB1 (0x12000070) and
its copy (+0xe10 + 16 n) to DISPLAY1 (0x12000080). &lt;code&gt;sceGsSwapDBuff&lt;/code&gt; then puts
the buffer&#39;s own environment, with DISPFB2 and DISPLAY2. &lt;code&gt;SetDisplayOffset&lt;/code&gt;
keeps the copies a line lower (&lt;a href=&quot;/339-adjust-screen-moves-the-picture-setdisplayoffset-s-units/&quot; title=&quot;Adjust Screen moves the picture: SetDisplayOffset&amp;#39;s units, and the presenter&amp;#39;s shift&quot;&gt;339&lt;/a&gt;). So both circuits read the same
buffer, circuit 1 a line lower, and a screen line comes out as
&lt;code&gt;ALP / 255 x the buffer&#39;s line above + (1 - ALP / 255) x its own&lt;/code&gt;, about
half each. On an interlaced TV that softens one-line detail that would
flicker between the fields. Field mode (the movies&#39; &lt;code&gt;SetScreenMode(640, 448, 2)&lt;/code&gt;) skips all of it and sets &lt;code&gt;sceGsSetHalfOffset&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;PCSX2&#39;s &quot;Anti-Blur&quot; option, on by default, is meant to undo blurs of this
kind, so PCSX2 players see the sharp picture. That is from the option&#39;s
description; it was not tried here.&lt;/p&gt;
&lt;h2 id=&quot;the-port&quot;&gt;The port&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Frame::field_mode&lt;/code&gt; (piney-draw) says how a frame is shown. It is false for
the game&#39;s frames and true for &lt;code&gt;piney_mpeg::movie::draw&lt;/code&gt;. &lt;code&gt;Gs::render&lt;/code&gt;
remembers it. &lt;code&gt;piney_gs::Pcrtc&lt;/code&gt; carries the display offset and
&lt;code&gt;deflicker&lt;/code&gt; to &lt;code&gt;Presenter::present&lt;/code&gt;. The shader&#39;s &lt;code&gt;merged()&lt;/code&gt; mixes the
moved picture with itself one row (1/448) up, at 127/255, before the sRGB
conversion: the GS merges the stored values. Above the top row the line
above is black, as circuit 1 there has nothing to show.&lt;/p&gt;
&lt;p&gt;The port&#39;s picture is progressive, so there is no flicker to fight, and
the present is kept sharp (its sharp bilinear). So the merge is off by
default, like PCSX2&#39;s anti-blur. &lt;code&gt;--deflicker&lt;/code&gt; and
the console&#39;s &lt;code&gt;deflicker on|off&lt;/code&gt; switch it (README &quot;Play&quot;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;present_merges_the_line_above_in_frame_mode&lt;/code&gt; draws a one-row white line at
row 200. Sharp, rows 199-201 read 0, 255, 0. With the deflicker they read
0, 128, 127, 0. A field-mode frame keeps 255, 0.&lt;/p&gt;
</content>
</entry>
<entry>
<title>Adjust Screen moves the picture: SetDisplayOffset&#39;s units, and the presenter&#39;s shift</title>
<id>/339-adjust-screen-moves-the-picture-setdisplayoffset-s-units</id>
<link rel="alternate" href="/339-adjust-screen-moves-the-picture-setdisplayoffset-s-units"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="render"/>
<category term="ui"/>
<category term="engine"/>
<summary>Adjust Screen (the desktop&#39;s ScreenMenu, INF SLUS_202.67:0x0016d4d0, and the field Option&#39;s, INF GCMN.PRG:0x0053f060) calls ccSystem::SetDisplayOffset(x, y) every frame it is open.</summary>
<content type="html">&lt;p&gt;Adjust Screen (the desktop&#39;s &lt;code&gt;ScreenMenu&lt;/code&gt;, INF SLUS_202.67:0x0016d4d0, and
the field Option&#39;s, INF GCMN.PRG:0x0053f060) calls
&lt;code&gt;ccSystem::SetDisplayOffset(x, y)&lt;/code&gt; every frame it is open. The port&#39;s menus
already asked for it (&lt;code&gt;Request::DisplayOffset&lt;/code&gt;), but the game dropped the
request, so the option did nothing (&lt;a href=&quot;/50-the-title-s-load-the-game-s-load-screen-on-the-card-and-the/&quot; title=&quot;The title&amp;#39;s Load: the game&amp;#39;s load screen on the card, and the loaded save handed to the desktop&quot;&gt;50&lt;/a&gt;, &lt;a href=&quot;/51-the-title-s-option-is-the-system-menu-in-its-title-mode-and/&quot; title=&quot;The title&amp;#39;s Option is the system menu in its title mode, and the title music written up&quot;&gt;51&lt;/a&gt;, &lt;a href=&quot;/196-a-book-given-to-a-party-member-in-town-raises-its-stat-the/&quot; title=&quot;&amp;#39;A book given to a party member in town raises its stat; the third member remarks on a present&amp;#39;&quot;&gt;196&lt;/a&gt;). The picture now
moves.&lt;/p&gt;
&lt;h2 id=&quot;what-the-offset-is-in&quot;&gt;What the offset is in&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SetDisplayOffset&lt;/code&gt; (INF SLUS_202.67:0x0010ab30) keeps (x, y) at +0x20 /
+0x22 and writes base + x (clamped 0..4095) and base + y (0..2046) into the
DX and DY of both display buffers&#39; DISPLAY register (+0xbf8, +0xc20). With
the field flag (+0xbd6) 0 it also writes them, y + 1, into read circuit 1&#39;s
pair at +0xe10 / +0xe20, the game&#39;s deflicker (&lt;a href=&quot;/340-the-frame-mode-deflicker-read-circuit-1-a-line-lower-merged/&quot; title=&quot;The frame-mode deflicker: read circuit 1 a line lower, merged at ALP 127&quot;&gt;340&lt;/a&gt;). The base (+0x1c, +0x1e) is DISPLAY&#39;s DX and DY
as &lt;code&gt;SetScreenModeMain&lt;/code&gt; (0x0010ad40) leaves them after &lt;code&gt;sceGsSetDefDBuff&lt;/code&gt;
(0x0010b57c-0x0010b594).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ccSystem::Init&lt;/code&gt; (0x0010a900) asks &lt;code&gt;SetScreenMode(512, 448, 0)&lt;/code&gt;, which
&lt;code&gt;SetScreenModeMain&lt;/code&gt; turns into &lt;code&gt;sceGsResetGraph(0, 1, 2, 0)&lt;/code&gt;: NTSC,
interlaced, frame mode, one 448-line buffer. &lt;code&gt;sceGsSetDefDispEnv&lt;/code&gt;
(0x0010de50) sets, for NTSC interlaced:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MAGH = (2559 + w) / w - 1      4 at 512 wide (5 clocks a pixel), 3 at 640
DX   = 636 + dx * (MAGH + 1)
DY   = 50 + dy
DH   = 2 h - 1 (field mode), h - 1 (frame mode)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So DX counts video clocks (2,560 across the picture at either width), and DY
counts lines of the interlaced frame: one row of the 448. Adjust Screen
steps x by 3 (-48..48) and y by 1 (-16..16), up to 9.6 pixels across and 16
rows down. An earlier draft of this change took y as half-lines and halved
it; DH = h - 1 for the 448-line frame mode shows a DY unit is a whole row.&lt;/p&gt;
&lt;p&gt;MUT, OUT and QUA have the same &lt;code&gt;SetDisplayOffset&lt;/code&gt; (MUT 0x0010abd0, OUT
0x0010b1a0, QUA 0x0010af70; OUT&#39;s and QUA&#39;s differ from INF&#39;s only in
instruction scheduling) and the same constants in &lt;code&gt;sceGsSetDefDispEnv&lt;/code&gt;
(636, 50, 72, 656).&lt;/p&gt;
&lt;h2 id=&quot;the-port&quot;&gt;The port&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Event::DisplayOffset { x, y }&lt;/code&gt; carries the request out of the desktop,
the title and top page (through &lt;code&gt;desktop::event&lt;/code&gt;), the town (&lt;code&gt;world.rs&lt;/code&gt;)
and the fields (&lt;code&gt;area.rs&lt;/code&gt;). Two places set it from the save, as the game
does through its own &lt;code&gt;SetDisplayOffset(screenX, screenY)&lt;/code&gt;
(&lt;a href=&quot;/docs/engine/title/&quot;&gt;title&lt;/a&gt;): the title&#39;s boot, where the kept
settings (&lt;code&gt;crates/piney-game/src/settings.rs&lt;/code&gt;, &lt;code&gt;screen_x&lt;/code&gt; / &lt;code&gt;screen_y&lt;/code&gt;) are already in the
save, and &lt;code&gt;LoadGame&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The app keeps the last offset and hands &lt;code&gt;Presenter::present&lt;/code&gt; a shift of
(x / 5, y) in pixels of the 512 x 448 picture. The presenter moves the
picture in its shader (&lt;code&gt;picture()&lt;/code&gt;: the sample point less the shift, black
outside the picture), inside the same 4:3 box. A viewport moved by the shift
was tried first, but wgpu rejects a viewport that leaves the attachment,
which a window with no bars would do at any offset. The console overlay does
not move.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;present_moves_the_picture_by_the_display_offset&lt;/code&gt; presents a cleared frame
moved an eighth across and down on a 600 x 450 surface: black left of x 75
and above y 56, the colour from there. &lt;code&gt;options_kept_across_the_parts&lt;/code&gt;
writes &lt;code&gt;screen_x = -12&lt;/code&gt;, &lt;code&gt;screen_y = 7&lt;/code&gt; into the settings file and sees
&lt;code&gt;DisplayOffset { x: -12, y: 7 }&lt;/code&gt; at power-on.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;Saves from PCSX2: a PS2 card image reader, the import, and Infection slots of 0x8d84&quot;</title>
<id>/338-saves-from-pcsx2-a-ps2-card-image-reader-the-import-and</id>
<link rel="alternate" href="/338-saves-from-pcsx2-a-ps2-card-image-reader-the-import-and"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="save"/>
<category term="format"/>
<category term="tooling"/>
<summary>Players asked to bring their PCSX2 saves over.</summary>
<content type="html">&lt;p&gt;Players asked to bring their PCSX2 saves over. The port&#39;s card is a
directory laid out as the PS2 card is: &lt;code&gt;&amp;lt;dir&amp;gt;/icon.sys&lt;/code&gt;, the icons, the
index &lt;code&gt;&amp;lt;dir&amp;gt;/&amp;lt;dir&amp;gt;&lt;/code&gt; and &lt;code&gt;dhdata01&lt;/code&gt;-&lt;code&gt;12&lt;/code&gt; (&lt;a href=&quot;/docs/formats/save/&quot;&gt;the save page&lt;/a&gt;).
So a save only has to be taken off PCSX2&#39;s card and its files copied
across.&lt;/p&gt;
&lt;h2 id=&quot;pcsx2-s-cards&quot;&gt;PCSX2&#39;s cards&lt;/h2&gt;
&lt;p&gt;PCSX2 keeps a card as one of two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a folder card: a directory per save, with PCSX2&#39;s own &lt;code&gt;_pcsx2_index&lt;/code&gt; (a
YAML-like list of each file&#39;s order and times) beside the game&#39;s files;&lt;/li&gt;
&lt;li&gt;a card image (&lt;code&gt;Mcd001.ps2&lt;/code&gt;): the PS2 memory card&#39;s file system, 8 MB of
528-byte pages (512 bytes of data and 16 of ECC).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The folder form needs copying only. The image needs a reader.
&lt;code&gt;piney_data::ps2card&lt;/code&gt; (new) reads it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the superblock: magic &quot;Sony PS2 Memory Card Format &quot;, page size,
pages a cluster, &lt;code&gt;alloc_offset&lt;/code&gt;, the root&#39;s cluster, &lt;code&gt;ifc_list&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the FAT through its indirect clusters;&lt;/li&gt;
&lt;li&gt;the 512-byte directory entries (the root&#39;s own &quot;.&quot; gives its count);&lt;/li&gt;
&lt;li&gt;a file through its FAT chain.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ECC is assumed when the image is a power-of-two count of 528-byte pages.
&lt;code&gt;a_save_directory_reads_back&lt;/code&gt; builds a card with and without ECC (a file
over two clusters, an empty one) and reads the directory back. No real
&lt;code&gt;.ps2&lt;/code&gt; image was at hand. The reader follows the format as published, and
an image from a real card is still to be tried.&lt;/p&gt;
&lt;h2 id=&quot;the-import&quot;&gt;The import&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;piney_desktop::card::import(src, card)&lt;/code&gt; finds the four volumes&#39; save
directories (&lt;code&gt;own_dir_name&lt;/code&gt; of each, from &lt;code&gt;mcDirName&lt;/code&gt;) in any of three
sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;.ps2&lt;/code&gt; image;&lt;/li&gt;
&lt;li&gt;a folder card;&lt;/li&gt;
&lt;li&gt;one exported save directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It copies each found directory whole, leaving out &lt;code&gt;_pcsx2_*&lt;/code&gt;. A directory
it replaces is moved to a fresh &lt;code&gt;backup-&amp;lt;secs&amp;gt;&lt;/code&gt; beside the card. It is
reached through &lt;code&gt;piney-game --import-card PATH&lt;/code&gt;, which imports and exits,
and through the console&#39;s &lt;code&gt;import_card PATH&lt;/code&gt;. &lt;code&gt;a_pcsx2_save_imports&lt;/code&gt; checks
a folder card, the backup, a lone save directory and a source with no save.
The user&#39;s export (&lt;code&gt;work/savecards&lt;/code&gt;, not in the repository) imports its 17
files.&lt;/p&gt;
&lt;h2 id=&quot;infection-slots-of-0x8d84&quot;&gt;Infection slots of 0x8d84&lt;/h2&gt;
&lt;p&gt;The user&#39;s export holds Infection&#39;s directory (&lt;code&gt;BASLUS-20267DOTHACK&lt;/code&gt;, the
&lt;code&gt;icon10/11/12.ico&lt;/code&gt; icons), but its twelve slot files are 0x8d84 (36,228)
bytes. That is the later volumes&#39; size; Infection&#39;s own &lt;code&gt;MakeDir&lt;/code&gt; makes
them 0x8530 (&lt;code&gt;li 0x8530&lt;/code&gt;, INF SLUS_202.67:0x001660d4). Slots 1 and 12 hold
a cleared game (level 34, &lt;code&gt;clearFlag&lt;/code&gt; 1). The index record&#39;s sum covers the
whole 0x8d84 bytes (slot 1: 0x599, against 0xc31b over the first 0x8530).
The first 0x8530 bytes are the same &lt;code&gt;ccSaveData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Infection&#39;s own load (and the port&#39;s) summed 0x8530 bytes and would refuse
the slot. At the user&#39;s request the port now takes either size. The card&#39;s
&lt;code&gt;read_slot&lt;/code&gt; hands over the whole file, and the load accepts the sum over the
volume&#39;s size or, for a longer Infection file, over 0x8d84. It copies the
0x8530-byte record as before. &lt;code&gt;an_infection_slot_loads_at_either_size&lt;/code&gt;
covers both sizes, a wrong sum, and (when &lt;code&gt;work/savecards&lt;/code&gt; is present) the
user&#39;s slot 1.&lt;/p&gt;
</content>
</entry>
<entry>
<title>A member&#39;s equipment remark in town</title>
<id>/337-a-member-s-equipment-remark-in-town</id>
<link rel="alternate" href="/337-a-member-s-equipment-remark-in-town"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="ui"/>
<category term="world"/>
<summary>[[67]] left the CHAT menu&#39;s ChangeEquipReport dropped in town.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/67-piney-battle-the-rules-of-combat-the-enemies-and-party-s/&quot; title=&quot;piney-battle: the rules of combat, the enemies&amp;#39; and party&amp;#39;s decisions and items, checked against the game&quot;&gt;67&lt;/a&gt; left the CHAT menu&#39;s &lt;code&gt;ChangeEquipReport&lt;/code&gt; dropped in town. In a
field, &lt;code&gt;FieldWorld::equip_report&lt;/code&gt; has the member answer the settings it was
left (&lt;code&gt;ccSpcChar::ChangeEquipReport(n)&lt;/code&gt;, gcmn 0x0059f4a0):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; 0: &lt;code&gt;ChatMessageEquipOK&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; 7: &lt;code&gt;ChatMessageEquipNOT(1)&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; 1-10 otherwise: &lt;code&gt;ChatMessageEquipNOT(0)&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Kite, or any other &lt;code&gt;n&lt;/code&gt;: nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The town&#39;s &lt;code&gt;WorldMode&lt;/code&gt; listed the request among those it drops.&lt;/p&gt;
&lt;p&gt;The town&#39;s party has the same AI. &lt;code&gt;World::equip_report(code, n)&lt;/code&gt; now runs
&lt;code&gt;chat_line&lt;/code&gt; on the member through &lt;code&gt;with_ai&lt;/code&gt;, as &lt;code&gt;chat_cmd&lt;/code&gt; runs its
orders. The line is queued like any line raised outside the frame. The
town&#39;s frame (&lt;code&gt;Combat::town_frame&lt;/code&gt;, &lt;code&gt;drain_chats&lt;/code&gt; at its start) sends it
the next frame, and &lt;code&gt;WorldMode&lt;/code&gt; puts it up over the member, as the field
does. The menu&#39;s &lt;code&gt;R::ChangeEquipReport&lt;/code&gt; goes there.&lt;/p&gt;
&lt;p&gt;The test is &lt;code&gt;a_member_reports_his_equipment_in_town&lt;/code&gt; (piney-game,
event22). In Mac Anu with Piros in the party, Kite&#39;s report and an &lt;code&gt;n&lt;/code&gt; of
11 put up nothing. Piros&#39;s report with 0 puts up his balloon on the next
frame.&lt;/p&gt;
</content>
</entry>
<entry>
<title>&quot;The menu task runs in its place: ccThGameCtrl, ccThMenu, then the world&quot;</title>
<id>/336-the-menu-task-runs-in-its-place-ccthgamectrl-ccthmenu-then</id>
<link rel="alternate" href="/336-the-menu-task-runs-in-its-place-ccthgamectrl-ccthmenu-then"/>
<updated>2026-10-01T00:00:00Z</updated>
<category term="engine"/>
<category term="ui"/>
<category term="audio"/>
<summary>[[334]] found the cause of [[101]]&#39;s late words.</summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;/334-each-mode-s-tasks-and-priorities-the-port-s-menu-task-runs/&quot; title=&quot;&amp;#39;Each mode&amp;#39;s tasks and priorities; the port&amp;#39;s menu task runs after the world&amp;#39;s&amp;#39;&quot;&gt;334&lt;/a&gt; found the cause of &lt;a href=&quot;/101-the-fights-voices-a-skill-request-names-its-skill-and-what/&quot; title=&quot;The fights&amp;#39; voices: a skill request names its skill, and what else speaks in a fight&quot;&gt;101&lt;/a&gt;&#39;s late words. The game runs, in this
order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ccThGameCtrl&lt;/code&gt; (33);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ccThMenu&lt;/code&gt; (34);&lt;/li&gt;
&lt;li&gt;the camera (40), the party (48), the entries (64), the effects and
the skills.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The town&#39;s and the field&#39;s modes ran the menu task after every world task.
Every request the menus make (a skill, an item, an order) therefore reached
the characters, and the sound it leads to the driver, a frame late.&lt;/p&gt;
&lt;h2 id=&quot;the-change&quot;&gt;The change&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;FieldWorld::step_game_ctrl&lt;/code&gt; and &lt;code&gt;World::step_game_ctrl&lt;/code&gt;&lt;/strong&gt; (the town)
run &lt;code&gt;ccThGameCtrl&lt;/code&gt; on its own, under the same condition as before (play
from F1, awake). Their &lt;code&gt;frame&lt;/code&gt; skips it once it has run (&lt;code&gt;ctrl_done&lt;/code&gt;).
Anything that only calls &lt;code&gt;step_into&lt;/code&gt; behaves as before.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;AreaMode::step&lt;/code&gt;&lt;/strong&gt; now runs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the event task, the map button and the ride&#39;s slot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;step_game_ctrl&lt;/code&gt;, then what it raised: the game over&#39;s first step,
the talks, the attack flag;&lt;/li&gt;
&lt;li&gt;the new &lt;code&gt;menu_task&lt;/code&gt;: Data Drain&#39;s movie, the ride&#39;s and the Fairy&#39;s
Orb&#39;s slots, &lt;code&gt;ui.step_into&lt;/code&gt; and its requests, which carry out item
uses and steps at once;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;world.step_into&lt;/code&gt;, the camera on;&lt;/li&gt;
&lt;li&gt;the shows, the sounds, the menu&#39;s rules, the chats and the map.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The menu runs from F1, which is the frame it ran in before (the old
test of &lt;code&gt;f &amp;gt;= 2&lt;/code&gt; came after the world had counted the frame). Data
Drain&#39;s black now keeps the world out of the frame by stepping it into
a frame of its own, not by clearing what was drawn, which would have
wiped the menus.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;WorldMode::step&lt;/code&gt;&lt;/strong&gt; (the towns) does the same. The book&#39;s stream and
the gate hack&#39;s screen go the same way. The walking PCs&#39; and the
members&#39; chat lines come from later tasks, so the menus show them the
next frame, as in the game.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-tests-that-moved&quot;&gt;The tests that moved&lt;/h2&gt;
&lt;p&gt;Five tests assumed the old order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;a_skill_in_a_fight_names_itself&lt;/code&gt;&lt;/strong&gt; now also requires Kite&#39;s
&lt;code&gt;SkillWords&lt;/code&gt; in the frame the target is confirmed (menu 65&#39;s CROSS).
The old order fails it (words at 249, confirm at 248). The new one
passes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;a_spell_lands_through_the_effects&lt;/code&gt;&lt;/strong&gt;: the blast&#39;s noise reads 20 at
the frame&#39;s end, not 19. &lt;code&gt;SetNoizBs(20)&lt;/code&gt; comes from the effects (80),
after the menu task&#39;s &lt;code&gt;Disp&lt;/code&gt; (34) has drawn that frame&#39;s noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;the_last_portal_puts_up_its_banner&lt;/code&gt;&lt;/strong&gt;: 91 frames on, not 90. The
entry control (64) starts &lt;code&gt;ccThDfComp&lt;/code&gt; after the menu task, so its first
&lt;code&gt;Main&lt;/code&gt; is the next frame, and it then runs its 90 &lt;code&gt;Main&lt;/code&gt;s. Before, the
port started and ran it in one frame.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;a_scroll_on_a_foe_is_cast_by_kite&lt;/code&gt;&lt;/strong&gt; watched for &lt;code&gt;skill_id&lt;/code&gt; 193 at a
frame&#39;s end. The request sets it, and the party&#39;s and the skills&#39; tasks
clear it in the same frame, as the cast (act 17) begins. Only the old
order left it visible, so the test now takes the cast&#39;s act as the use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;chat_a_member_to_heal_in_a_fight&lt;/code&gt;&lt;/strong&gt; recorded the order after scanning
the frame&#39;s events. The member now names Repth in the order&#39;s own
frame.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The piney-game (204), piney-world and piney-fieldui suites pass. So do
Mutation&#39;s and Outbreak&#39;s whole stories.&lt;/p&gt;
</content>
</entry>
</feed>
