Skip to content

Commit d236819

Browse files
Merge pull request #91 from fractal161/negative-delay
Add negative delay to the hz display
2 parents 7d59300 + 9ffe6dd commit d236819

File tree

15 files changed

+467
-84
lines changed

15 files changed

+467
-84
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
* Famicom Keyboard support
2525
* MMC3 Support
2626
* MMC5 Support
27+
* Added "negative delay" to the hz display
28+
* Keep hz display enabled when topped out
2729

2830
## [v5 tournament]
2931
* Linecap Menu (from CTM Masters September 2022)

src/gamemodestate/initstate.asm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ gameModeState_initGameState:
149149
@noTypeBPlayfield:
150150

151151
jsr hzStart
152+
lda #0
153+
sta hzSpawnDelay
152154
jsr practiseInitGameState
153155
jsr resetScroll
154156

src/modes/hz.asm

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ hzDebounceThreshold := $10
99

1010
hzStart: ; called in playState_spawnNextTetrimino, gameModeState_initGameState, gameMode_gameTypeMenu
1111
lda #0
12-
sta hzSpawnDelay
1312
sta hzTapCounter
1413
lda #hzDebounceThreshold
1514
sta hzDebounceCounter
@@ -50,7 +49,7 @@ hzControl: ; called in playState_playerControlsActiveTetrimino, gameTypeLoopCont
5049
bne @noDelayInc
5150
lda hzSpawnDelay
5251
cmp #$F
53-
beq @noDelayInc
52+
bcs @noDelayInc
5453
inc hzSpawnDelay
5554
@noDelayInc:
5655
rts
@@ -183,6 +182,33 @@ hzTap:
183182
sta renderFlags
184183
rts
185184

185+
; X: value to store if left or right is newly pressed
186+
checkNegativeDelay:
187+
; the tail of entry delay has two paths: a normal path, and one where
188+
; spawn delay is added (currently, tspins and debug mode)
189+
; if the spawn delay is too large, we shouldn't update here
190+
lda spawnDelay
191+
cmp #3
192+
bcs @ret
193+
lda hzSpawnDelay
194+
bne @ret
195+
lda newlyPressedButtons_player1
196+
and #BUTTON_DPAD
197+
cmp #BUTTON_LEFT
198+
beq @setDelay
199+
lda newlyPressedButtons_player1
200+
and #BUTTON_DPAD
201+
cmp #BUTTON_RIGHT
202+
beq @setDelay
203+
rts
204+
@setDelay:
205+
stx hzSpawnDelay
206+
lda renderFlags
207+
ora #$10
208+
sta renderFlags
209+
@ret:
210+
rts
211+
186212
dasLimitLookup:
187213
.byte 0, 0, 4, 11, 18, 24, 30, 36, 42 , 48; , 54, 60
188214
.byte 0, 0, 3, 7, 12, 16, 20, 24, 28, 32 ; PAL

src/nmi/render_hz.asm

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
renderHz:
22
; only set at game start and when player is controlling a piece
33
; during which, no other tile updates are happening
4-
; this is pretty expensive and uses up $7 PPU tile writes and 1 palette write
4+
; this is pretty expensive and uses up $8 PPU tile writes and 1 palette write
55

66
; delay
77

88
lda #$22
99
sta PPUADDR
10-
lda #$68
10+
lda #$67
1111
sta PPUADDR
12+
ldx #$24 ; minus sign
1213
lda hzSpawnDelay
14+
and #$80
15+
bne @isNegative
16+
ldx #$FF ; blank tile
17+
@isNegative:
18+
stx PPUDATA
19+
lda hzSpawnDelay
20+
and #$7F ; clear sign flag
1321
sta PPUDATA
1422

1523
renderHzSpeedTest:

src/nmi/render_mode_play_and_demo.asm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ render_mode_play_and_demo:
9393
and #RENDER_SCORE
9494
beq @renderHz
9595

96-
; 8 safe tile writes freed from stats / hz
96+
; 7 safe tile writes freed from stats / hz
9797
; (lazy render hz for 10 more)
9898
; 1 added in level (3 total)
9999
; 2 added in lines (5 total)

src/playstate/gameover_rocket.asm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
playState_checkStartGameOver:
2+
jsr hzControl
23
.if !ALWAYS_CURTAIN
34
; skip curtain / rocket when not qualling
45
lda qualFlag

src/playstate/garbage.asm

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@ playState_receiveGarbage:
3737
lda #$00
3838
sta pendingGarbage
3939
sta vramRow
40-
@ret: inc playState
41-
@delay: rts
40+
@ret: inc playState
41+
lda #$00 ; earliest possible measured point
42+
sta hzSpawnDelay
43+
ldx #$83 ; -3 tap delay
44+
jsr checkNegativeDelay
45+
rts
4246

4347

4448
garbageLines:

src/playstate/spawnnext.asm

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,24 @@ SPAWN_NEXT_ADDONS := 1
33
playState_spawnNextTetrimino:
44
lda vramRow
55
cmp #$20
6-
bmi @ret
6+
bpl :+
7+
ldx #$82 ; -2 tap delay
8+
jsr checkNegativeDelay
9+
rts
710

11+
:
812
.if SPAWN_NEXT_ADDONS
913
lda spawnDelay
1014
beq @notDelaying
15+
; here, spawnDelay=1 means hzSpawnDelay=-2, 2 implies -3, and etc.
16+
cmp #3 ; if spawnDelay is >= 3, don't update
17+
bcs @noCheck
18+
clc
19+
adc #1
20+
ora #$80 ; mark delay as negative
21+
tax
22+
jsr checkNegativeDelay
23+
@noCheck:
1124
dec spawnDelay
1225
jmp @ret
1326
.endif
@@ -24,7 +37,8 @@ playState_spawnNextTetrimino:
2437
sta saveStateDirty
2538
rts
2639
@noSaveState:
27-
40+
ldx #$81 ; -1 tap delay
41+
jsr checkNegativeDelay
2842
jsr hzStart
2943
.endif
3044

