I7 Line spacing rules EXPLAINED (with quick reference chart)

Since the rules around “paragraph control” are an enduring mystery and the subject of frequent posts on the forum, I spent some time analyzing the system. This post should be everything you need to know to get the line spacing behavior that you seek without unwanted surprises.

(Note that the following was derived via testing in 6M62, but 10.1 should behave largely the same.)

Reasons that line breaks are generated

There are three sources of line breaks in text output:

  1. invocation of paragraph control text substitutions, per logic defined in the Standard Rules

  2. automatic injection after text segments ending with ./!/? (or grammatical equivalent), per logic defined in I7 compiler code

  3. automatic injection before evaluation of a rule (standalone or within a rulebook) after a say statement lacking certain substitutions has occurred, per logic defined in I7 template code

It is a complex system that is difficult to explain briefly. However, if an author is more interested in what the system does instead of why it does those things, then the rules can be laid out in a fairly compact way – which is what’s done in the table at the end of this post.

Core concepts

Text segments

The most important concept to understand is the idea of a text segment. Every say statement is composed of one or more text segments. A new segment is generated for any contiguous run of one or more alphanumeric and/or white space characters within a text (called strings here), or for any single substitution within the text.

Automatic punctuation line breaks

When the I7 compiler is translating a say statement into I6, it looks at the end of every text segment being created for a string. If the last character(s) of the string are sentence-ending punctuation (i.e. period, question mark or exclamation mark – or one of these followed by a close quote), then the I7 compiler adds a line break at the end of the segment… unless it is overridden by a paragraph control phrase, as described next.

Paragraph control substitutions

Some substitutions are designated as being for paragraph control. These are defined in “Section SR5/1/5 - Saying - Paragraph control” of the Standard Rules. They are: [line break] (LB), [no line break] (NLB), [run paragraph on] (RPO), [paragraph break] (PB), [conditional paragraph break] (CPB) and [run paragraph on with special look spacing] (RPOWSLS).

The to say... phrases for these substitutions are all specified in such a way that they cancel an automatic punctuation line break for any text segment that precedes them within the same say statement. They have no effect on a segment at the end of a previous say statement.

Other substitutions

No other substitution is capable of canceling an automatic punctuation line break. This includes those related to conditions ([if], [otherwise], [else], [end if]), those related to [one of] constructions, etc.

Author-supplied substitutions can be set up to cancel automatic punctuation line breaks, but only if they do not accept parameters.

The say__p and PARA_CONTENTEXPECTED flags

The paragraph control system tracks many boolean flags. The most important of these is say__p, which takes the form of an I6 global variable. Every say statement at the I7 level sets the say__p flag as its first effect – prior to any code related to text segments. This occurs even for say statements that include no text at all (such as say no line break;).

A significant secondary flag is called PARA_CONTENTEXPECTED. At the start of each text segment (whether string or substitution), a routine is run that checks the state of PARA_CONTENTEXPECTED. If it sees this flag set, the routine will set say__p and clear PARA_CONTENTEXPECTED.

Many paragraph control phrases clear say__p. Some paragraph control phrases clear say__p but also set PARA_CONTENTEXPECTED. If one of these occurs as the last segment of a say statement, say__p will be clear at the end of that say statement.

Pre-rule and inter-rule line breaks

Whenever a rule is about to be processed (either standalone or as part of a rulebook), then the state of say__p is checked. If the flag is set, then a line break is printed and the flag is cleared. Line breaks generated in this manner are here called rulebook breaks.

Rulebook breaks do not run the routine that checks the state of PARA_CONTENTEXPECTED. While processing rules, the first generated rulebook break will clear say__p, and it cannot be set again unless one of the rules executes a say statement.

EDIT: In trying to simplify this section, I went a bit too far. There is a significant distinction in default behavior for rulebooks that depends on whether or not the rulebook has a parameter, i.e. is “based” on something other than an action (the implicit default) or explicitly nothing. (See WWI 19.9 Basis of a rulebook for more.) For an <X> based rulebook of any other kind other than these two, the default is for all rulebook breaks to be skipped.

