Entry 165
The screen shake: cameraShake, cameraShockAbsorber and cameraSet's offset
Filescrates/piney-world/src/camera.rscrates/piney-world/src/combat/stage.rscrates/piney-world/src/field_world.rscrates/piney-game/src/fx.rscrates/piney-game/src/area.rscrates/piney-world/examples/shake_probe.rstools/test_camera_shake_rs.py
Hits and spells did not shake the screen. The effects already raised
Event::CameraShake for the criticals, the spells' blasts and the
shockwaves, but nothing took the events. The enemies' note 0x8002 asked
checkCameraShakeRange, which answered no. docs/engine/field-game.md,
"The screen shake", has the rules.
camera::Shake holds camera.cpp's eight camShockForce slots and the
vibrate statics:
cameraShake(main 0x00162cd0) keeps or drops a new shock against the held ones, and a turning shake (dirc 2) takes its heading fromrand().cameraShockAbsorber(0x001629b0) runs at the end of everycameraMain. The strongest shock sets the force (10, 20 or 30), the cycle and aRy(dirc)matrix, which a turning one turns 0x1000 a frame. The oscillator gives the offset's z, each crossing of 0 keeps a fifth of the force, and the slots count down.cameraSet, for camera 1, moves the eye byo = 0.5 (Rz(a) vibrateMatrix) vibrateOffset, withathe heading (a doubleatan2). The target moves byoand then again byotimesmin(dist / 280, 1).
The first comparison caught that last one: the port had moved the target only by the scaled part.
Two statics matter at the start. vibrateRotate starts at 0, not -1, so
slot 0's heading turns every frame until a shake without rot comes.
vibrateCycle starts at 0, so the game's first absorber divides by
zero. The force is 0 then, so the oscillator is reset right after, and
the port skips the division.
The shakes now reach the camera three ways:
- the enemies' note, through
Stage::shake_range(ccCheckCameraDeg67.5 degrees, and within 2000 of the eye), thenCall::CameraShakeat the note; - the effects' events, which
FieldFx::take_shakeshands over after the frame; FieldWorld::camera_shake, which drawsrand()from the battle's generator.
tools/test_camera_shake_rs.py (new) runs the game's cameraShake,
cameraShockAbsorber and cameraSet in eemu. It does 12 runs of 400
frames with random shakes, and rand() hooked to the port's generator
from one seed. SetMatrix_PosTarget is hooked to take the eye and
target. The shake_probe example answers with the port's state. All 801
shakes, 4,406 shaking frames and 4,800 eyes and targets were equal.
test_world_rs (18) and test_field_rt (5) still pass, as do 605
workspace tests.
The double atan2 is Rust's (the system libm), not newlib's. It agreed in every case here, since the result is rounded to float. The effects' shakes are applied after the frame, where the game's effect task (priority 80) calls cameraShake inside it. Since the absorber runs in the next cameraMain either way, only the order of rand() draws within the frame can differ.