Even if the time displayed doesn't exactly represent the amount of real time that passed, it's still useful for comparing scores from the same game against each other. An external timer has a human element that also introduces imprecision. (The human must react to the game actually ending.)
Indivisible has a speed run timer (that's even adjusted for PAL) in the NMI. It counts frames, seconds, minutes, and hours. The time is a little wrong since NES doesn't quite run at 60 FPS (or 50 FPS for PAL) like the game assumes, but it's still fine for competition. Another game with this kind of issue is Tetris Grand Master 2: https://www.youtube.com/watch?v=xfSafCTJomA
The game starts a little after the beginning of the video but by 4:46 in the video when the in game timer stops, the in game timer has gained a lead. (4:52). Same kind of the thing. The game counts frames, but the framerate isn't quite 60. Still, this is the time used for competition.
Edit: To be more clear, the hardware is consistent, but it's consistent to some fraction close to 60 rather than exactly 60. NTSC NES is 60.0988 according to the wiki.
A fun topic on the matter: https://tetrisconcept.net/threads/tgm-and-tap-framerates.1796/
A fair amount of speed games (Maybe just a lot of Metroid games) do still use an in game timer despite things like that, they just mark emulator vs not or only accept real hardware scores.
I guess my personal thought about it is the most hardcore people will seek out the fastest way to fit whatever the rules are. (If you're a serious runner, you'll buy the game in a language that's not your native language if it's the fastest one to game time.)
Edit2: Actually, now that I'm thinking about it, there's that Blameless runner who got accused of cheating because of his monitor's refresh rate. At a high enough level you kind of can't win on the issue... I think my ideal would be in game timer that ignores load times but... I'll stop typing now.
The only gotcha I'll mention if anyone attempts to implement this:
Make sure you disable the NMI before you read the timer! (And. Well, also make sure you keep the NMI enabled for the whole rest of the game.)
Imagine a time of 3 minutes 59 seconds 59 frames when the ending sequence is reached. If the NMI isn't disabled. The game might read the 3 and store it in RAM for display. Then the NMI hits. The frame counter is incremented. This makes the second counter get incremented. This makes the minute counter get incremented. Now the time is 4 minutes 0 seconds 0 frames. The game returns from the NMI, but since the game already read the 3 for display, the time displayed ends up being 3 minutes 0 seconds 0 frames. A whole minute off.
Edit: Here's Indivisible's timer code:
Code:
NMI.end:
ldy <timerframe;Add one to the frame
iny
sty <timerframe
cpy #50;If it's < 50
bcc NMI.timerend;we're done
;If here, we might need to update for PAL
lda consoletype
bne NMI.timer.add
;If here, we're NTSC, so we need to check for 60 FPS
cpy #60
bcc NMI.timerend;if < 60 we're done
NMI.timer.add:
ldy #0;Else set it to zero
sty <timerframe
ldy <timersec;And increment seconds
iny
sty <timersec
cpy #60;If it's < 60
bcc NMI.timerend;we're done
ldy #0;else set it to zero
sty <timersec
ldy <timermin;and increment minutes
iny
sty <timermin
cpy #60;If it's < 60
bcc NMI.timerend;we're done
ldy #0;else set it to zero
sty <timermin
ldy <timerhour;and increment hours
iny;If we're not at 255 hours
bne NMI.storehours;store the addition
ldy #59
sty <timermin
sty <timersec
sty <timerframe
bne NMI.timerend;converted from a jmp
NMI.storehours:
sty <timerhour
NMI.timerend:
There's code somewhere to convert "frames" (range of 0-49 or 0-59) to fractions of a second (range of 0-99) but it's a bit more complex. There's code for detecting PAL consoles here: https://forums.nesdev.com/viewtopic.php?p=163258#p163258
Indivisible puts a little (PAL) next to the time. (It definitely needs to be detected. If a PAL player and NTSC player beat the game and the same amount of real time has passed, it means a very different level of play)