Program skips over some generated inputs and instead generates new input

Sorry for the uninformative title. I’m not really sure how to shortly articulate this problem.

I am trying to display a pseudo-random set of letters (a “stream”) on the screen that changes every 0.4 seconds. Each 0.4s interval of letter being displayed is called a streambit. Here is an example of a typical stream being displayed over a few streambits:

While this looks well and good, there is actually a problem that’s not apparent from the video alone. The experiment is indeed showing pseudo-randomly chosen letters, however they are not the letters that the program is meant to display.

For example, the first stream of letters is reported in my logger to be: trial.stream = [[['N', 'T'], 1], [['G', 'U'], 1], [['Q', 'Z'], 1], [['I', 'V'], -1], [['L', 'X'], -1]] (the 1/-1 is necessary for the experiment but not relevant to this problem. It’s just a flag indicating the streambit’s proximity to the closest T). We only see the first part of the stream ([['N', 'T'], 1]), before it jumps to the next randomly generated stream, which is shown in the log to be [[['A', 'M'], -1], [['J', 'C'], -1], [['T', 'H'], 1], [['N', 'U'], 1], [['V', 'R'], 1]]. What this tells me is that only the first streambit is being displayed from each stream, then it goes to the next generated stream to display the first streambit, and so on. Why is this? Here is the relevant (but not complete) code for displaying the streams

# Main loop for phase 1
while continue_phase1:
    _timeToFirstFrame = win.getFutureFlipTime(clock="now")
    trialClock.reset(-_timeToFirstFrame) # t0 is time of first possible flip
    routineTimer.reset()

    # Each RSVP has five trials
    for i in range(5):
        trial = generate_trial(phase, trial_id, level, 1) # Generate the trial according to phase 1 rules
        log.info(f"Generated trial for RSVP {rsvp_count}: {trial}")

        streams = reposition(streams, level)  # Reposition each stimuli to the correct place
        tThisFlip = win.getFutureFlipTime(clock=trialClock)
        tThisFlipGlobal = win.getFutureFlipTime(clock=None)

        while routineTimer.getTime() > 0:
            # Iterate over the letters in each stream and display them appropriately
            for letters, T_nearby in trial.stream:
                # Check if there is a target in the stream
                T_in_stream = "T" in letters
                letters_iter = iter(letters)

                for stream in streams:
                    if stream.status == NOT_STARTED:# and tThisFlipGlobal >= heads_up.tStopRefresh - frame_tolerance:
                        stream.status = STARTED
                        stream.tStartRefresh = tThisFlipGlobal
                        win.timeOnFlip(stream, "tStartRefresh")
                        stream.setAutoDraw(True)

                    if stream.status == STARTED:
                        if tThisFlipGlobal > stream.tStartRefresh + 0.4 * trial.stream.index([letters, T_nearby]) - frame_tolerance:
                            try:
                                stream.text = next(letters_iter)
                                log.info(f"Displaying stream with value {stream.text}")
                            except StopIteration:
                                stream.text = ""

                if continueRoutine:
                    win.flip()

        trial_id += 1
        trials.append(trial)

        # Reset stream statuses to NOT_STARTED so they will still display on the next run-through
        for stream in streams:
            if hasattr(stream, 'status'):
                stream.status = NOT_STARTED

I have confirmed via a debugger that for letters, T_nearby in trial.stream does actually iterate through each streambit in the stream, but for some reason stream.text = next(letters_iter) is only capturing the first streambit each time. In the logs, stream.text is being updated an appropriate amount of times for the entire stream, but the update itself is only the first two/three letters in the streambit, i.e.

Any help would be greatly appreciated. If there is any more info needed to help solve this problem, I will do my best to provide it.

P.S. Sorry for the varying snake/camel case. Snake case is my own code and camel case is what psychopy generated.