tests/src/cycle_count.rs

Lines changed: 53 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
11
use crate::{util, score, labels, playfield};
2-
use rusticnes_core::nes::NesState;
32

43
pub fn count_cycles() {
4+
test_hz_cycles();
5+
test_max_score_cycles();
6+
test_mode_score_cycles();
7+
}
8+
9+
fn test_hz_cycles() {
10+
// check max hz calculation amount
11+
let mut emu = util::emulator(None);
12+
13+
let hz_flag = labels::get("hzFlag") as usize;
14+
let game_mode = labels::get("gameMode") as usize;
15+
let x = labels::get("tetriminoX") as usize;
16+
let y = labels::get("tetriminoY") as usize;
17+
let main_loop = labels::get("mainLoop");
18+
let level_number = labels::get("levelNumber") as usize;
19+
let debounce = labels::get("hzDebounceThreshold") as usize;
20+
21+
util::run_n_vblanks(&mut emu, 3);
22+
23+
emu.memory.iram_raw[hz_flag] = 1;
24+
emu.memory.iram_raw[level_number] = 18;
25+
emu.memory.iram_raw[game_mode] = 4;
26+
emu.registers.pc = main_loop;
27+
28+
util::run_n_vblanks(&mut emu, 5);
29+
30+
let mut highest = 0;
31+
32+
for buttons in &[
33+
"L.L.L.L.L",
34+
"RR...R...R...R.R.",
35+
".....L.L..L.L.",
36+
"LLL..L.L...L.R.R..L...R....L....L...L....L",
37+
"R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R",
38+
] {
39+
buttons.chars().for_each(|button| {
40+
emu.memory.iram_raw[x] = 5;
41+
emu.memory.iram_raw[y] = 0;
42+
util::set_controller(&mut emu, button);
43+
highest = highest.max(util::cycles_to_vblank(&mut emu));
44+
});
45+
46+
util::run_n_vblanks(&mut emu, debounce + 1);
47+
}
48+
49+
println!("hz display most cycles {}", highest);
50+
}
51+
52+
fn test_max_score_cycles() {
53+
// check max scoring cycle amount
554
let mut emu = util::emulator(None);
655

756
let completed_lines = labels::get("completedLines") as usize;
@@ -18,7 +67,6 @@ pub fn count_cycles() {
1867

1968
let mut highest = 0;
2069

21-
// check every linecount on every level
2270
for level in 0..=255 {
2371
for lines in 0..=4 {
2472
let count = score(999999, lines, level);
@@ -30,9 +78,10 @@ pub fn count_cycles() {
3078
}
3179

3280
println!("scoring routine most cycles: {}", highest);
81+
}
3382

83+
fn test_mode_score_cycles() {
3484
// check clock cycles frames in each mode
35-
3685
let mut emu = util::emulator(None);
3786

3887
for mode in 0..labels::get("MODE_GAME_QUANTITY") {
@@ -76,7 +125,7 @@ pub fn count_cycles() {
76125
emu.memory.iram_raw[labels::get("autorepeatY") as usize] = 0;
77126

78127
for _ in 0..45 {
79-
let cycles = cycles_to_vblank(&mut emu);
128+
let cycles = util::cycles_to_vblank(&mut emu);
80129

81130
if cycles > highest {
82131
highest = cycles;
@@ -88,64 +137,4 @@ pub fn count_cycles() {
88137

89138
println!("cycles {} lines {} mode {}", highest, lines, mode);
90139
}
91-
92-
}
93-
94-
fn cycles_to_vblank(emu: &mut NesState) -> u32 {
95-
let vblank = labels::get("verticalBlankingInterval") as usize;
96-
let mut cycles = 0;
97-
let mut done = false;
98-
99-
while emu.ppu.current_scanline == 242 {
100-
emu.cycle();
101-
if !done {
102-
cycles += 1;
103-
if emu.memory.iram_raw[vblank] == 1 {
104-
done = true;
105-
}
106-
}
107-
let mut i = 0;
108-
while emu.cpu.tick >= 1 && i < 10 {
109-
emu.cycle();
110-
if !done {
111-
cycles += 1;
112-
if emu.memory.iram_raw[vblank] == 1 {
113-
done = true;
114-
}
115-
}
116-
i += 1;
117-
}
118-
if emu.ppu.current_frame != emu.last_frame {
119-
emu.event_tracker.swap_buffers();
120-
emu.last_frame = emu.ppu.current_frame;
121-
}
122-
}
123-
emu.memory.iram_raw[vblank] = 1;
124-
done = false;
125-
while emu.ppu.current_scanline != 242 {
126-
emu.cycle();
127-
if !done {
128-
cycles += 1;
129-
if emu.memory.iram_raw[vblank] == 0 {
130-
done = true;
131-
}
132-
}
133-
let mut i = 0;
134-
while emu.cpu.tick >= 1 && i < 10 {
135-
emu.cycle();
136-
if !done {
137-
cycles += 1;
138-
if emu.memory.iram_raw[vblank] == 0 {
139-
done = true;
140-
}
141-
}
142-
i += 1;
143-
}
144-
if emu.ppu.current_frame != emu.last_frame {
145-
emu.event_tracker.swap_buffers();
146-
emu.last_frame = emu.ppu.current_frame;
147-
}
148-
}
149-
150-
cycles
151140
}

0 commit comments

Comments
 (0)