diff --git a/src/deh_soc.c b/src/deh_soc.c
index 7b4b0ad172d05b8feeca41ddf99c33c3455ffe57..b22e01014e38ad756449866037ed6294be5b1ae2 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -2278,7 +2278,12 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
 			else if (fastcmp(word, "ICONFLIP"))
 				page->iconflip = (i || word2[0] == 'T' || word2[0] == 'Y');
 			else if (fastcmp(word, "LINES"))
-				page->lines = usi;
+			{
+				if (i <= 0)
+					deh_warning("PromptPage %d: line count must be 1 or higher", num);
+				else
+					page->lines = usi;
+			}
 			else if (fastcmp(word, "BACKCOLOR"))
 			{
 				INT32 backcolor = -1;
@@ -2395,8 +2400,9 @@ void readtextprompt(MYFILE *f, INT32 num)
 			{
 				if (1 <= value && value <= MAX_PAGES)
 				{
-					textprompts[num]->page[value - 1].backcolor = 1; // default to gray
-					textprompts[num]->page[value - 1].hidehud = 1; // hide appropriate HUD elements
+					textpage_t *page = &textprompts[num]->page[value - 1];
+					if (page->lines == 0)
+						P_InitTextPromptPage(page);
 					readtextpromptpage(f, num, value - 1);
 				}
 				else
diff --git a/src/doomstat.h b/src/doomstat.h
index e8a475b6f8208f10a4b73757af637c91dc168def..b9a6f07dc2d536ed01984bbefc102c0e5a81c77e 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -257,7 +257,7 @@ typedef struct
 	INT32 backcolor; // see CON_SetupBackColormap: 0-11, INT32_MAX for user-defined (CONS_BACKCOLOR)
 	UINT8 align; // text alignment, 0 = left, 1 = right, 2 = center
 	UINT8 verticalalign; // vertical text alignment, 0 = top, 1 = bottom, 2 = middle
-	UINT8 textspeed; // text speed, delay in tics between characters.
+	SINT8 textspeed; // text speed, delay in tics between characters.
 	sfxenum_t textsfx; // sfx_ id for printing text
 	UINT16 nextprompt; // next prompt to jump to, one-based. 0 = current prompt
 	UINT8 nextpage; // next page to jump to, one-based. 0 = next page within prompt->numpages
diff --git a/src/p_dialog.c b/src/p_dialog.c
index 2ae00da218e7fa4f98e0c84d2bdd08fb248da3c8..ae6b3ecdf942d877e9c8ef21fb7a7a2dd8550972 100644
--- a/src/p_dialog.c
+++ b/src/p_dialog.c
@@ -62,12 +62,15 @@ static void WriterTextBufferAlloc(textwriter_t *writer)
 UINT8 P_CutsceneWriteText(textwriter_t *writer)
 {
 	INT32 numtowrite = 1;
+
 	const char *c;
 
+	const int max_speed = 8;
+
 	if (writer->boostspeed)
 	{
 		// for custom cutscene speedup mode
-		numtowrite = 8;
+		numtowrite = max_speed;
 	}
 	else
 	{
@@ -75,8 +78,8 @@ UINT8 P_CutsceneWriteText(textwriter_t *writer)
 		if (--writer->textcount >= 0)
 			return 1;
 
-		if (writer->textspeed < 7)
-			numtowrite = 8 - writer->textspeed;
+		if (writer->textspeed < max_speed-1)
+			numtowrite = max_speed - writer->textspeed;
 	}
 
 	for (;numtowrite > 0;++writer->baseptr)
@@ -181,6 +184,14 @@ static INT32 P_FindTextPromptSlot(const char *name)
 	return -1;
 }
 
+void P_InitTextPromptPage(textpage_t *page)
+{
+	page->lines = 4; // default line count to four
+	page->textspeed = TICRATE/5; // default text speed to 1/5th of a second
+	page->backcolor = 1; // default to gray
+	page->hidehud = 1; // hide appropriate HUD elements
+}
+
 void P_FreeTextPrompt(textprompt_t *prompt)
 {
 	if (!prompt)
@@ -235,7 +246,7 @@ static void F_GetPageTextGeometry(dialog_t *dialog, dialog_geometry_t *geo)
 	if (dialog->icon[0])
 		iconlump = W_CheckNumForLongName(dialog->icon);
 
-	INT32 pagelines = dialog->page->lines ? dialog->page->lines : 4;
+	INT32 pagelines = dialog->page->lines;
 	boolean rightside = iconlump != LUMPERROR && dialog->page->rightside;
 
 	// Vertical calculations
@@ -597,8 +608,13 @@ static UINT8 P_DialogWriteText(dialog_t *dialog, textwriter_t *writer)
 
 	writer->numtowrite = 1;
 
+	const int max_speed = 8;
+
 	if (writer->boostspeed && !writer->paused)
-		writer->numtowrite = 8;
+	{
+		// When the player is holding jump or spin
+		writer->numtowrite = max_speed;
+	}
 	else
 	{
 		// Don't draw any characters if the count was 1 or more when we started
@@ -607,8 +623,8 @@ static UINT8 P_DialogWriteText(dialog_t *dialog, textwriter_t *writer)
 
 		writer->paused = false;
 
-		if (writer->textspeed < 7)
-			writer->numtowrite = 8 - writer->textspeed;
+		if (writer->textspeed >= 0 && writer->textspeed < max_speed-1)
+			writer->numtowrite = max_speed - writer->textspeed;
 	}
 
 	while (writer->numtowrite > 0)
@@ -636,7 +652,7 @@ static UINT8 P_DialogWriteText(dialog_t *dialog, textwriter_t *writer)
 
 		// Ignore non-printable characters
 		// (that is, decrement numtowrite if this character is printable.)
-		if (lastchar < 0x80)
+		if (lastchar < 0x80 && writer->textspeed >= 0)
 			--writer->numtowrite;
 	}
 
@@ -645,8 +661,8 @@ static UINT8 P_DialogWriteText(dialog_t *dialog, textwriter_t *writer)
 	if (writer->textcount < 0)
 	{
 		writer->textcount = 0;
-		if (writer->textspeed > 7)
-			writer->textcount = writer->textspeed - 7;
+		if (writer->textspeed > max_speed-1)
+			writer->textcount = writer->textspeed - max_speed-1;
 	}
 
 	if (lastchar == '\0' || isspace(lastchar))
@@ -710,13 +726,22 @@ void P_DialogSetText(dialog_t *dialog, char *pagetext, size_t textlength)
 
 	P_ResetTextWriter(writer, dialog->pagetext, dialog->pagetextlength);
 
-	writer->textspeed = dialog->page->textspeed ? dialog->page->textspeed : TICRATE/5;
+	INT32 textspeed = dialog->page->textspeed;
+	if (textspeed >= -1)
+		writer->textspeed = textspeed;
+	else
+		writer->textspeed = TICRATE/5;
+
 	writer->textcount = 0; // no delay in beginning
 	writer->boostspeed = false; // don't print 8 characters to start
 
 	P_DialogSetDisplayText(dialog);
+
+	// Immediately type the first character
+	P_DialogWriteText(dialog, writer);
 }
 
+// TODO: Just remove this? This used to be a wrapper.
 static void P_PreparePageText(dialog_t *dialog, char *pagetext, size_t textlength)
 {
 	P_DialogSetText(dialog, pagetext, textlength);
@@ -727,8 +752,6 @@ static void P_PreparePageText(dialog_t *dialog, char *pagetext, size_t textlengt
 
 static void P_DialogStartPage(player_t *player, dialog_t *dialog)
 {
-	P_PreparePageText(dialog, dialog->page->text, dialog->page->textlength);
-
 	// on page mode, number of tics before allowing boost
 	// on timer mode, number of tics until page advances
 	dialog->timetonext = dialog->page->timetonext ? dialog->page->timetonext : TICRATE/10;
@@ -783,6 +806,9 @@ static void P_DialogStartPage(player_t *player, dialog_t *dialog)
 
 	dialog->iconflip = dialog->page->iconflip;
 
+	// Prepare page text
+	P_PreparePageText(dialog, dialog->page->text, dialog->page->textlength);
+
 	// music change
 	if (P_IsLocalPlayer(player) || globaltextprompt)
 	{
@@ -2290,12 +2316,14 @@ static void InterpretControlCode(char **input, UINT8 **buf, size_t *buffer_pos,
 		char *param = GetControlCodeParam(input);
 		if (param)
 		{
-			int speed = 0;
+			int speed = 1;
 
 			if (StringToNumber(param, &speed))
 			{
 				if (speed < 0)
 					speed = 0;
+
+				speed--;
 			}
 			else
 				EXPECTED_NUMBER("SPEED");
@@ -2459,8 +2487,7 @@ static void ParsePage(textprompt_t *prompt, tokenizer_t *sc)
 
 	textpage_t *page = &prompt->page[prompt->numpages];
 
-	page->backcolor = 1; // default to gray
-	page->hidehud = 1; // hide appropriate HUD elements
+	P_InitTextPromptPage(page);
 
 	prompt->numpages++;
 
@@ -2595,6 +2622,9 @@ static void ParsePage(textprompt_t *prompt, tokenizer_t *sc)
 
 			EXPECT_NUMBER("page 'duration'");
 
+			if (num < 0)
+				num = 0;
+
 			page->timetonext = num;
 
 			EXPECT_TOKEN(";");
@@ -2607,7 +2637,10 @@ static void ParsePage(textprompt_t *prompt, tokenizer_t *sc)
 
 			EXPECT_NUMBER("page 'textspeed'");
 
-			page->textspeed = num;
+			if (num < 1)
+				num = 1;
+
+			page->textspeed = num - 1;
 
 			EXPECT_TOKEN(";");
 		}
@@ -2619,6 +2652,9 @@ static void ParsePage(textprompt_t *prompt, tokenizer_t *sc)
 
 			EXPECT_NUMBER("page 'textlines'");
 
+			if (num < 1)
+				num = 1;
+
 			page->lines = num;
 
 			EXPECT_TOKEN(";");
diff --git a/src/p_dialog.h b/src/p_dialog.h
index 6201a2754f0d1e679925d5711baecaf9fab11d4d..6c94a992c51c24574719c56141366077aee70312 100644
--- a/src/p_dialog.h
+++ b/src/p_dialog.h
@@ -103,6 +103,7 @@ INT32 P_GetPromptPageByName(textprompt_t *prompt, const char *name);
 void P_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum);
 void P_SetMetaPage(textpage_t *page, textpage_t *metapage);
 void P_SetPicsMetaPage(textpage_t *page, textpage_t *metapage);
+void P_InitTextPromptPage(textpage_t *page);
 void P_FreeTextPrompt(textprompt_t *prompt);
 
 char *P_ConvertSOCPageDialog(char *text, size_t *text_length);