{w} tag and text shifting during gameplay

In this forum we discuss the future of Ren'Py, both bug fixes and longer-term development. Pre-releases are announced and discussed here.
Post Reply
User avatar
Posts: 4
Joined: Fri Nov 09, 2018 12:27 am
Deviantart: TanteiSakana
itch: TanteiSakana

{w} tag and text shifting during gameplay

#1 Post by TanteiSakana » Sat Oct 19, 2019 12:24 am

(tl;dr: The {w} tag seems to have a low but effectively random chance of causing text to move around when encountered. Since this glitch is pretty difficult to reproduce, I've tried to include as much info as I can about how to observe it in action, how I worked around it in my game, and what I think is happening. Hopefully this will help the problem get identified and fixed.)

I ran into a number of issues while developing my game in Ren'Py, but this one was by far the worst.

I wanted to make the dialogue in my game slowly type and briefly pause after punctuation marks. This was easy enough to set up, but there was one problem—a specific line of dialogue in my game caused a good portion of the text to quite jarringly shift to different positions after one of the pauses.

Further investigation revealed that this is likely specific to the exact text, font, font size, message window width, word wrapping algorithm, and click-to-continue configuration of my game and that particular line of dialogue.

In addition, working around this without rewriting my prose was… kind of nontrivial and involved choosing between some weird tradeoffs.

Unfortunately, though, since this issue seems to be extremely text metric dependent, I've only been able to reproduce it with the exact line of dialogue from the game where I noticed this, and with a decent portion of the GUI recreated—so, while this is a bit embarrassing, until someone comes up with a better test case for this, this is the best way I can think of to describe how to reproduce the glitch I encountered:

When typeset in the TrueType version of Zilla Slab Regular at 37 pixels tall in the TeX word wrapping algorithm with letter spacing adjustment on in a text box roughly between 1100 and 1140 pixels wide in a 1080p game, with a roughly 37x37 click-to-continue graphic set to the "nestled-close" position, the following line of dialogue triggers a number of edge cases in Ren'Py's text display code that likely deserve further investigation:

Code: Select all

"Cat people are carnivores, so I always have to pack my own food, in case we end up somewhere where there's not much meat...{w=0.1} like here."
…as you could imagine, I've put together minimal reproduction project with all of this set up.

I'm not sure how important the game being 1080p or using the TrueType version of that font is in reproducing this glitch, but I've set up the project like this to duplicate the environment in which I noticed the glitch, and am fairly confident the rest of the factors are important to this.

In addition, somehow, wrapping this text in quotation marks and identing all but the first line by the width of a quotation mark, as my game does, appears to not affect whether this happens or not at all—however, I've replicated this in the reproduction project just in case I'm wrong.

To avoid lots of back and forth, the reproduction project contains the following test cases:
  1. The line of dialogue set up reproduce the rewrapping glitch.
  2. The first workaround I tried—setting the word wrap algorithm to "greedy." This solves the rewrapping, but I didn't use this because at lower resolutions, it caused letters to noticably squish together after the pause.
  3. The second workaround I tried—setting the word wrap algorithm to "greedy" and also turning off the automatic letter spacing adjustment. This solved both problems, but I didn't use this because at lower resolutions, it caused text to overflow out of the game's text box, which I felt was worse. (This seems to be known and documented, so I didn't try to contrive a GUI layout to demonstrate that in the reproduction project.)
  4. The third workaround I tried—pausing by temporarily reducing the typing speed with the {cps} tag instead of by using the {w} tag. This is what I ended up shipping my game with. While this is a bit counterintuitive, it did avoid all problems most thoroughly, and had the neat extra of letting me specify pause times in relation to the current text typing speed. (Because of this, if anyone tests this project with the text speed set to instant, they will see no pause here—this is a little counterintuitive for the tests, but I wanted to make sure that I duplicated what I actually shipped my game with here.)
  5. A sort-of-workaround I discovered while compiling the reproduction project—removing the ctc, or setting its position to "nestled" instead of "nestled-close" also avoids the glitch.
The first test case is the most important, but I figured showing some workarounds to this with some different trade-offs might be useful.

(Edit: Screenshots of first two test cases here.)

I'll try to be as brief as I can in describing what I think is happening here and the implications of this:

Basically… it seems that as Ren'Py displays text with {w} tags, it does not merely hide the text that hasn't appeared yet, but actually doesn't include it in the text sent to its word wrapping algorithm, and rewraps and rerenders blocks of text in their entirety as more sections of them become visible.

Under normal circumstances, this would not cause any visual side effects, since this is merely adding more text to the end of what's already visible—something which, in a greedy word wrapping algorithm, is effectively guaranteed to never impact how earlier lines of text are wrapped. However, Ren'Py does not use a greedy word wrapping algorithm. Instead, by default, it word wraps text using the same algorithm as TeX, which attempts to even out the length of lines.

I don't quite understand exactly how this works, but I do understand one of the main consequences of this. In a greedy word wrapping algorithm, lines will only wrap differently when they or a line before them is altered—the entire point of something like TeX's algorithm, however, is to let lines of text influence each others' contents in more elaborate ways.

In this case, I imagine that means this—if including the text after a {w} tag significantly alters how the length of lines in a block of text are distributed, this can lead to text moving around during gameplay, since later lines can impact how earlier lines are wrapped, even if nothing in those earlier lines has changed. I strongly suspect that's what's happening here.

In addition, that isn't the only problem the {w} tag in this line of dialogue exacerbates. Ren'Py seems to adjust the spacing between letters at downscaled resolutions based on how long a given piece of text is, and {w} tags splitting text apart like this seems to repeatedly change the letter spacing algorithm's idea of what it needs to fit into what. This line of dialogue seems to be particularly hard on that, and at sub-1080p resolutions, the letters quite visibly squish together after the pause. (I'm not sure if this, or anything else about this issue, is DPI or OS dependent though.)

The reason I settled for pausing with {cps} as a workaround is that I knew this would force the engine to pause the text while treating it as a single unit—rendering and word wrapping each line of dialogue only once, and pausing by making the typing effect slow down at a given point. And given that switching to {cps} solved all of these problems with no additional work, I can't help but think that I'm on the right track here.

Personally, if this is the case, I think the {w} tag should be altered to behave as similarly to this workaround as possible. However, it is possible Ren'Py has some safeguards to prevent text from shifting around like this during gameplay that my specific setup caused to malfunction, and that's one of the reasons I am including as many details as possible.

(Edit: Further experimentation reveals this is likely the case. Feeding contrived strings filled with {w} tags to the "subtitle" word wrapping algorithm reveals that Ren'Py is does indeed try to take into account text that is hidden by {w} tags when word wrapping.

…however, it does not always succeed. During this, I discovered another line of dialogue that will cause rewrap. Under the same conditions mentioned before, but using "subtitle" word wrapping, this text will rewrap at the second {w=0.1}:

Code: Select all

"Long text long text long text long text long text long text long text long text long text long text.{w=0.1} Short text.{w=0.1} Long text long text long text long text long text long text long text long text long text long text."
Unlike before, setting the ctc position to "nestled" accomplishes nothing, but setting it to "fixed" or removing the ctc entirely does fix it.

If anyone thinks it would help, I can contrive another reproduction project with these test cases.)

Anyway, if anyone could take a look at my reproduction project and determine exactly what Ren'Py's text displaying code is doing with this line of dialogue, I would appreciate that :p
Reproduction project
(732.14 KiB) Downloaded 8 times

User avatar
Ren'Py Creator
Posts: 15481
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY

Re: {w} tag and text shifting during gameplay

#2 Post by PyTom » Sat Nov 02, 2019 11:45 pm

This is a known issue, and documented. It's a damned if you do, damned if you don't situation. Look at adjust_spacing for the solution that lets you pick.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
"Silly and fun things are important." - Elon Musk
Software > Drama • https://www.patreon.com/renpytom

User avatar
Posts: 4
Joined: Fri Nov 09, 2018 12:27 am
Deviantart: TanteiSakana
itch: TanteiSakana

Re: {w} tag and text shifting during gameplay

#3 Post by TanteiSakana » Sun Nov 03, 2019 1:51 pm

…perhaps you could explain to me how this is a "damned if you do, damned if you don't situation"? Because I have trouble understanding that line of thought.

In trying to work around the various issues happening here, I did read about and experiment with the adjust_spacing style property. When I did so, the situation was exactly how you described—I ran into a clearly documented "damned if you do, damned if you don't situation," where I had to choose between text instability and text layout problems.

However, this was only a single part of a single workaround I tried. As soon as I discovered adjust_spacing left me in this kind of situation, I tried to find a workaround that did not involve using it… and I succeeded.

There seem to be a number of issues interacting here, with the letter spacing adjustment that adjust_spacing controls being one small part of them.

The root problem here seems to me that the {w} tag is one of a small handful of text tags that divides text into "segments," and that the way Ren'Py handles these segments can lead to unstable text in certain situations.

To me, there appears to be no benefit to dividing text into "segments" like this.

The workaround I shipped my game with—replacing all timed {w} tags with the equivalent {cps} tags—works entirely by avoiding this segment system without changing anything else whatsoever. And while pausing with {cps} is slightly inconvenient for me, from the player's perspective, it appears to avoid all of the text display problems I have mentioned with 100% effectiveness and no downsides whatsoever—a situation I would definitely not describe as "damned if you do, damned if you don't."

This does not appear to be known and documented, and I think it is worth discussing the implications of this working, and whether some or all of the benefits of this provides can be worked into Ren'Py internally.

While having a solution that "lets you pick" is nice, I think I have good reasons to believe that this workaround demonstrates that there are other solutions to at least some of these problems—solutions that could avoid most of these issues without anyone having to manually change the adjust_spacing style property or even be aware it exists.

Post Reply

Who is online

Users browsing this forum: No registered users