Summary table

This table tries to condense all of the above into a visual quick reference:

								TABLE 1: AUTHOR-VISIBLE EFFECTS OF PHRASES

										LB		NLB		RPO		PB		CPB		CCB		RPOWSLS		other
unconditional new_line?					+		-		-		+		-		+		-			-
conditional new_line?					-		-		-		+p		+p		-		-			-
overrides prev segment punct break?		+		+		+		+		+		+		+			-
suppresses rulebook breaks?				-		-		+		Lp		Lp		+		+			-


EFFECT KEY:

		+   = always
		-   = never
		L   = only if occurring at end of most recent say statement
		p	= effect applies only when say__p is set at start of segment

Special credit to @neroden, who outlined the idea of text segments in Nathanael Nerode’s Cookbook (https://raw.githubusercontent.com/i7/extensions/10.1/Nathanael%20Nerode/Nathanael’s%20Cookbook-v6.i7x), and to @drpeterbatesuk, who worked out the effect of the undocumented -- running on designation for say phrases (Trouble with paragraph breaks - #8 by drpeterbatesuk). Any errors in the above are mine.

Good, albeit hard to follow (because it’s inherently hard to follow) work.

In practice I have four kinds of line break problems that recur.

85% are elementary fixes when I see a missing line or an extra line during a big blob of text. One change and it’s fixed.

5% are caused by rulebooks producing extra lines, which can be tedious to work around.

5% are headachey things where many different mechanisms want to share a piece of text, and depending on which mechanism was used, the shared piece of text may appear correctly or not.

5% are black magic moments around the edges of weird stuff where no matter what I try, I end up with either no line break or two line breaks. At such times I try using Conditional Paragraph Break, and sometimes it’s the one magic trick that gets the spacing right. I can see from your column why that probably is: PB and CPB are identical except for that + / - difference in the first row.

-Wade

Armed with the above information, some new options are available. For example, there’s nothing to stop one from redefining the phrases for following rulebooks:

To follow (RL - a rule), avoiding rulebook breaks:
	(- FollowRulebook({RL}, nothing, {phrase options}); -).

To follow (RL - a nothing based rule), avoiding rulebook breaks:
	(- FollowRulebook({RL}, nothing, {phrase options}); -).

To follow (RL - value of kind K based rule producing a value) for (V - K), including rulebook breaks:
	(- FollowRulebook({RL}, {V}, (~~{phrase options})); -).

These are backwards-compatible with all Standard Rules. If you write a text-emitting rulebook that produces a value and just want it to behave like a “regular” rulebook with respect to line breaks, you can now say:

follow the myspecialrules rules, including rulebook breaks;

Want your “regular” rulebook that doesn’t produce anything to stop emitting stray line breaks when it is called? Just say:

follow the myquietrules rules, avoiding rulebook breaks;

I’ve used definitions like that when rulebook breaks were getting under my skin.

The element of the system that really gripes me:

Whenever a rule is followed (either standalone or as part of a rulebook), then the state of say__p is checked. If the flag is set, then a line break is printed and the flag is cleared. Line breaks generated in this manner are here called rulebook breaks .

We could surely design a logically equivalent system that doesn’t print a linebreak here but merely keeps track of how many line breaks it should print before the next say statement. (That is, delay rulebook breaks until the next say.)

Then (a) we would never print newlines at inopportune times (like when there is no Glk stream active); (b) you could always squash rulebook breaks at print time, rather than having to use customized forms of FollowRulebook.

How does say__pc fit in? If you ever want to truly avoid all rulebook breaks you have to do something like this:

! Run the glk event handling rules (but disable rules debugging because it crashes if keyboard input events are pending)
@push debug_rules; @push say__p; @push say__pc;
debug_rules = false; ClearParagraphing(1);
FollowRulebook(GLK_EVENT_HANDLING_RB, Glk_Event_Struct_type, true);
@pull say__pc; @pull say__p; @pull debug_rules;

It would be nice if there could be just one value to push/pull, but that’s unlikely to be something that we could change. (Unless they’re single flags, then perhaps they could be combined into a bitfield. But Zarf’s idea of actually counting would be even better, and probably precludes a bitfield (unless we split a 32bit word into parts.))

say__pc is a bitmap that is used to track five flags:

  • PARA_COMPLETED
  • PARA_PROMPTSKIP
  • PARA_SUPPRESSPROMPTSKIP
  • PARA_NORULEBOOKBREAKS
  • PARA_CONTENTEXPECTED

There is an explanation of these in the template files, but based on an inspection of the actual template code, the explanation seems to be at least partly out-of-date.

Rulebook breaks are caused solely by routine RulebookParBreak(), which is a simple routine that conditionally calls DivideParagraphPoint() (aka DPP). DPP has the logic that tries to determine whether a line break is appropriate at that point in the text. It only prints a line break in response to say__p being set, as described above – the state of say__pc is altered by DPP but does not directly affect its choice.

Yes, this approach would make a lot of sense to me. It seems like each say statement should:

  1. process any pending line break requests
  2. emit its own text, if any
  3. make a request for zero to two line breaks to follow it before additional text

The default request in step 3 would be for zero following line breaks. The same compiler logic that currently checks for sentence-terminating punctuation could stay in place, but instead of injecting a line break it could inject a statement to indicate a request for two following line breaks.

Certain situations (like printing command clarifications or room names) would want a request for one following line break. This could be called [single break] (one conditional line break). The current [command clarification break] could be a synonym.

Authors would also want phrases to execute immediate, mid-say line breaks – to me, these would be [line break] (one unconditional line break) and [paragraph break] (two unconditional line breaks).

It would seem fine to keep [run paragraph on] and have it mean “change the request for following line breaks to zero,” but I think it would still function as desired without doing anything at the I6 level, simply by preventing the say statement from terminating with sentence-ending punctuation. (Likewise [no line break].)

Add logic to erase the current following lines request, and it is easy to cover the special situation of ensuring a blank line before the command prompt. The “special look spacing” case might be handled as easily as First carry out going: say single break.

I wanted to try to put a proof-of-concept of this together, but it depends on changes to compiler logic. It wouldn’t surprise me if this theoretical design had shortcomings when it came to actual application – particularly with respect to how it would interact with printing happening in template code or inclusions.

Yes, it will certainly be a case of “prove your code logically equivalent to the old system, then run the I7 test suite and watch logic start weeping.”

On the topic of say__pc, one thing that I’ve noticed is that the substitution [command clarification break] indirectly invokes the I6 routine ClearParagraphing(), which zeroes the say__pc global and therefore clears all of its bitmap flags.

It’s not clear to me why this behavior would be desirable, as it affects flags that are applicable to edge case behavior (the “going look break” and ensuring a blank line before the command prompt) that do not seem like they should be affected by that substitution. I have a suspicion that it is a vestigial call left over from an earlier era of the paragraph control system.

The following definition is an attempt to prevent any unexpected side effects:

To say command clarification break -- running on
	(documented at phs_clarifbreak):
	(- new_line; RunParagraphOn(); -).

It has not been extensively tested; but I have not seen problems in basic testing.

I suspect that [command clarification break] is, in practice, always followed by action output. Any edge cases that come up would look weird anyway. (E.g if the action prints nothing at all, neither success nor failure.)

Since this post has been getting some attention lately, I went back over it myself. It’s actually pretty incomprehensible, isn’t it? Although it made sense to me at the end of about two weeks of studying the underlying code, the “explanation” is phrased entirely in that code’s paradigm and not well-related to what authors should have to care about.

I should note that I subsequently learned that Dialog’s system of paragraph control seems similar to the set of rules I had tried to derive from first principles, so that system is worth looking at as a working example of something that seems more consistent and author-friendly.

To make this thread more useful, it makes sense to try to provide some examples of code paired with output behavior, and try to build some rules that would make sense to someone who cares nothing about the underlying code. I’ll post some examples as they occur to me, but if anyone has some puzzling examples from their own work, please post them here, and I’ll try to explain.


Example 1: Sentence-ending punctuation within rules versus across rules

Here’s a simple example:

When play begins:
	say "One.";
	say "Two."

When play begins:
	say "Three."

The output of these is:

One.
Two.

Three.

(GAME HEADER)

Right away, from a naive perspective something looks broken. Why is there only one line break after the period in One. but two line breaks after the periods in Two. and Three.?

For reference, this is the underlying generated code (in 6M62):

! ----------------------------------------------------------------------------------------------------
! Rules in rulebook: When play begins (B4_when_play_begins)
! ----------------------------------------------------------------------------------------------------
! Rule 1/2 ! When play begins:
!   === equally specific with ===
! Rule 2/2 ! When play begins:
! ----------------------------------------------------------------------------------------------------
! No specific request
! When play begins:
[ R_799 ;
	if (debug_rules) DB_Rule(R_799, 799);
	! [2: say ~One.~]
	say__p=1;! [3: ~One.~]
	ParaContent(); print "One."; new_line; .L_Say1; .L_SayX1;! [4: say ~Two.~]
	say__p=1;! [5: ~Two.~]
	ParaContent(); print "Two."; new_line; .L_Say2; .L_SayX2;rfalse;
];
! No specific request
! When play begins:
[ R_800 ;
	if (debug_rules) DB_Rule(R_800, 800);
	! [2: say ~Three.~]
	say__p=1;! [3: ~Three.~]
	ParaContent(); print "Three."; new_line; .L_Say3; .L_SayX3;rfalse;
];

The two say statements in the first rule both generate one line break each (via new_line;), because each creates its own string which ends in a segment that ends with sentence-ending punctuation. Each also sets say__p, redundantly in the case of say "Two.".

Because say__p is set, when the when play begins rulebook continues to the next rule, it inserts an extra line break after Two.. The same happens again when the startup rulebook moves on from the when play begins stage rule (or its way to the display banner rule) after printing Three.

If we assume that the desired output is:

One.

Two.

Three.

(GAME HEADER)

then here are some possible alternatives:

Variant A: Collapse say statements to one string and make line spacing explicit

When play begins:
	say "One.[paragraph break]Two."

This works because the single say statement creates only one string with three segments (i.e. some text, a text substitution and some text), and the automation around sentence-ending punctuation applies only to the last segment in the string. The manual specification of a paragraph break in this context produces the naively expected result.

Variant B: Request extra line breaks as needed

When play begins:
	say “One.”;
	say line break;
	say “Two.”

This works because each say statement translates to its own string with one segment. The string producing One. has sentence-ending punctuation, so it generates its own single line break. Manually requesting another single line break results in two consecutive line breaks.

Variant C: Separate rules for each output

When play begins:
	say "One."

When play begins:
	say "Two."

When play begins:
	say "Three."

This works because each rule produces a string whose final segment ends with sentence-ending punctuation, each of which both inserts a single line break and sets the say__p flag, and because each each rule transition reacts to the set say__p flag by producing another (second) single line break.


Example 2: Complex text substitutions backed by special-purpose to say phrases

This pattern pops up with some regularity as authors try to build reactive descriptions: The contents of a description or initial property are linked to a to say phrase with logic to vary the text produced.

Field of Flowers is a room. "A vast field of flowers, extending to the horizon in all directions."

A neuter animal called a butterfly is here. "[butterfly business]"

The butterfly has a number called business counter. The business counter of the butterfly is 1.

To say butterfly business:
	if business counter of butterfly is:
		-- 1: say "A butterfly dances in the breeze here.";
		-- 2: say "The butterfly alights briefly on one of the flowers, slowing flexing its wings.";
		-- 3: say "The butterfly leaps into the air again, fluttering about.";
	unless expanding text for comparison purposes:
		increment business counter of butterfly;
		if business counter of butterfly is at least 4, now business counter of butterfly is one.

This yields output:

Field of Flowers
A vast field of flowers, extending to the horizon in all directions.

A butterfly dances in the breeze here.


>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.

The butterfly alights briefly on one of the flowers, slowing flexing its wings.


>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.

The butterfly leaps into the air again, fluttering about.


>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.

A butterfly dances in the breeze here.


>

Aside from the fact that there are quite a few snares around this pattern due to nuances of the way Inform works under the hood, the example above basically works as naively expected except for the presence of extra line breaks before the command prompt.

There’s a hidden part of the puzzle in the Standard Rules:

For printing a locale paragraph about a thing (called the item)
	(this is the use initial appearance in room descriptions rule):
	if the item is not mentioned:
		if the item provides the property initial appearance and the
			item is not handled and the initial appearance of the item is not "":
			increase the locale paragraph count by 1;
			say "[initial appearance of the item]";
			say "[paragraph break]";
			if a locale-supportable thing is on the item:
				repeat with possibility running through things on the item:
					now the possibility is marked for listing;
					if the possibility is mentioned:
						now the possibility is not marked for listing;
				say "On [the item] " (A);
				list the contents of the item, as a sentence, including contents,
					giving brief inventory information, tersely, not listing
					concealed items, prefacing with is/are, listing marked items only;
				say ".[paragraph break]";
			now the item is mentioned;
	continue the activity.

The reason that the extra line breaks occur are:

  1. The room description machinery tries to say the initial appearance property of the butterfly. This generates a string with only one segment, the text substitution [butterfly business], which does not register as ending with sentence-ending punctuation.
  2. The [butterfly business] text substitution invokes a routine which has its own internal say statements that generate single-segment strings, and these do end with sentence-ending punctuation. These underlying say phrases automatically generate single line breaks when the to say butterfly business phrase is executed, which are not generated when the initial appearance is a simple text string.
  3. The forced [paragraph break] in the use initial appearance in room descriptions rule adds another two line breaks, which is one too many.

It’s not possible to unset say__p in a meaningful way, since the say statement requesting a paragraph break will reset it before deciding how many line breaks to generate. Instead, the problem must be addressed at the point where the initial appearance property is being printed.

When a plain text initial appearance is printed in the normal way, sentence-ending punctuation is ignored. (The code just prints the characters in the text property.) However, when a text substitution is invoked, a routine is run, and here the compiler has paid attention to sentence-ending punctuation, and has hardcoded some line breaks as a result:

! Request 46: phrase nothing -> nothing
! To say butterfly business:
[ PHR_799_r46  ;
	! [2: if business counter of butterfly is]
	switch (GProperty(10, I126_butterfly,p15_business_counter)) 
	{1:
	    ! [3: say ~A butterfly dances in the breeze here.~]
	    say__p=1;! [4: ~A butterfly dances in the breeze here.~]
	    ParaContent(); print "A butterfly dances in the breeze here."; new_line; .L_Say464; .L_SayX462;;
	    2:
	    ! [5: say ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.~]
	    say__p=1;! [6: ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.~]
	    ParaContent(); print "The butterfly alights briefly on one of the flowers, slowing flexing its wings."; new_line; .L_Say465; .L_SayX463;;
	    3:
	    ! [7: say ~The butterfly leaps into the air again, fluttering about.~]
	    say__p=1;! [8: ~The butterfly leaps into the air again, fluttering about.~]
	    ParaContent(); print "The butterfly leaps into the air again, fluttering about."; new_line; .L_Say466; .L_SayX464;}
	! [9: unless expanding text for comparison purposes]
	if (~~((say__comp)))
	{! [10: increment business counter of butterfly]
	    WriteGProperty(10, I126_butterfly,p15_business_counter,GProperty(10, I126_butterfly,p15_business_counter) + 1);;
	    ! [11: if business counter of butterfly is at least 4]
	    if (((GProperty(10, I126_butterfly,p15_business_counter) >= 4)))
	    {! [12: now business counter of butterfly is one]
	        WriteGProperty(10, I126_butterfly,p15_business_counter,1);
	        }
	        }
	        rfalse;
];

Assuming that the goal is output:

Field of Flowers
A vast field of flowers, extending to the horizon in all directions.

A butterfly dances in the breeze here.

>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.

The butterfly alights briefly on one of the flowers, slowing flexing its wings.

>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.

The butterfly leaps into the air again, fluttering about.

>

there are some options to make this happen.

Variant A: Suppress line breaks for text output within the special-purpose to say phrase

To say butterfly business:
	if business counter of butterfly is:
		-- 1: say "A butterfly dances in the breeze here.[no line break]";
		-- 2: say "The butterfly alights briefly on one of the flowers, slowing flexing its wings.[run paragraph on]";
		-- 3: say "The butterfly leaps into the air again, fluttering about. ";
	unless expanding text for comparison purposes:
		increment business counter of butterfly;
		if business counter of butterfly is at least 4, now business counter of butterfly is one.

This works differently in the different possible say statements. The first and second variations generates strings that end in segments that are text substitutions, substitutions which tell the compiler not to automatically insert a hardcoded new_line; statement even if it normally would. The third variation works because the last character of the segment is a space, not a period, so the paragraph control logic will not insert a new_line; after the command to print the desired text.

You can see how these play out at the I6 level.

! To say butterfly business:
[ PHR_800_r46  ;
	! [2: if business counter of butterfly is]
	switch (GProperty(10, I126_butterfly,p15_business_counter)) 
	{1:
	    ! [3: say ~A butterfly dances in the breeze here.[no line break]~]
	    say__p=1;! [4: ~A butterfly dances in the breeze here.~]
	    ParaContent(); print "A butterfly dances in the breeze here.";! [5: no line break]
	    ParaContent(); (PHR_350_r2 ()); .L_Say464; .L_SayX462;;
	    2:
	    ! [6: say ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.[run paragraph on]~]
	    say__p=1;! [7: ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.~]
	    ParaContent(); print "The butterfly alights briefly on one of the flowers, slowing flexing its wings.";! [8: run paragraph on]
	    ParaContent(); RunParagraphOn(); .L_Say465; .L_SayX463;;
	    3:
	    ! [9: say ~The butterfly leaps into the air again, fluttering about. ~]
	    say__p=1;! [10: ~The butterfly leaps into the air again, fluttering about. ~]
	    ParaContent(); print "The butterfly leaps into the air again, fluttering about. "; .L_Say466; .L_SayX464;}
	! [11: cancel pending paragraph break]
	ClearParagraphing();
	! [12: unless expanding text for comparison purposes]
	if (~~((say__comp)))
	{! [13: increment business counter of butterfly]
	    WriteGProperty(10, I126_butterfly,p15_business_counter,GProperty(10, I126_butterfly,p15_business_counter) + 1);;
	    ! [14: if business counter of butterfly is at least 4]
	    if (((GProperty(10, I126_butterfly,p15_business_counter) >= 4)))
	    {! [15: now business counter of butterfly is one]
	        WriteGProperty(10, I126_butterfly,p15_business_counter,1);
	        }
	        }
	        rfalse;
];

Variant B: Create a say phrase variant

To simply say (T - text):
	say "[T][no line break]".

To say butterfly business:
	if business counter of butterfly is:
		-- 1: simply say "A butterfly dances in the breeze here.";
		-- 2: simply say "The butterfly alights briefly on one of the flowers, slowing flexing its wings.";
		-- 3: simply say "The butterfly leaps into the air again, fluttering about.";
	unless expanding text for comparison purposes:
		increment business counter of butterfly;
		if business counter of butterfly is at least 4, now business counter of butterfly is one.

This works by creating a phrase variant which indicates that text printed should not be followed by an automatic line break. The phrase variant automatically appends the [no line break] substitution to whichever text is printed.

The name of the phrase can be varied as the author likes, e.g. To sayx (T - text):. The important thing is to remember to use it inside your special-purpose to say phrases.

Variant C: Modify the Standard Rules to compensate for unexpected behavior

To clear pending paragraph break:
	(- say__p = 0; -) 

To surreptitiously line break:
	(- new_line; -)

To decide whether (T - text) terminates in sentence-ending punctuation:
	let N be (the number of characters in T) minus one;
	if N is zero, decide no;
	let terminating punctuation be {".", "?", "!"};
	if character number N in T is "[quotation mark]", decrement N;
	if character number N in T is "[apostrophe]", decrement N;
	if character number N in T is listed in terminating punctuation, decide yes;
	decide no.

For printing a locale paragraph about a thing (called the item)
	(this is the alternate use initial appearance in room descriptions rule):
	if the item is not mentioned:
		if the item provides the property initial appearance and the
			item is not handled and the initial appearance of the item is not "":
			increase the locale paragraph count by 1;
			let IA be the substituted form of "[initial appearance of the item]";
			say IA;
			if a paragraph break is pending and IA terminates in sentence-ending punctuation:
				clear pending paragraph break;
				surreptitiously line break;
			otherwise:
				say paragraph break;
			if a locale-supportable thing is on the item:
				repeat with possibility running through things on the item:
					now the possibility is marked for listing;
					if the possibility is mentioned:
						now the possibility is not marked for listing;
				say "On [the item] " (A);
				list the contents of the item, as a sentence, including contents,
					giving brief inventory information, tersely, not listing
					concealed items, prefacing with is/are, listing marked items only;
				say ".[paragraph break]";
			now the item is mentioned;
	continue the activity.

The alternate use initial appearance in room descriptions rule is listed instead of the use initial appearance in room descriptions rule in the for printing a locale paragraph about rules.

This works by detecting situations in which the undesired extra line break will have been generated by a special-purpose to say phrase embedded in an initial appearance routine, and to use some I6 hacks to get the desired line spacing in such cases. It’s pretty costly, from a computational perspective, but if you don’t care about that it may be your preferred solution.

Example 3: Building text using rulebooks

This is another fairly common pattern: The author wishes to use a rulebook to determine part of output text.

Basement is a room.

The machine is a device in Basement. It is switched on.

Every turn when the player can see the switched on machine:
	say "The machine ";
	follow the machine activity rules;
	say "."

Machine activity rules is a rulebook. The machine activity rules have default success.

A machine activity rule:
	say "whirrs and clanks";

A machine activity rule when the remainder after dividing the turn count by 3 is zero:
	say "emits a burst of steam".

This produces:

Basement
You can see a machine here.

>z
Time passes.

The machine 
whirrs and clanks.

>z
Time passes.

The machine 
whirrs and clanks.

>z
Time passes.

The machine 
emits a burst of steam.

>

Obviously, the line breaks are undesirable in this case. None of the say statements in the machine activity rules have sentence-ending punctuation, so these “stray” line breaks are purely the result of rule transitions.

Assuming that what you want is:

Basement
You can see a machine here.

>z
Time passes.

The machine whirrs and clanks.

>z
Time passes.

The machine whirrs and clanks.

>z
Time passes.

The machine emits a burst of steam.

>

then here are some options:

Variant A: Use special invocation of the rulebooks that generate text

As seen in the posts above, you can add:

To follow (RL - a rule), avoiding rulebook breaks:
	(- FollowRulebook({RL}, nothing, {phrase options}); -).

And then:

Every turn when the player can see the switched on machine:
	say "The machine ";
	follow the machine activity rules, avoiding rulebook breaks;
	say "."

This works because the underlying I6 routines allow for an optional flag that is not exposed by the Standard Rules. The addition of the new phrase just makes it possible to set that flag when following a rulebook.

Variant B: Use rules that produce text instead of saying text in the rulebook

Every turn when the player can see the switched on machine:
	say "The machine [text produced by the machine activity rules]."

Machine activity rules is a rulebook producing a text.

A machine activity rule:
	rule succeeds with result "whirrs and clanks";

A machine activity rule when the remainder after dividing the turn count by 3 is zero:
	rule succeeds with result "emits a burst of steam".

This works because the rules do not output any text, only the single say phrase in the every turn rule does. As a result, say__p is not set within rules and does not trigger line breaks on rule transitions.