Devlog ·
PS1 Fishing 1 Water Animation Handoff — April 21, 2026
~4 min read · 1239 words
Date: 2026-04-21
Repo: repo:/
Branch: ps1
On this page
Goal
Get fgpilot fishing1 to:
- keep the current correct scene-relative foreground/tree occlusion behavior
- keep the current title -> scene handoff
- animate the island water/wave sprites correctly
- do it in a low-memory way that can scale to other scenes
Known Good Baseline
The last strong water-free foreground milestone is:
5ea67b35Make Fishing 1 foreground packs scene-relative
This fixed the tree/Johnny mismatch by making the foreground pack scene-relative.
Other later committed milestones that are relevant to the current path:
44cec0a6Restore ocean base under foreground runtime27ac05c9Keep title visible during staged island base loads373c38adRemove title-screen CD settle delay
User-validated integrated scene state before wave work:
- title screen -> scene handoff works
- ocean + static island base appear
- Johnny foreground pack works
- scene-relative tree occlusion works
- waves do not animate
What Has Been Proven
1. The wave primitive itself works on PS1
A sandbox worktree was created:
workspace:/jc_reborn_wave_sandbox- branch
ps1-wave-sandbox
A minimal fgpilot wavetest was added there:
- black background only
- no island
- no Johnny
- no FG pack
- just 4
BACKGRND.BMPwave sprites drawn each frame
User validation:
- black-screen
wavetestanimated correctly - after one bad island-pop regression, the corrected version was “super legit working”
So:
- PS1 can animate the wave sprites
BACKGRND.BMPwave frames are valid- the basic draw path works in isolation
2. The Fishing 1 integrated failure is not a host-data absence issue
Earlier investigations established:
- host source contains moving water pixels / wave draw state
- earlier
BG1/ backdrop pack experiments did generate changing data - wave data was not simply “missing”
3. Water failure is integration/runtime-side
Because black-screen wavetest works but integrated fgpilot fishing1 does not, the bug is in the integrated scene path:
- clean base construction / restore semantics
- when dynamic water is drawn relative to that base
- or how integrated runtime composition/upload suppresses or overwrites those changes
Current Technical Findings
grLoadBmp() is RAM-path only on PS1
In graphics_ps1.c:
grLoadBmp()immediately callsgrLoadBmpRAM()- the old VRAM/OT path is disabled
That means grDrawSprite() for BACKGRND.BMP in PS1 normally goes through:
grCompositeToBackground()- dirty-rect marking
grDrawBackground()upload
So this is not an OT-vs-RAM issue anymore.
Integrated main loop shape
fgPlayOceanRuntimeScene() currently does:
- build static scene base
grSaveCleanBgTiles()- start foreground runtime
- frame loop:
grBeginFrame()grRestoreBgTiles()grUpdateDisplay(NULL, NULL, NULL)foregroundPilotRuntimeAdvance()
Inside grUpdateDisplay():
VSync(0)- if
foregroundPilotRuntimeActive()->foregroundPilotRuntimeCompose() grDrawBackground()eventsWaitTick(grUpdateDelay)
So integrated dynamic wave drawing must survive:
grRestoreBgTiles()- any draws inside
foregroundPilotRuntimeCompose() grDrawBackground()upload
Foreground pack path uses direct compositing
Foreground pack frames go through:
fgBlit16ToBackgroundRect()grCompositeDirect16ToBackground()
That path marks dirty rows directly and does not use sprite OT primitives.
Some direct helper paths clear currDirty*
In graphics_ps1.c, these functions explicitly zero currDirtyMinY/MaxY:
grRestoreBackgroundRectForFrame()grRestoreAndCompositeDirect16BackgroundRectForFrame()
Those are dangerous in mixed-draw scenarios because they can wipe previously dirtied rows in the same frame.
The main integrated scene-pack path is currently using grCompositeDirect16ToBackground(), which does not clear currDirty*, but this area remains suspect and should be watched carefully.
What Has Been Tried And Failed
High-memory backdrop frame streams
Tried:
BG116-bit backdrop frame packs- dense backdrop frame playback
- catch-up logic on backdrop frame playback
Outcome:
- waves still did not visibly animate
- memory usage ballooned
- one prelude-baseline attempt blew
FISHING1.BG1up from about6.6 MBto about75 MB - this path also increased crash / blank-screen risk
Conclusion:
- full-frame backdrop streams are the wrong memory model for PS1 here
Draw-pack replay variants
Tried:
FOC/draw-pack backdrop replay- host visible-draw replay
BACKGRND.BMPdraw-list replay- sparse and dense timing variations
Outcome:
- user repeatedly observed absolutely no water motion
Conclusion:
- asset-side variations were not fixing the actual runtime integration bug
Native backdrop thread reuse
Tried:
adsPilotComposeIslandBackdrop()adsPilotAdvanceIslandBackdrop()islandComposePilotWaves()- related backdrop-thread timing/catch-up variants
Outcome:
- user still saw no water animation in
fishing1
Wrong-path proof overlays
Several “proof” attempts were invalid or misleading:
- Fresh per-runtime
BACKGRND.BMPproof slot- bad because
grLoadBmpRAM()can silently short-load or fail under memory pressure - also made the build extremely slow
- bad because
- Scene-state / tide / island-position guesses
- user correctly pushed back on these as not explaining total absence of motion
- Repainting waves over sprites with a new slot
- also invalid under memory pressure if the slot never loaded
Most Important Validated Sandbox Mechanism
The black-screen sandbox that worked used a very simple loop:
fgInitVisiblePipeline()fgInitBlackBackground()- preload
BACKGRND.BMPonce - each frame:
grBeginFrame()grRestoreBgTiles()grDrawSprite(grBackgroundSfc, &slot, x, y, spriteNo, 0)for 4 wavesgrUpdateDisplay(NULL, NULL, NULL)
That worked.
So the exact delta between:
- that sandbox loop
- and integrated
fgpilot fishing1
is where the bug lives.
Current Dirty Worktree State
At the time of writing, the worktree is dirty with ongoing wave-debugging changes, including:
ads.cads.hforeground_pilot.cisland.cisland.hconfig/ps1/cd_layout.xmlscripts/export-fishing1-foreground-pilot.shttm.c- generated files like
generated/ps1/foreground/FISHING1.FOC
The latest attempted diagnostic change was:
- remove the fresh proof-slot allocation entirely
- reuse the already-loaded island background slot via:
adsPilotComposeWaveProofOverlay(uint32 vblankCount)islandComposePilotProofWaves(struct TTtmThread*, uint32)
- draw those proof waves before
grUpdateDisplay()infgPlayOceanRuntimeScene()
User result on that family of proof builds:
- still no visible wave overlay
- speed badly degraded in some builds
So the integrated path is still not surfacing even deliberately wrong wave draws.
Best Current Hypothesis
There is a runtime integration bug in the visible frame pipeline, likely one of:
- dynamic wave draws are being overwritten later in the same frame
- dirty-row bookkeeping for integrated scene frames is wiping or skipping the wave rows
- clean-base restore + foreground compose ordering is neutralizing the dynamic wave changes
- integrated scene base construction leaves the wrong clean baseline, so live water changes never survive to upload
What is not currently a strong hypothesis:
- missing wave assets
- host export not containing wave data
- PS1 not being able to animate the wave sprites
Those have effectively been disproved.
Recommended Next Debugging Boundary
Stop trying new export formats until the integrated runtime path is pinned down.
The right next comparison is:
Sandbox working path
- black base
- draw waves
- upload
Integrated failing path
- restore clean scene base
- draw dynamic waves
- draw foreground pack
- upload
The next agent should prove exactly where the dynamic wave draw disappears:
- Does the integrated loop actually call the wave draw every frame?
- After the wave draw, are the expected dirty rows still marked?
- After foreground pack compose, are those dirty rows still marked?
- Does
grDrawBackground()upload those rows? - Is the clean base itself already containing a static wave frame that visually masks the motion?
Practical Constraint
Keep the solution low-memory.
Do not go back to full decoded backdrop frame buffers as the primary solution.
The likely scalable solution is still:
- static scene base recipe
- tiny dynamic base-layer draw replay
- visible-pixels-only scene-relative foreground pack
But the integrated runtime bug must be fixed first.
Short Version
- Tree/Johnny foreground is in good shape.
- Water animation works in a black-screen sandbox.
- Water animation does not appear in integrated
fishing1. - This is now clearly an integrated runtime composition/upload bug, not an asset-generation bug.
- The next agent should debug the runtime draw/dirty/upload boundary, not invent more export variations.