0006-Review-NFA-executor-and-add-comprehensive-runtime-tests.txt

text/plain

Filename: 0006-Review-NFA-executor-and-add-comprehensive-runtime-tests.txt
Type: text/plain
Part: 6
Message: Re: Row pattern recognition
From 9d2796ca6cc434ef323e31f60d4a1c9976bfeb1c Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Mon, 9 Feb 2026 12:55:01 +0900
Subject: [PATCH 1/1] Review NFA executor and add comprehensive runtime tests

Review the RPR NFA executor code, fix issues found, and add a
comprehensive runtime test suite (rpr_nfa.sql) to validate NFA
behavior at pattern matching boundaries.

Code review fixes in nodeWindowAgg.c:

- Extract nfa_process_row() with three clear phases: match
  (convergence), absorb, and advance (divergence).  Previously these
  phases were interleaved in update_reduced_frame().

- Simplify update_reduced_frame() result registration by separating
  match and mismatch paths into early-return branches, removing the
  combined if-else block.

- Remove nfa_remove_contexts_up_to().  Context cleanup is now handled
  uniformly by nfa_context_free() for both SKIP PAST LAST ROW and
  SKIP TO NEXT ROW modes.

- Rename nfa_state_clone -> nfa_state_create,
  nfa_find_context_for_pos -> nfa_get_head_context for clarity.

- Add nfa_record_context_success/skipped/absorbed statistics helpers.

- Remove unused RPRPattern parameter from nfa_update_absorption_flags().

- Document two FIXME issues found during review:
  (1) altPriority tracks only the last ALT choice, so repeated or
  nested ALTs like (A|B)+ cannot correctly implement SQL standard
  lexical ordering.  A full-path classifier structure is needed.
  (2) Cycle prevention condition (count == 0 && min == 0) is
  insufficient for patterns like (A*)* where cycles occur at
  count > 0.  Currently relies on implicit duplicate detection in
  nfa_add_state_unique.

Code review fixes in rpr.c:

- Remove parentDepth parameter from isUnboundedStart() and
  computeAbsorbabilityRecursive().  Scope boundaries are now
  determined by element depth comparison instead of an explicit
  parent depth parameter.

- Simplify isUnboundedStart(): check simple VAR case first, then
  GROUP case with a depth-bounded loop instead of a FIN-terminated
  traversal with multiple break conditions.

Code review fixes in parse_rpr.c:

- Remove dead frame-type determination code.  RANGE and GROUPS
  frames are already rejected before the start-position check, so
  frameType is always "ROWS".

Test changes:

- Add rpr_nfa.sql: comprehensive NFA runtime tests covering
  quantifier boundaries, alternation priority, nested patterns,
  frame boundary variations, INITIAL mode, pathological patterns,
  context absorption, and FIXME reproduction cases.

- Add nth_value out-of-frame tests, ReScan/LATERAL test, and
  nth_value IGNORE NULLS test to rpr.sql.

- Fix stale comments across rpr.sql, rpr_base.sql, and rpr_nfa.sql:
  remove resolved BUG annotations, update error messages to match
  actual output, correct optimization result descriptions, and
  standardize Expected comment placement to after SQL statements.
---
 src/backend/commands/explain.c            |   12 +-
 src/backend/executor/nodeWindowAgg.c      |  609 ++---
 src/backend/optimizer/plan/rpr.c          |  193 +-
 src/backend/parser/parse_rpr.c            |   10 +-
 src/include/optimizer/rpr.h               |    2 +-
 src/test/regress/expected/rpr.out         |  142 +-
 src/test/regress/expected/rpr_base.out    |   96 +-
 src/test/regress/expected/rpr_explain.out |  549 +++--
 src/test/regress/expected/rpr_nfa.out     | 2524 +++++++++++++++++++++
 src/test/regress/parallel_schedule        |    2 +-
 src/test/regress/sql/rpr.sql              |   88 +-
 src/test/regress/sql/rpr_base.sql         |   92 +-
 src/test/regress/sql/rpr_explain.sql      |   32 +-
 src/test/regress/sql/rpr_nfa.sql          | 1865 +++++++++++++++
 14 files changed, 5445 insertions(+), 771 deletions(-)
 create mode 100644 src/test/regress/expected/rpr_nfa.out
 create mode 100644 src/test/regress/sql/rpr_nfa.sql

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 96542e9538d..c6baa6ca990 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2986,7 +2986,7 @@ deparse_rpr_elements(RPRPattern *pattern, int *idx, StringInfoData *buf,
 					 RPRDepth groupDepth, RPRDepth *prevDepth,
 					 bool *needSpace)
 {
-	List	   *altSeps = NIL;		/* pending alternation separator indices */
+	List	   *altSeps = NIL;	/* pending alternation separator indices */
 
 	while (*idx < pattern->numElements)
 	{
@@ -3047,9 +3047,9 @@ deparse_rpr_group(RPRPattern *pattern, int *idx, StringInfoData *buf,
 	RPRPatternElement *end;
 
 	/*
-	 * Check if this group wraps a single ALT with no siblings.
-	 * Scan from after ALT to END: if no element at childDepth exists,
-	 * the ALT is the sole child.
+	 * Check if this group wraps a single ALT with no siblings. Scan from
+	 * after ALT to END: if no element at childDepth exists, the ALT is the
+	 * sole child.
 	 */
 	if (*idx + 1 < pattern->numElements &&
 		RPRElemIsAlt(&pattern->elements[*idx + 1]))
@@ -3080,7 +3080,7 @@ deparse_rpr_group(RPRPattern *pattern, int *idx, StringInfoData *buf,
 		*needSpace = false;
 	}
 	*prevDepth = childDepth;
-	(*idx)++;						/* consume BEGIN */
+	(*idx)++;					/* consume BEGIN */
 
 	/* Process group children; stops at matching END */
 	deparse_rpr_elements(pattern, idx, buf, begin->depth,
@@ -3101,7 +3101,7 @@ deparse_rpr_group(RPRPattern *pattern, int *idx, StringInfoData *buf,
 	append_rpr_quantifier(buf, end);
 	*prevDepth = end->depth;
 	*needSpace = true;
-	(*idx)++;						/* consume END */
+	(*idx)++;					/* consume END */
 }
 
 /*
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index b2874bf6e9d..b7b87f65b47 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -256,13 +256,17 @@ static void update_reduced_frame(WindowObject winobj, int64 pos);
 static void check_rpr_navigation(Node *node, bool is_prev);
 static bool rpr_navigation_walker(Node *node, void *context);
 
+/* Forward declarations - NFA row processing */
+static void nfa_process_row(WindowAggState *winstate, int64 currentPos,
+							bool hasLimitedFrame, int64 frameOffset);
+
 /* Forward declarations - NFA state management */
 static RPRNFAState *nfa_state_alloc(WindowAggState *winstate);
 static void nfa_state_free(WindowAggState *winstate, RPRNFAState *state);
 static void nfa_state_free_list(WindowAggState *winstate, RPRNFAState *list);
-static RPRNFAState *nfa_state_clone(WindowAggState *winstate, int16 elemIdx,
-									int16 altPriority, int32 *counts,
-									bool sourceAbsorbable);
+static RPRNFAState *nfa_state_create(WindowAggState *winstate, int16 elemIdx,
+									 int16 altPriority, int32 *counts,
+									 bool sourceAbsorbable);
 static bool nfa_states_equal(WindowAggState *winstate, RPRNFAState *s1,
 							 RPRNFAState *s2);
 static bool nfa_add_state_unique(WindowAggState *winstate, RPRNFAContext *ctx,
@@ -275,28 +279,30 @@ static RPRNFAContext *nfa_context_alloc(WindowAggState *winstate);
 static void nfa_unlink_context(WindowAggState *winstate, RPRNFAContext *ctx);
 static void nfa_context_free(WindowAggState *winstate, RPRNFAContext *ctx);
 static RPRNFAContext *nfa_start_context(WindowAggState *winstate, int64 startPos);
-static RPRNFAContext *nfa_find_context_for_pos(WindowAggState *winstate, int64 pos);
+static RPRNFAContext *nfa_get_head_context(WindowAggState *winstate, int64 pos);
 
 /* Forward declarations - NFA statistics */
 static void nfa_update_length_stats(int64 count, NFALengthStats *stats, int64 newLen);
+static void nfa_record_context_success(WindowAggState *winstate, int64 matchLen);
 static void nfa_record_context_failure(WindowAggState *winstate, int64 failedLen);
+static void nfa_record_context_skipped(WindowAggState *winstate, int64 skippedLen);
+static void nfa_record_context_absorbed(WindowAggState *winstate, int64 absorbedLen);
 
 /* Forward declarations - NFA row evaluation */
 static bool nfa_evaluate_row(WindowObject winobj, int64 pos, bool *varMatched);
 
 /* Forward declarations - NFA context lifecycle */
-static void nfa_remove_contexts_up_to(WindowAggState *winstate, int64 endPos,
-									  RPRNFAContext *excludeCtx);
 static void nfa_cleanup_dead_contexts(WindowAggState *winstate, RPRNFAContext *excludeCtx);
+static void nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos);
 
 /* Forward declarations - NFA absorption */
-static void nfa_update_absorption_flags(RPRNFAContext *ctx, RPRPattern *pattern);
+static void nfa_update_absorption_flags(RPRNFAContext *ctx);
 static bool nfa_states_covered(RPRPattern *pattern, RPRNFAContext *older,
 							   RPRNFAContext *newer);
 static bool nfa_try_absorb_context(WindowAggState *winstate, RPRNFAContext *ctx);
 static void nfa_absorb_contexts(WindowAggState *winstate);
 
-/* Forward declarations - NFA execution */
+/* Forward declarations - NFA match and advance */
 static inline bool nfa_eval_var_match(WindowAggState *winstate,
 									  RPRPatternElement *elem, bool *varMatched);
 static void nfa_match(WindowAggState *winstate, RPRNFAContext *ctx,
@@ -309,6 +315,9 @@ static void nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
 static void nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
 							RPRNFAState *state, RPRPatternElement *elem,
 							int64 currentPos, bool initialAdvance);
+static void nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
+							  RPRNFAState *state, RPRPatternElement *elem,
+							  int64 currentPos, bool initialAdvance);
 static void nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
 							RPRNFAState *state, RPRPatternElement *elem,
 							int64 currentPos, bool initialAdvance);
@@ -317,9 +326,6 @@ static void nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
 							int64 currentPos, bool initialAdvance);
 static void nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx,
 						int64 currentPos, bool initialAdvance);
-static void nfa_process_row(WindowAggState *winstate, int64 currentPos,
-							bool hasLimitedFrame, int64 frameOffset);
-static void nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos);
 
 /*
  * Not null info bit array consists of 2-bit items
@@ -4813,6 +4819,7 @@ update_reduced_frame(WindowObject winobj, int64 pos)
 	int			frameOptions = winstate->frameOptions;
 	bool		hasLimitedFrame;
 	int64		frameOffset = 0;
+	int64		matchLen;
 
 	/*
 	 * Check if we have a limited frame (ROWS ... N FOLLOWING). Each context
@@ -4839,7 +4846,7 @@ update_reduced_frame(WindowObject winobj, int64 pos)
 	/*
 	 * Case 2: Find existing context for this pos, or create new one.
 	 */
-	targetCtx = nfa_find_context_for_pos(winstate, pos);
+	targetCtx = nfa_get_head_context(winstate, pos);
 	if (targetCtx == NULL)
 	{
 		/*
@@ -4920,10 +4927,6 @@ update_reduced_frame(WindowObject winobj, int64 pos)
 		 * appropriately as pruned or mismatched.
 		 */
 		nfa_cleanup_dead_contexts(winstate, targetCtx);
-
-		/* Check if target context is now complete */
-		if (targetCtx->states == NULL)
-			break;
 	}
 
 register_result:
@@ -4934,43 +4937,26 @@ register_result:
 	 */
 	if (targetCtx->matchEndRow < targetCtx->matchStartRow)
 	{
-		int64		mismatchLen = targetCtx->lastProcessedRow - targetCtx->matchStartRow + 1;
+		matchLen = targetCtx->lastProcessedRow - targetCtx->matchStartRow + 1;
 
 		register_reduced_frame_map(winstate, targetCtx->matchStartRow, RF_UNMATCHED);
-		nfa_record_context_failure(winstate, mismatchLen);
+		nfa_record_context_failure(winstate, matchLen);
+		nfa_context_free(winstate, targetCtx);
+		return;
 	}
-	else
-	{
-		int64		matchLen = targetCtx->matchEndRow - targetCtx->matchStartRow + 1;
 
-		register_reduced_frame_map(winstate, targetCtx->matchStartRow, RF_FRAME_HEAD);
-		for (int64 i = targetCtx->matchStartRow + 1; i <= targetCtx->matchEndRow; i++)
-		{
-			register_reduced_frame_map(winstate, i, RF_SKIPPED);
-		}
-		winstate->nfaMatchesSucceeded++;
-		nfa_update_length_stats(winstate->nfaMatchesSucceeded,
-								&winstate->nfaMatchLen,
-								matchLen);
-	}
+	/* Match succeeded - register frame map and record statistics */
+	matchLen = targetCtx->matchEndRow - targetCtx->matchStartRow + 1;
 
-	/*
-	 * Cleanup contexts based on SKIP mode.
-	 */
-	if (targetCtx->matchEndRow >= targetCtx->matchStartRow &&
-		winstate->rpSkipTo == ST_PAST_LAST_ROW)
-	{
-		/*
-		 * Remove all contexts with start <= matchEnd, excluding targetCtx
-		 * from skip count
-		 */
-		nfa_remove_contexts_up_to(winstate, targetCtx->matchEndRow, targetCtx);
-	}
-	else
+	register_reduced_frame_map(winstate, targetCtx->matchStartRow, RF_FRAME_HEAD);
+	for (int64 i = targetCtx->matchStartRow + 1; i <= targetCtx->matchEndRow; i++)
 	{
-		/* SKIP TO NEXT ROW or no match: just remove the target context */
-		nfa_context_free(winstate, targetCtx);
+		register_reduced_frame_map(winstate, i, RF_SKIPPED);
 	}
+	nfa_record_context_success(winstate, matchLen);
+
+	/* Remove the matched context */
+	nfa_context_free(winstate, targetCtx);
 }
 
 /*
@@ -5057,6 +5043,82 @@ register_result:
  * allows absorption even when Ctx1 has extra non-absorbable states.
  */
 
+/*
+ * nfa_process_row
+ *
+ * Process all contexts for one row:
+ *   1. Match all contexts (convergence) - evaluate VARs, prune dead states
+ *   2. Absorb redundant contexts - ideal timing after convergence
+ *   3. Advance all contexts (divergence) - create new states for next row
+ */
+static void
+nfa_process_row(WindowAggState *winstate, int64 currentPos,
+				bool hasLimitedFrame, int64 frameOffset)
+{
+	RPRNFAContext *ctx;
+	bool	   *varMatched = winstate->nfaVarMatched;
+
+	/*
+	 * Phase 1: Match all contexts (convergence) Evaluate VAR elements, update
+	 * counts, remove dead states.
+	 */
+	for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
+	{
+		if (ctx->states == NULL)
+			continue;
+
+		/* Check frame boundary - finalize if exceeded */
+		if (hasLimitedFrame)
+		{
+			int64		ctxFrameEnd = ctx->matchStartRow + frameOffset + 1;
+
+			if (currentPos >= ctxFrameEnd)
+			{
+				/* Frame boundary exceeded: force mismatch */
+				nfa_match(winstate, ctx, NULL);
+				continue;
+			}
+		}
+
+		nfa_match(winstate, ctx, varMatched);
+		ctx->lastProcessedRow = currentPos;
+	}
+
+	/*
+	 * Phase 2: Absorb redundant contexts After match phase, states have
+	 * converged - ideal for absorption. First update absorption flags that
+	 * may have changed due to state removal.
+	 */
+	if (winstate->rpPattern->isAbsorbable)
+	{
+		for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
+			nfa_update_absorption_flags(ctx);
+
+		nfa_absorb_contexts(winstate);
+	}
+
+	/*
+	 * Phase 3: Advance all contexts (divergence) Create new states
+	 * (loop/exit) from surviving matched states.
+	 */
+	for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
+	{
+		if (ctx->states == NULL)
+			continue;
+
+		/*
+		 * Phase 1 already handled frame boundary exceeded contexts by forcing
+		 * mismatch (nfa_match with NULL), which removes all states (all states
+		 * are at VAR positions after advance). So any surviving context here
+		 * must be within its frame boundary.
+		 */
+		Assert(!hasLimitedFrame ||
+			   currentPos < ctx->matchStartRow + frameOffset + 1);
+
+		nfa_advance(winstate, ctx, currentPos, false);
+	}
+}
+
 /*
  * nfa_state_alloc
  *
@@ -5109,33 +5171,32 @@ nfa_state_free(WindowAggState *winstate, RPRNFAState *state)
 /*
  * nfa_state_free_list
  *
- * Free all states in a list using pfree.
+ * Return all states in a list to the free list.
  */
 static void
 nfa_state_free_list(WindowAggState *winstate, RPRNFAState *list)
 {
-	RPRNFAState *state;
+	RPRNFAState *next;
 
-	while (list != NULL)
+	for (; list != NULL; list = next)
 	{
-		state = list;
-		list = list->next;
-		nfa_state_free(winstate, state);
+		next = list->next;
+		nfa_state_free(winstate, list);
 	}
 }
 
 /*
- * nfa_state_clone
+ * nfa_state_create
  *
- * Clone a state with given elemIdx, altPriority and counts.
+ * Create a new state with given elemIdx, altPriority and counts.
  * isAbsorbable is computed immediately: inherited AND new element's flag.
  * Monotonic property: once false, stays false through all transitions.
  *
  * Caller is responsible for linking the returned state.
  */
 static RPRNFAState *
-nfa_state_clone(WindowAggState *winstate, int16 elemIdx, int16 altPriority,
-				int32 *counts, bool sourceAbsorbable)
+nfa_state_create(WindowAggState *winstate, int16 elemIdx, int16 altPriority,
+				 int32 *counts, bool sourceAbsorbable)
 {
 	RPRPattern *pattern = winstate->rpPattern;
 	int			maxDepth = pattern->maxDepth;
@@ -5165,13 +5226,19 @@ nfa_state_clone(WindowAggState *winstate, int16 elemIdx, int16 altPriority,
 static bool
 nfa_states_equal(WindowAggState *winstate, RPRNFAState *s1, RPRNFAState *s2)
 {
-	int			maxDepth = winstate->rpPattern->maxDepth;
+	RPRPattern *pattern = winstate->rpPattern;
+	RPRPatternElement *elem;
+	int			compareDepth;
 
 	if (s1->elemIdx != s2->elemIdx)
 		return false;
 
-	if (maxDepth > 0 &&
-		memcmp(s1->counts, s2->counts, sizeof(int32) * maxDepth) != 0)
+	/* Compare counts up to current element's depth */
+	elem = &pattern->elements[s1->elemIdx];
+	compareDepth = elem->depth + 1;		/* depth 0 needs 1 count, etc. */
+
+	if (compareDepth > 0 &&
+		memcmp(s1->counts, s2->counts, sizeof(int32) * compareDepth) != 0)
 		return false;
 
 	return true;
@@ -5222,6 +5289,19 @@ nfa_add_state_unique(WindowAggState *winstate, RPRNFAContext *ctx, RPRNFAState *
  * Record a matched state following SQL standard semantics.
  * Lexical order (lower altPriority) wins first. Among same lexical order,
  * longer match wins (greedy).
+ *
+ * FIXME: altPriority is a single value that only tracks the last ALT choice.
+ * For patterns with repeated or nested ALTs like (A|B)+, this cannot correctly
+ * implement SQL standard lexical order, which requires comparing the full path
+ * from left to right. For example:
+ *   Pattern: (A | B)+
+ *   Path "A B A" vs "B A B"
+ *   Current: compares last choice (A vs B) → altPriority 10 vs 20
+ *   Correct: should compare first choice (A < B) → "A B A" wins
+ *
+ * A classifier structure tracking the entire ALT path is required for correct
+ * implementation. Without it, patterns with repeated or nested ALTs will
+ * produce incorrect match selection.
  */
 static void
 nfa_add_matched_state(WindowAggState *winstate, RPRNFAContext *ctx,
@@ -5247,6 +5327,49 @@ nfa_add_matched_state(WindowAggState *winstate, RPRNFAContext *ctx,
 		ctx->matchedState = state;
 		state->next = NULL;
 		ctx->matchEndRow = matchEndRow;
+
+		/*
+		 * SKIP PAST LAST ROW: eagerly prune contexts within match range.
+		 *
+		 * This function is called whenever a FIN state is reached, including
+		 * during greedy matching when intermediate (shorter) matches are found.
+		 * Each time we update matchEndRow (whether extending a greedy match or
+		 * finding a new match), we can prune pending contexts that started
+		 * within the current match range.
+		 *
+		 * SKIP PAST LAST ROW uses lexical order (matchStartRow). Therefore,
+		 * any pending context that started at or before matchEndRow can never
+		 * produce a valid output row - it would be skipped anyway per SQL
+		 * standard.
+		 *
+		 * Example (greedy matching in progress):
+		 *   Pattern: START UP+
+		 *   Rows: 1 2 3 4 5
+		 *   Context A starts at row 1:
+		 *     - Matches START UP (rows 1-2) → matchEndRow=2 → prune Context B(row 2)
+		 *     - Matches START UP UP (rows 1-3) → matchEndRow=3 → prune Context C(row 3)
+		 *     - Continues greedy extension while pruning incrementally
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+		{
+			RPRNFAContext *nextCtx;
+			int64		skippedLen;
+
+			while (ctx->next != NULL &&
+				   ctx->next->matchStartRow <= matchEndRow)
+			{
+				nextCtx = ctx->next;
+				ctx->next = ctx->next->next;
+
+				Assert(nextCtx->lastProcessedRow >= nextCtx->matchStartRow);
+				skippedLen = nextCtx->lastProcessedRow - nextCtx->matchStartRow + 1;
+				nfa_record_context_skipped(winstate, skippedLen);
+
+				nfa_context_free(winstate, nextCtx);
+			}
+			if (ctx->next == NULL)
+				winstate->nfaContextTail = ctx;
+		}
 	}
 	else
 	{
@@ -5258,7 +5381,7 @@ nfa_add_matched_state(WindowAggState *winstate, RPRNFAContext *ctx,
 /*
  * nfa_context_alloc
  *
- * Allocate an NFA context from free list or palloc.
+ * Allocate an NFA context, reusing from free list if available.
  */
 static RPRNFAContext *
 nfa_context_alloc(WindowAggState *winstate)
@@ -5351,8 +5474,9 @@ nfa_context_free(WindowAggState *winstate, RPRNFAContext *ctx)
  * nfa_start_context
  *
  * Start a new match context at given position.
- * Initializes context and state absorption flags.
- * Adds context to winstate->nfaContext list and returns the new context.
+ * Initializes context, state absorption flags, and performs initial advance
+ * to expand epsilon transitions (ALT branches, optional elements).
+ * Adds context to the tail of winstate->nfaContext list.
  */
 static RPRNFAContext *
 nfa_start_context(WindowAggState *winstate, int64 startPos)
@@ -5418,28 +5542,24 @@ nfa_start_context(WindowAggState *winstate, int64 startPos)
 }
 
 /*
- * nfa_find_context_for_pos
+ * nfa_get_head_context
  *
- * Find a context with the given start position.
- * Returns NULL if not found.
+ * Return the head context if its start position matches pos.
+ * Returns NULL if no context exists or head doesn't match pos.
  */
 static RPRNFAContext *
-nfa_find_context_for_pos(WindowAggState *winstate, int64 pos)
+nfa_get_head_context(WindowAggState *winstate, int64 pos)
 {
-	RPRNFAContext *ctx;
+	RPRNFAContext *ctx = winstate->nfaContext;
 
 	/*
-	 * List is sorted by matchStartRow ascending (oldest/smallest at head).
-	 * Stop early if we pass the target position.
+	 * Contexts are sorted by matchStartRow ascending.  If the head
+	 * context doesn't match pos, no context exists for this position.
 	 */
-	for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
-	{
-		if (ctx->matchStartRow == pos)
-			return ctx;
-		if (ctx->matchStartRow > pos)
-			break;				/* won't find it, list is sorted */
-	}
-	return NULL;
+	if (ctx == NULL || ctx->matchStartRow != pos)
+		return NULL;
+
+	return ctx;
 }
 
 /*
@@ -5466,6 +5586,20 @@ nfa_update_length_stats(int64 count, NFALengthStats *stats, int64 newLen)
 	stats->total += newLen;
 }
 
+/*
+ * nfa_record_context_success
+ *
+ * Record a successful context in statistics.
+ */
+static void
+nfa_record_context_success(WindowAggState *winstate, int64 matchLen)
+{
+	winstate->nfaMatchesSucceeded++;
+	nfa_update_length_stats(winstate->nfaMatchesSucceeded,
+							&winstate->nfaMatchLen,
+							matchLen);
+}
+
 /*
  * nfa_record_context_failure
  *
@@ -5489,6 +5623,34 @@ nfa_record_context_failure(WindowAggState *winstate, int64 failedLen)
 	}
 }
 
+/*
+ * nfa_record_context_skipped
+ *
+ * Record a skipped context in statistics.
+ */
+static void
+nfa_record_context_skipped(WindowAggState *winstate, int64 skippedLen)
+{
+	winstate->nfaContextsSkipped++;
+	nfa_update_length_stats(winstate->nfaContextsSkipped,
+							&winstate->nfaSkippedLen,
+							skippedLen);
+}
+
+/*
+ * nfa_record_context_absorbed
+ *
+ * Record an absorbed context in statistics.
+ */
+static void
+nfa_record_context_absorbed(WindowAggState *winstate, int64 absorbedLen)
+{
+	winstate->nfaContextsAbsorbed++;
+	nfa_update_length_stats(winstate->nfaContextsAbsorbed,
+							&winstate->nfaAbsorbedLen,
+							absorbedLen);
+}
+
 /*
  * nfa_evaluate_row
  *
@@ -5557,47 +5719,6 @@ nfa_evaluate_row(WindowObject winobj, int64 pos, bool *varMatched)
 	return true;				/* Row exists */
 }
 
-/*
- * nfa_remove_contexts_up_to
- *
- * Remove all contexts with matchStartRow <= endPos.
- * Used by SKIP PAST LAST ROW to discard contexts within matched frame.
- *
- * excludeCtx: if not NULL, this context should not be counted in statistics
- * (typically the matched context that triggered this removal).
- */
-static void
-nfa_remove_contexts_up_to(WindowAggState *winstate, int64 endPos,
-						  RPRNFAContext *excludeCtx)
-{
-	RPRNFAContext *ctx;
-	RPRNFAContext *next;
-
-	/* Contexts are sorted by matchStartRow ascending, so we can stop early */
-	for (ctx = winstate->nfaContext; ctx != NULL; ctx = next)
-	{
-		next = ctx->next;
-		if (ctx->matchStartRow > endPos)
-			break;
-
-		/*
-		 * Track skipped context length statistics, excluding the matched
-		 * context
-		 */
-		if (ctx != excludeCtx && ctx->lastProcessedRow >= ctx->matchStartRow)
-		{
-			int64		skippedLen = ctx->lastProcessedRow - ctx->matchStartRow + 1;
-
-			winstate->nfaContextsSkipped++;
-			nfa_update_length_stats(winstate->nfaContextsSkipped,
-									&winstate->nfaSkippedLen,
-									skippedLen);
-		}
-
-		nfa_context_free(winstate, ctx);
-	}
-}
-
 /*
  * nfa_cleanup_dead_contexts
  *
@@ -5640,6 +5761,27 @@ nfa_cleanup_dead_contexts(WindowAggState *winstate, RPRNFAContext *excludeCtx)
 	}
 }
 
+/*
+ * nfa_finalize_all_contexts
+ *
+ * Finalize all active contexts when partition ends.
+ * Match with NULL to force mismatch, then advance to process epsilon transitions.
+ */
+static void
+nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos)
+{
+	RPRNFAContext *ctx;
+
+	for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
+	{
+		if (ctx->states != NULL)
+		{
+			nfa_match(winstate, ctx, NULL);
+			nfa_advance(winstate, ctx, lastPos, false);
+		}
+	}
+}
+
 /*
  * nfa_update_absorption_flags
  *
@@ -5657,7 +5799,7 @@ nfa_cleanup_dead_contexts(WindowAggState *winstate, RPRNFAContext *excludeCtx)
  * permanently, so we skip recalculation.
  */
 static void
-nfa_update_absorption_flags(RPRNFAContext *ctx, RPRPattern *pattern)
+nfa_update_absorption_flags(RPRNFAContext *ctx)
 {
 	RPRNFAState *state;
 	bool		hasAbsorbable = false;
@@ -5681,9 +5823,6 @@ nfa_update_absorption_flags(RPRNFAContext *ctx, RPRPattern *pattern)
 		return;
 	}
 
-	if (pattern == NULL)
-		return;
-
 	/*
 	 * Iterate through all states to check absorption status. Uses
 	 * state->isAbsorbable which tracks if state is in absorbable region. This
@@ -5800,10 +5939,7 @@ nfa_try_absorb_context(WindowAggState *winstate, RPRNFAContext *ctx)
 			int64		absorbedLen = ctx->lastProcessedRow - ctx->matchStartRow + 1;
 
 			nfa_context_free(winstate, ctx);
-			winstate->nfaContextsAbsorbed++;
-			nfa_update_length_stats(winstate->nfaContextsAbsorbed,
-									&winstate->nfaAbsorbedLen,
-									absorbedLen);
+			nfa_record_context_absorbed(winstate, absorbedLen);
 			return true;
 		}
 	}
@@ -5853,6 +5989,9 @@ static inline bool
 nfa_eval_var_match(WindowAggState *winstate, RPRPatternElement *elem,
 				   bool *varMatched)
 {
+	/* This function should only be called for VAR elements */
+	Assert(RPRElemIsVar(elem));
+
 	if (varMatched == NULL)
 		return false;
 	if (elem->varId >= list_length(winstate->defineVariableList))
@@ -5884,17 +6023,19 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched)
 	RPRPatternElement *elements = pattern->elements;
 	RPRNFAState **prevPtr = &ctx->states;
 	RPRNFAState *state;
+	RPRNFAState *nextState;
 
 	/*
 	 * Evaluate VAR elements against current row. For simple VARs with END
 	 * next, advance to END and update group count inline so absorb phase can
 	 * compare states properly.
 	 */
-	for (state = ctx->states; state != NULL;)
+	for (state = ctx->states; state != NULL; state = nextState)
 	{
-		RPRNFAState *nextState = state->next;
 		RPRPatternElement *elem = &elements[state->elemIdx];
 
+		nextState = state->next;
+
 		if (RPRElemIsVar(elem))
 		{
 			bool		matched;
@@ -5909,15 +6050,8 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched)
 				if (count < RPR_COUNT_MAX)
 					count++;
 
-				/* Check max constraint */
-				if (elem->max != RPR_QUANTITY_INF && count > elem->max)
-				{
-					/* Exceeded max - remove state */
-					*prevPtr = nextState;
-					nfa_state_free(winstate, state);
-					state = nextState;
-					continue;
-				}
+				/* Max constraint should not be exceeded */
+				Assert(elem->max == RPR_QUANTITY_INF || count <= elem->max);
 
 				state->counts[depth] = count;
 
@@ -5926,33 +6060,30 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched)
 				 * and update group count inline. This keeps state in place,
 				 * preserving lexical order.
 				 */
-				if (elem->min == 1 && elem->max == 1 && count == 1 &&
+				if (elem->min == 1 && elem->max == 1 &&
 					RPRElemIsEnd(&elements[elem->next]))
 				{
 					RPRPatternElement *endElem = &elements[elem->next];
 					int			endDepth = endElem->depth;
 					int32		endCount = state->counts[endDepth];
 
+					Assert(count == 1);
+
 					/* Increment group count with overflow protection */
 					if (endCount < RPR_COUNT_MAX)
 						endCount++;
 
-					/* Check END's max constraint */
-					if (endElem->max != RPR_QUANTITY_INF && endCount > endElem->max)
-					{
-						/* Exceeded END's max - remove state */
-						*prevPtr = nextState;
-						nfa_state_free(winstate, state);
-						state = nextState;
-						continue;
-					}
+					/*
+					 * END's max can never be exceeded here because
+					 * nfa_advance_end only loops when count < max,
+					 * so endCount entering inline advance is at most
+					 * max-1, and incrementing yields at most max.
+					 */
+					Assert(endElem->max == RPR_QUANTITY_INF ||
+						   endCount <= endElem->max);
 
 					state->elemIdx = elem->next;
 					state->counts[endDepth] = endCount;
-
-					/* Clear deeper counts */
-					for (int d = endDepth + 1; d < pattern->maxDepth; d++)
-						state->counts[d] = 0;
 				}
 				/* else: stay at VAR for advance phase */
 			}
@@ -5964,14 +6095,12 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched)
 				 */
 				*prevPtr = nextState;
 				nfa_state_free(winstate, state);
-				state = nextState;
 				continue;
 			}
 		}
 		/* Non-VAR elements: keep as-is for advance phase */
 
 		prevPtr = &state->next;
-		state = nextState;
 	}
 }
 
@@ -5993,9 +6122,9 @@ nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
 		{
 			RPRNFAState *skipState;
 
-			skipState = nfa_state_clone(winstate, nextElem->next,
-										state->altPriority, state->counts,
-										state->isAbsorbable);
+			skipState = nfa_state_create(winstate, nextElem->next,
+										 state->altPriority, state->counts,
+										 state->isAbsorbable);
 			nfa_advance_state(winstate, ctx, skipState, currentPos, initialAdvance);
 		}
 	}
@@ -6026,6 +6155,10 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
 		RPRPatternElement *altElem = &elements[altIdx];
 		RPRNFAState *newState;
 
+		/* Stop if element is outside ALT scope (not a branch) */
+		if (altElem->depth <= elem->depth)
+			break;
+
 		if (first)
 		{
 			state->elemIdx = altIdx;
@@ -6035,8 +6168,8 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
 		}
 		else
 		{
-			newState = nfa_state_clone(winstate, altIdx, altIdx,
-									   state->counts, state->isAbsorbable);
+			newState = nfa_state_create(winstate, altIdx, altIdx,
+										state->counts, state->isAbsorbable);
 		}
 
 		/* Recursively process this branch before next */
@@ -6044,8 +6177,8 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
 		altIdx = altElem->jump;
 	}
 
-	if (first)
-		nfa_state_free(winstate, state);
+	/* ALT must have at least one branch */
+	Assert(!first);
 }
 
 /*
@@ -6063,24 +6196,28 @@ nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
 {
 	RPRPattern *pattern = winstate->rpPattern;
 	RPRPatternElement *elements = pattern->elements;
+	RPRNFAState *skipState = NULL;
 
 	state->counts[elem->depth] = 0;
 
-	/* Optional group: create skip path */
+	/* Optional group: create skip path (but don't route yet) */
 	if (elem->min == 0)
 	{
-		RPRNFAState *skipState;
-
-		skipState = nfa_state_clone(winstate, elem->jump, state->altPriority,
+		skipState = nfa_state_create(winstate, elem->jump, state->altPriority,
 									 state->counts, state->isAbsorbable);
-		nfa_route_to_elem(winstate, ctx, skipState,
-						  &elements[elem->jump], currentPos, initialAdvance);
 	}
 
-	/* Enter group: route to first child */
+	/* Enter group: route to first child (lexically first) */
 	state->elemIdx = elem->next;
 	nfa_route_to_elem(winstate, ctx, state,
 					  &elements[state->elemIdx], currentPos, initialAdvance);
+
+	/* Now route skip path (lexically second) */
+	if (skipState != NULL)
+	{
+		nfa_route_to_elem(winstate, ctx, skipState,
+						  &elements[elem->jump], currentPos, initialAdvance);
+	}
 }
 
 /*
@@ -6117,11 +6254,17 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
 		/*
 		 * Must exit: either reached max iterations, or group matched empty.
 		 *
-		 * The second condition (count == 0 && min == 0) prevents infinite
-		 * recursion with patterns like (A*)* where the inner group can match
-		 * empty.  If the group completed without consuming any input
-		 * (count=0) and is optional (min=0), looping back would just repeat
-		 * the empty match forever.  So we force exit instead.
+		 * FIXME: The (count == 0 && min == 0) condition is insufficient for
+		 * cycle prevention. Cycles can occur at any count value when loop back
+		 * happens without consuming rows. For example:
+		 *   Pattern: (A*)*
+		 *   After matching 3 A's (count=3), loop back at a B row
+		 *   Inner A* matches 0 times (skip path) → same (elemIdx, count=3)
+		 *   Infinite cycle at count=3, not count=0
+		 *
+		 * Currently, cycles are silently prevented by nfa_add_state_unique
+		 * detecting duplicate states, but this is implicit and not guaranteed
+		 * for all code paths. Explicit cycle detection is needed.
 		 */
 		RPRPatternElement *nextElem;
 
@@ -6155,8 +6298,8 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
 		 * Create exit state first (need original counts before modifying
 		 * state)
 		 */
-		exitState = nfa_state_clone(winstate, elem->next, exitAltPriority,
-									state->counts, state->isAbsorbable);
+		exitState = nfa_state_create(winstate, elem->next, exitAltPriority,
+									 state->counts, state->isAbsorbable);
 		exitState->counts[depth] = 0;
 		nextElem = &elements[exitState->elemIdx];
 
@@ -6165,7 +6308,6 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
 			exitState->counts[nextElem->depth]++;
 
 		/* Route loop state first (earlier in pattern = lexical order) */
-		state->counts[depth] = count;
 		for (int d = depth + 1; d < pattern->maxDepth; d++)
 			state->counts[d] = 0;
 		state->elemIdx = elem->jump;
@@ -6196,14 +6338,17 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
 	bool		canLoop = (elem->max == RPR_QUANTITY_INF || count < elem->max);
 	bool		canExit = (count >= elem->min);
 
+	/* After a successful match, count >= 1, so at least one must be true */
+	Assert(canLoop || canExit);
+
 	if (canLoop && canExit)
 	{
 		/* Both: clone for loop, modify original for exit */
 		RPRNFAState *loopState;
 		RPRPatternElement *nextElem;
 
-		loopState = nfa_state_clone(winstate, state->elemIdx, state->altPriority,
-									state->counts, state->isAbsorbable);
+		loopState = nfa_state_create(winstate, state->elemIdx, state->altPriority,
+									 state->counts, state->isAbsorbable);
 		nfa_add_state_unique(winstate, ctx, loopState);
 
 		/* Exit: advance to next element */
@@ -6220,7 +6365,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
 	}
 	else if (canExit)
 	{
-		/* Exit only: modify state */
+		/* Exit only: advance to next element */
 		RPRPatternElement *nextElem;
 
 		state->counts[depth] = 0;
@@ -6229,11 +6374,6 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
 
 		nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos, initialAdvance);
 	}
-	else
-	{
-		/* Neither - shouldn't happen, free state */
-		nfa_state_free(winstate, state);
-	}
 }
 
 /*
@@ -6311,106 +6451,3 @@ nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos,
 		nfa_advance_state(winstate, ctx, state, currentPos, initialAdvance);
 	}
 }
-
-/*
- * nfa_process_row
- *
- * Process all contexts for one row using the new flow:
- *   1. Match all contexts (convergence) - evaluate VARs, prune dead states
- *   2. Absorb redundant contexts - ideal timing after convergence
- *   3. Advance all contexts (divergence) - create new states for next row
- */
-static void
-nfa_process_row(WindowAggState *winstate, int64 currentPos,
-				bool hasLimitedFrame, int64 frameOffset)
-{
-	RPRNFAContext *ctx;
-	bool	   *varMatched = winstate->nfaVarMatched;
-
-	/*
-	 * Phase 1: Match all contexts (convergence) Evaluate VAR elements, update
-	 * counts, remove dead states.
-	 */
-	for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
-	{
-		if (ctx->states == NULL)
-			continue;
-
-		/* Check frame boundary - finalize if exceeded */
-		if (hasLimitedFrame)
-		{
-			int64		ctxFrameEnd = ctx->matchStartRow + frameOffset + 1;
-
-			if (currentPos >= ctxFrameEnd)
-			{
-				/*
-				 * Frame boundary: match with NULL (force mismatch), then
-				 * advance
-				 */
-				nfa_match(winstate, ctx, NULL);
-				nfa_advance(winstate, ctx, ctxFrameEnd - 1, false);
-				continue;
-			}
-		}
-
-		nfa_match(winstate, ctx, varMatched);
-		ctx->lastProcessedRow = currentPos;
-	}
-
-	/*
-	 * Phase 2: Absorb redundant contexts After match phase, states have
-	 * converged - ideal for absorption. First update absorption flags that
-	 * may have changed due to state removal.
-	 */
-	if (winstate->rpPattern->isAbsorbable)
-	{
-		RPRPattern *pattern = winstate->rpPattern;
-
-		for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
-			nfa_update_absorption_flags(ctx, pattern);
-
-		nfa_absorb_contexts(winstate);
-	}
-
-	/*
-	 * Phase 3: Advance all contexts (divergence) Create new states
-	 * (loop/exit) from surviving matched states.
-	 */
-	for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
-	{
-		if (ctx->states == NULL)
-			continue;
-
-		/* Skip contexts already finalized in phase 1 */
-		if (hasLimitedFrame)
-		{
-			int64		ctxFrameEnd = ctx->matchStartRow + frameOffset + 1;
-
-			if (currentPos >= ctxFrameEnd)
-				continue;
-		}
-
-		nfa_advance(winstate, ctx, currentPos, false);
-	}
-}
-
-/*
- * nfa_finalize_all_contexts
- *
- * Finalize all active contexts when partition ends.
- * Match with NULL to force mismatch, then advance to process epsilon transitions.
- */
-static void
-nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos)
-{
-	RPRNFAContext *ctx;
-
-	for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next)
-	{
-		if (ctx->states != NULL)
-		{
-			nfa_match(winstate, ctx, NULL);
-			nfa_advance(winstate, ctx, lastPos, false);
-		}
-	}
-}
diff --git a/src/backend/optimizer/plan/rpr.c b/src/backend/optimizer/plan/rpr.c
index 67710a94a0d..112ed034fe2 100644
--- a/src/backend/optimizer/plan/rpr.c
+++ b/src/backend/optimizer/plan/rpr.c
@@ -89,12 +89,10 @@ static void fillRPRPattern(RPRPatternNode *node, RPRPattern *pat,
 static void finalizeRPRPattern(RPRPattern *result);
 
 /* Forward declarations - context absorption */
-static bool isUnboundedStart(RPRPattern *pattern, RPRElemIdx idx,
-							 RPRDepth parentDepth);
+static bool isUnboundedStart(RPRPattern *pattern, RPRElemIdx idx);
 static void computeAbsorbabilityRecursive(RPRPattern *pattern,
 										  RPRElemIdx startIdx,
-										  bool *hasAbsorbable,
-										  RPRDepth parentDepth);
+										  bool *hasAbsorbable);
 static void computeAbsorbability(RPRPattern *pattern);
 
 /*
@@ -1184,13 +1182,13 @@ fillRPRPatternGroup(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth de
 		endElem->min = node->min;
 		endElem->max = (node->max == INT_MAX) ? RPR_QUANTITY_INF : node->max;
 		endElem->next = RPR_ELEMIDX_INVALID;
-		endElem->jump = groupStartIdx;		/* loop to first child */
+		endElem->jump = groupStartIdx;	/* loop to first child */
 		if (node->reluctant >= 0)
 			endElem->flags |= RPR_ELEM_RELUCTANT;
 		(*idx)++;
 
 		/* Set BEGIN skip pointer (next is set by finalize) */
-		beginElem->jump = *idx;				/* skip: go to after END */
+		beginElem->jump = *idx; /* skip: go to after END */
 	}
 }
 
@@ -1439,121 +1437,79 @@ finalizeRPRPattern(RPRPattern *result)
 
 /*
  * isUnboundedStart
- *		Check if the element at idx starts an unbounded sequence.
+ *		Check if the element at idx starts an unbounded greedy sequence.
  *
- * For context absorption to work, the sequence starting at idx must be
- * unbounded (max = infinity) so that we can "shift" by decrementing count.
+ * For context absorption to work, the sequence starting at idx must be:
+ *   - Unbounded (max = infinity)
+ *   - Greedy (not reluctant)
+ *   - At the start of current scope
  *
  * Algorithm:
- *   - Traverse elements within current scope (bounded by parentDepth)
- *   - For GROUP: must be unbounded AND contain only simple {1,1} VARs
- *   - Check if sequence starts with unbounded element (VAR or GROUP END)
+ *   - Traverse elements within current scope (parentDepth to startDepth)
+ *   - For GROUP: must be unbounded greedy AND contain only simple {1,1} VARs
+ *   - Sets ABSORBABLE and ABSORBABLE_BRANCH flags on matching elements
  *
  * Two cases are handled:
- *   1. Simple var: A+ B C - first element A has max=INF
- *   2. Group: (A B){2,} C - group END has max=INF, and all elements
- *      inside the group must be simple {1,1} vars (no nested complexity)
+ *   1. Simple VAR: A+ B C - A has max=INF, gets both flags
+ *   2. Group: (A B)+ C - END has max=INF, all children are {1,1} VARs
+ *      A,B,END get ABSORBABLE_BRANCH, only END gets ABSORBABLE
  *
  * Returns false for patterns where absorption cannot work:
  *   - A B+ (unbounded not at start)
- *   - (A | B){2,} (ALT inside group)
- *   - (A B+){2,} (unbounded element inside group)
- *   - ((A B){2,} C){3,} (nested groups)
+ *   - A+? B (reluctant quantifier)
+ *   - (A | B)+ (ALT inside group)
+ *   - (A B+)+ (unbounded element inside group)
+ *   - ((A B)+ C)+ (nested unbounded groups)
  */
 static bool
-isUnboundedStart(RPRPattern *pattern, RPRElemIdx idx, RPRDepth parentDepth)
+isUnboundedStart(RPRPattern *pattern, RPRElemIdx idx)
 {
 	RPRPatternElement *elem = &pattern->elements[idx];
 	RPRDepth	startDepth = elem->depth;
 	RPRPatternElement *nextElem;
 	RPRPatternElement *e;
 
+	/* Case 1: Simple unbounded VAR at start (greedy only) */
+	if (RPRElemIsVar(elem) && elem->max == RPR_QUANTITY_INF &&
+		!RPRElemIsReluctant(elem))
+	{
+		/* Set both flags on first element */
+		elem->flags |= RPR_ELEM_ABSORBABLE_BRANCH | RPR_ELEM_ABSORBABLE;
+		return true;
+	}
+
 	/*
-	 * Traverse elements until we exit the current scope. For groups, check if
-	 * all elements inside are simple {1,1} vars. Groups with complex
-	 * quantifiers (e.g., (A+ B)+) are not absorbable. Uses parentDepth as
-	 * upper boundary to detect scope exit.
+	 * Case 2: Unbounded GROUP - traverse siblings at startDepth and check if
+	 * they're all simple {1,1} VARs, then check if END at startDepth - 1 is
+	 * unbounded greedy.
 	 */
-	for (e = elem; !RPRElemIsFin(e); e = nextElem)
+	for (e = elem; e->depth == startDepth; e = nextElem)
 	{
-		/* Check for unbounded END at enclosing depth */
-		if (e->depth < startDepth)
-		{
-			/* Case 2: Unbounded group - END element points back to idx */
-			if (e->depth == startDepth - 1 &&
-				RPRElemIsEnd(e) && e->max == RPR_QUANTITY_INF &&
-				!RPRElemIsReluctant(e))
-			{
-				Assert(e->jump == idx); /* END-based break ensures this */
-
-				/*
-				 * Set ABSORBABLE_BRANCH on all elements, ABSORBABLE on END
-				 * only
-				 */
-				for (e = elem; !RPRElemIsEnd(e); e = &pattern->elements[e->next])
-					e->flags |= RPR_ELEM_ABSORBABLE_BRANCH;
-				e->flags |= RPR_ELEM_ABSORBABLE_BRANCH | RPR_ELEM_ABSORBABLE;
-				return true;
-			}
-			break;
-		}
-
-		/* Element must be simple {1,1} VAR */
-		if (e->depth == startDepth &&
-			(!RPRElemIsVar(e) || e->min != 1 || e->max != 1))
-			break;
+		/* Must be simple {1,1} VAR */
+		if (!RPRElemIsVar(e) || e->min != 1 || e->max != 1)
+			return false;
 
-		/* Next must be valid (will break before reaching FIN) */
 		Assert(e->next != RPR_ELEMIDX_INVALID);
 		nextElem = &pattern->elements[e->next];
-
-		/* Break if next element exits current scope */
-		if (nextElem->depth < parentDepth || nextElem->depth > startDepth)
-			break;
 	}
 
-	/* Case 1: Simple unbounded var at start (greedy only) */
-	if (RPRElemIsVar(elem) && elem->max == RPR_QUANTITY_INF &&
-		!RPRElemIsReluctant(elem))
+	/* Now e should be END at startDepth - 1 */
+	if (e->depth == startDepth - 1 &&
+		RPRElemIsEnd(e) && e->max == RPR_QUANTITY_INF &&
+		!RPRElemIsReluctant(e))
 	{
-		/* Set both flags on first element */
-		elem->flags |= RPR_ELEM_ABSORBABLE_BRANCH | RPR_ELEM_ABSORBABLE;
+		Assert(e->jump == idx); /* END points back to first child */
+
+		/* Set ABSORBABLE_BRANCH on all children, ABSORBABLE on END only */
+		for (e = elem; !RPRElemIsEnd(e); e = &pattern->elements[e->next])
+			e->flags |= RPR_ELEM_ABSORBABLE_BRANCH;
+		e->flags |= RPR_ELEM_ABSORBABLE_BRANCH | RPR_ELEM_ABSORBABLE;
 		return true;
 	}
 
 	return false;
 }
 
-/*
- * computeAbsorbability
- *		Determine if pattern supports context absorption optimization.
- *
- * Context absorption eliminates redundant match searches by absorbing
- * newer contexts that cannot produce longer matches than older contexts.
- * This achieves O(n^2) -> O(n) performance improvement.
- *
- * Only greedy unbounded quantifiers at pattern start can be absorbable.
- * Reluctant quantifiers are excluded because they don't maintain monotonic
- * decrease property required for safe absorption.
- *
- * This function sets two flags:
- *   RPR_ELEM_ABSORBABLE: Absorption judgment point
- *     - Simple unbounded VAR: the VAR itself (e.g., A in A+)
- *     - Unbounded GROUP: the END element (e.g., END in (A B)+)
- *   RPR_ELEM_ABSORBABLE_BRANCH: All elements in absorbable region
- *     - All elements within the same scope as unbounded start
- *
- * Examples:
- *   A+ B C         - absorbable (A gets both flags)
- *   (A B)+ C       - absorbable (A,B,END get BRANCH, END gets ABSORBABLE)
- *   A B+           - NOT absorbable (unbounded not at start)
- *   A+? B C        - NOT absorbable (reluctant quantifier)
- *   (A+ B+)+       - only first A+ on first iteration (nested unbounded not supported)
- *   A+ | B+        - both branches absorbable independently
- *   A+ | C D       - only A+ branch absorbable (C D branch not absorbable)
- *   ((A+ B) | C) D - nested ALT: A+ branch is absorbable
- */
-
 /*
  * computeAbsorbabilityRecursive
  *		Recursively check absorbability starting from given index.
@@ -1562,14 +1518,14 @@ isUnboundedStart(RPRPattern *pattern, RPRElemIdx idx, RPRDepth parentDepth)
  * Each branch gets its own absorbability status, and if any branch is absorbable,
  * the ALT element itself is marked with RPR_ELEM_ABSORBABLE_BRANCH.
  *
- * Otherwise, checks if the element starts an unbounded sequence via isUnboundedStart.
+ * If BEGIN, skips to first child.
  *
- * parentDepth acts as a scope boundary, preventing checks from crossing into
- * enclosing contexts.
+ * Otherwise (VAR), checks if the element starts an unbounded sequence via
+ * isUnboundedStart.
  */
 static void
 computeAbsorbabilityRecursive(RPRPattern *pattern, RPRElemIdx startIdx,
-							  bool *hasAbsorbable, RPRDepth parentDepth)
+							  bool *hasAbsorbable)
 {
 	RPRPatternElement *elem = &pattern->elements[startIdx];
 
@@ -1587,9 +1543,12 @@ computeAbsorbabilityRecursive(RPRPattern *pattern, RPRElemIdx startIdx,
 			Assert(branchIdx < pattern->numElements);
 			branchFirst = &pattern->elements[branchIdx];
 
-			/* Recursively check this branch with ALT depth + 1 as boundary */
-			computeAbsorbabilityRecursive(pattern, branchIdx, &branchAbsorbable,
-										  elem->depth + 1);
+			/* Stop if element is outside ALT scope (not a branch) */
+			if (branchFirst->depth <= elem->depth)
+				break;
+
+			/* Recursively check this branch */
+			computeAbsorbabilityRecursive(pattern, branchIdx, &branchAbsorbable);
 			if (branchAbsorbable)
 			{
 				*hasAbsorbable = true;
@@ -1605,7 +1564,7 @@ computeAbsorbabilityRecursive(RPRPattern *pattern, RPRElemIdx startIdx,
 	else if (RPRElemIsBegin(elem))
 	{
 		/* BEGIN: skip to first child and check that */
-		computeAbsorbabilityRecursive(pattern, elem->next, hasAbsorbable, parentDepth);
+		computeAbsorbabilityRecursive(pattern, elem->next, hasAbsorbable);
 
 		/* Mark BEGIN element if contents are absorbable */
 		if (*hasAbsorbable)
@@ -1613,14 +1572,46 @@ computeAbsorbabilityRecursive(RPRPattern *pattern, RPRElemIdx startIdx,
 	}
 	else
 	{
+		/* Should never reach END - structural invariant of pattern AST */
+		Assert(!RPRElemIsEnd(elem));
+
 		/* Non-ALT, non-BEGIN: check if unbounded start */
-		if (isUnboundedStart(pattern, startIdx, parentDepth))
+		if (isUnboundedStart(pattern, startIdx))
 		{
 			*hasAbsorbable = true;
 		}
 	}
 }
 
+/*
+ * computeAbsorbability
+ *		Determine if pattern supports context absorption optimization.
+ *
+ * Context absorption eliminates redundant match searches by absorbing
+ * newer contexts that cannot produce longer matches than older contexts.
+ * This achieves O(n^2) -> O(n) performance improvement.
+ *
+ * Only greedy unbounded quantifiers at pattern start can be absorbable.
+ * Reluctant quantifiers are excluded because they don't maintain monotonic
+ * decrease property required for safe absorption.
+ *
+ * This function sets two flags:
+ *   RPR_ELEM_ABSORBABLE: Absorption judgment point
+ *     - Simple unbounded VAR: the VAR itself (e.g., A in A+)
+ *     - Unbounded GROUP: the END element (e.g., END in (A B)+)
+ *   RPR_ELEM_ABSORBABLE_BRANCH: All elements in absorbable region
+ *     - All elements within the same scope as unbounded start
+ *
+ * Examples:
+ *   A+ B C         - absorbable (A gets both flags)
+ *   (A B)+ C       - absorbable (A,B,END get BRANCH, END gets ABSORBABLE)
+ *   A B+           - NOT absorbable (unbounded not at start)
+ *   A+? B C        - NOT absorbable (reluctant quantifier)
+ *   (A+ B+)+       - only first A+ on first iteration (nested unbounded not supported)
+ *   A+ | B+        - both branches absorbable independently
+ *   A+ | C D       - only A+ branch absorbable (C D branch not absorbable)
+ *   ((A+ B) | C) D - nested ALT: A+ branch is absorbable
+ */
 static void
 computeAbsorbability(RPRPattern *pattern)
 {
@@ -1629,8 +1620,8 @@ computeAbsorbability(RPRPattern *pattern)
 	/* Parser always produces at least one element + FIN */
 	Assert(pattern->numElements >= 2);
 
-	/* Start recursion with parentDepth=0 (top level) */
-	computeAbsorbabilityRecursive(pattern, 0, &hasAbsorbable, 0);
+	/* Start recursion from first element */
+	computeAbsorbabilityRecursive(pattern, 0, &hasAbsorbable);
 	pattern->isAbsorbable = hasAbsorbable;
 }
 
diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
index 80ebf3c33f8..9e1fa228759 100644
--- a/src/backend/parser/parse_rpr.c
+++ b/src/backend/parser/parse_rpr.c
@@ -90,17 +90,9 @@ transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef,
 	/* Frame must start at current row */
 	if ((wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) == 0)
 	{
-		const char *frameType = "GROUPS";
+		const char *frameType = "ROWS";
 		const char *startBound = "unknown";
 
-		/* Determine frame type */
-		if (wc->frameOptions & FRAMEOPTION_ROWS)
-			frameType = "ROWS";
-		else if (wc->frameOptions & FRAMEOPTION_RANGE)
-			frameType = "RANGE";
-		else
-			frameType = "GROUPS";
-
 		/* Determine current start bound */
 		if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
 			startBound = "UNBOUNDED PRECEDING";
diff --git a/src/include/optimizer/rpr.h b/src/include/optimizer/rpr.h
index 8e1bc47643c..7a8938dbebd 100644
--- a/src/include/optimizer/rpr.h
+++ b/src/include/optimizer/rpr.h
@@ -22,7 +22,7 @@
 #define RPR_COUNT_MAX		INT32_MAX	/* max runtime count (NFA state) */
 #define RPR_ELEMIDX_MAX		INT16_MAX	/* max pattern elements */
 #define RPR_ELEMIDX_INVALID	((RPRElemIdx) -1)	/* invalid index */
-#define RPR_DEPTH_MAX		(UINT8_MAX - 1)	/* max pattern nesting depth: 254 */
+#define RPR_DEPTH_MAX		(UINT8_MAX - 1) /* max pattern nesting depth: 254 */
 #define RPR_DEPTH_NONE		UINT8_MAX	/* no enclosing group (top-level) */
 
 /* Special varId values for control elements (252-255) */
diff --git a/src/test/regress/expected/rpr.out b/src/test/regress/expected/rpr.out
index 8c8ff90e1cd..c921badb006 100644
--- a/src/test/regress/expected/rpr.out
+++ b/src/test/regress/expected/rpr.out
@@ -826,6 +826,46 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
  company2 | 07-10-2023 |  1300 |             |           
 (20 rows)
 
+-- nth_value beyond reduced frame (no IGNORE NULLS)
+-- Tests WinGetSlotInFrame/WinGetFuncArgInFrame out-of-frame with RPR
+SELECT company, tdate, price,
+ nth_value(price, 5) OVER w AS nth_5
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START UP+ DOWN+)
+ DEFINE
+  START AS TRUE,
+  UP AS price > PREV(price),
+  DOWN AS price < PREV(price)
+);
+ company  |   tdate    | price | nth_5 
+----------+------------+-------+-------
+ company1 | 07-01-2023 |   100 |      
+ company1 | 07-02-2023 |   200 |      
+ company1 | 07-03-2023 |   150 |      
+ company1 | 07-04-2023 |   140 |      
+ company1 | 07-05-2023 |   150 |      
+ company1 | 07-06-2023 |    90 |      
+ company1 | 07-07-2023 |   110 |      
+ company1 | 07-08-2023 |   130 |      
+ company1 | 07-09-2023 |   120 |      
+ company1 | 07-10-2023 |   130 |      
+ company2 | 07-01-2023 |    50 |      
+ company2 | 07-02-2023 |  2000 |      
+ company2 | 07-03-2023 |  1500 |      
+ company2 | 07-04-2023 |  1400 |      
+ company2 | 07-05-2023 |  1500 |      
+ company2 | 07-06-2023 |    60 |      
+ company2 | 07-07-2023 |  1100 |      
+ company2 | 07-08-2023 |  1300 |      
+ company2 | 07-09-2023 |  1200 |      
+ company2 | 07-10-2023 |  1300 |      
+(20 rows)
+
 -- backtracking with reclassification of rows
 -- using AFTER MATCH SKIP PAST LAST ROW
 SELECT company, tdate, price, first_value(tdate) OVER w, last_value(tdate) OVER w
@@ -1165,6 +1205,33 @@ count(*) OVER w
  07-07-2023 |  1100 |             |     0
 (14 rows)
 
+-- ReScan test: LATERAL join forces WindowAgg rescan with RPR
+-- Tests ExecReScanWindowAgg clearing prev_slot/next_slot
+SELECT g.x, sub.*
+FROM generate_series(1, 2) g(x),
+LATERAL (
+  SELECT id, price, count(*) OVER w AS c
+  FROM (VALUES (1, 100), (2, 200), (3, 150)) AS t(id, price)
+  WHERE id <= g.x + 1
+  WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (START UP+)
+    DEFINE
+      START AS TRUE,
+      UP AS price > PREV(price)
+  )
+) sub
+ORDER BY g.x, sub.id;
+ x | id | price | c 
+---+----+-------+---
+ 1 |  1 |   100 | 2
+ 1 |  2 |   200 | 0
+ 2 |  1 |   100 | 2
+ 2 |  2 |   200 | 0
+ 2 |  3 |   150 | 0
+(5 rows)
+
 -- PREV has multiple column reference
 CREATE TEMP TABLE rpr1 (id INTEGER, i SERIAL, j INTEGER);
 INSERT INTO rpr1(id, j) SELECT 1, g*2 FROM generate_series(1, 10) AS g;
@@ -1352,6 +1419,46 @@ WITH data AS (
   13 |  4 |           | 
 (4 rows)
 
+-- nth_value beyond reduced frame with IGNORE NULLS
+-- Tests ignorenulls_getfuncarginframe early out-of-frame check
+SELECT company, tdate, price,
+ nth_value(price, 5) IGNORE NULLS OVER w AS nth_5_in
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START UP+ DOWN+)
+ DEFINE
+  START AS TRUE,
+  UP AS price > PREV(price),
+  DOWN AS price < PREV(price)
+);
+ company  |   tdate    | price | nth_5_in 
+----------+------------+-------+----------
+ company1 | 07-01-2023 |   100 |         
+ company1 | 07-02-2023 |   200 |         
+ company1 | 07-03-2023 |   150 |         
+ company1 | 07-04-2023 |   140 |         
+ company1 | 07-05-2023 |   150 |         
+ company1 | 07-06-2023 |    90 |         
+ company1 | 07-07-2023 |   110 |         
+ company1 | 07-08-2023 |   130 |         
+ company1 | 07-09-2023 |   120 |         
+ company1 | 07-10-2023 |   130 |         
+ company2 | 07-01-2023 |    50 |         
+ company2 | 07-02-2023 |  2000 |         
+ company2 | 07-03-2023 |  1500 |         
+ company2 | 07-04-2023 |  1400 |         
+ company2 | 07-05-2023 |  1500 |         
+ company2 | 07-06-2023 |    60 |         
+ company2 | 07-07-2023 |  1100 |         
+ company2 | 07-08-2023 |  1300 |         
+ company2 | 07-09-2023 |  1200 |         
+ company2 | 07-10-2023 |  1300 |         
+(20 rows)
+
 -- View and pg_get_viewdef tests.
 CREATE TEMP VIEW v_window AS
 SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w,
@@ -2882,11 +2989,10 @@ WINDOW w AS (
 --   Row 2: B C D (2-4) <- ends first!
 --   Row 3: C D E F (3-6) <- ends last!
 -- Test 1b: Longer pattern FAILS, shorter pattern should survive
--- Pattern: A+ B C D | A+ B (greedy at front for absorption)
--- Data: A B C X (D expected but X found)
--- A+ B C D fails at row 4 (X instead of D)
--- A+ B succeeds at row 2
--- Result should be match 1-2 (A+ B)
+-- Pattern: A+ B C D E | B+ C
+-- A+ B C D E fails (no E found in sequence)
+-- B+ C matches at rows 2-3
+-- Result: match 2-3 (B+ C)
 WITH test_overlap1b AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -3179,8 +3285,7 @@ WINDOW w AS (
 (5 rows)
 
 -- Pattern A+ is absorbable (unbounded first element, only one unbounded)
--- Without absorption: 4 matches (1-4, 2-4, 3-4, 4-4)
--- With absorption: Row 1 match (1-4), rows 2-4 absorbed
+-- 4 matches: (1-4, 2-4, 3-4, 4-4)
 -- Test absorption 2: A+ B pattern - absorption with fixed suffix
 WITH test_absorb_suffix AS (
     SELECT * FROM (VALUES
@@ -3213,7 +3318,7 @@ WINDOW w AS (
 
 -- Pattern A+ B is absorbable (A+ unbounded first, B bounded suffix)
 -- All potential matches end at same row (row 4 with B)
--- With absorption: Row 1 match (1-4), rows 2-3 absorbed
+-- 3 matches: (1-4, 2-4, 3-4)
 -- Test absorption 3: Per-branch absorption with ALT (B+ C | B+ D)
 WITH test_absorb_alt AS (
     SELECT * FROM (VALUES
@@ -3246,8 +3351,7 @@ WINDOW w AS (
 (5 rows)
 
 -- Both branches B+ C and B+ D are absorbable (B+ unbounded first)
--- B+ D branch matches: potential 1-4, 2-4, 3-4
--- Row 1 (1-4) absorbs Row 2 (2-4) and Row 3 (3-4) - same endpoint
+-- B+ D branch matches: 3 matches (1-4, 2-4, 3-4)
 -- Test absorption 4: Non-absorbable pattern (A B+ - unbounded not first)
 WITH test_no_absorb AS (
     SELECT * FROM (VALUES
@@ -3315,8 +3419,7 @@ WINDOW w AS (
 (7 rows)
 
 -- Pattern optimized: (A B) (A B)+ -> (A B){2,}
--- Potential matches: 1-6 (3 reps), 3-6 (2 reps), 5-6 needs 2 reps (fail)
--- Row 1 (1-6) absorbs Row 3 (3-6) - same endpoint, top-level unbounded GROUP
+-- 2 matches: 1-6 (3 reps), 3-6 (2 reps)
 -- Test absorption 6: Multiple unbounded - first element unbounded enables absorption
 WITH test_multi_unbounded AS (
     SELECT * FROM (VALUES
@@ -3347,7 +3450,7 @@ WINDOW w AS (
   5 | {X}   |             |          
 (5 rows)
 
--- Row 1: 1-4, Row 2: absorbed (same endpoint 4)
+-- 2 matches: 1-4, 2-4 (same endpoint 4)
 -- ============================================
 -- Jacob's RPR Patterns (from jacob branch)
 -- ============================================
@@ -3671,7 +3774,6 @@ WINDOW w AS (
 -- Alternation with quantifiers (BUG cases from Jacob's tests)
 -- ============================================
 -- Test: (A | B)+ C - alternation inside quantified group followed by C
--- BUG: Currently returns NULL instead of matching 1-4
 WITH jacob_alt_quant AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -3702,7 +3804,6 @@ WINDOW w AS (
 
 -- Expected: 1-4 (A B A C)
 -- Test: ((A | B) C)+ - alternation inside group with outer quantifier
--- Currently returns two separate matches (1-2, 3-4) instead of single 1-4
 WITH jacob_alt_group AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -3737,8 +3838,7 @@ WINDOW w AS (
 -- ============================================
 -- RELUCTANT quantifiers (not yet supported)
 -- ============================================
--- Test: A+? B (reluctant) - CURRENTLY ACTS LIKE A+ (GREEDY)
--- Parser accepts the syntax but executor doesn't differentiate
+-- Test: A+? B (reluctant) - parser rejects with ERROR
 WITH jacob_reluctant AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -3761,9 +3861,8 @@ WINDOW w AS (
 ERROR:  reluctant quantifiers are not yet supported
 LINE 15:     PATTERN (A+? B)
                         ^
--- Current: 1-4 (A A A B) - greedy behavior
--- Expected when RELUCTANT implemented: 3-4 (A B) - shortest A before B
--- Test: A{1,3}? B (reluctant bounded)
+-- Expected: ERROR (reluctant quantifiers not yet supported)
+-- Test: A{1,3}? B (reluctant bounded) - parser rejects with ERROR
 WITH jacob_reluctant_bounded AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -3786,8 +3885,7 @@ WINDOW w AS (
 ERROR:  reluctant quantifiers are not yet supported
 LINE 15:     PATTERN (A{1,3}? B)
                             ^
--- Current: 1-4 (greedy, takes max A before B)
--- Expected when RELUCTANT implemented: 3-4 (takes min A before B)
+-- Expected: ERROR (reluctant quantifiers not yet supported)
 -- ============================================
 -- Nested quantifiers (pathological patterns)
 -- ============================================
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index a1f11bd61ce..ec67a099ee6 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -233,7 +233,7 @@ WINDOW w AS (
 ERROR:  row pattern definition variable name "a" appears more than once in DEFINE clause
 LINE 7:     DEFINE A AS id > 0, A AS id < 10
                    ^
--- Expected: ERROR: row pattern definition variable name "A" appears more than once in DEFINE clause
+-- Expected: ERROR: row pattern definition variable name "a" appears more than once in DEFINE clause
 DROP TABLE rpr_dup;
 -- Boolean coercion
 CREATE TABLE rpr_bool (id INT, flag BOOLEAN);
@@ -438,7 +438,7 @@ ERROR:  FRAME option RANGE is not permitted when row pattern recognition is used
 LINE 5:     RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWIN...
             ^
 HINT:  Use: ROWS instead
--- Expected: ERROR: FRAME must start at current row when row pattern recognition is used
+-- Expected: ERROR: FRAME option RANGE is not permitted when row pattern recognition is used
 -- GROUPS frame not starting at CURRENT ROW
 SELECT COUNT(*) OVER w
 FROM rpr_frame
@@ -452,7 +452,7 @@ ERROR:  FRAME option GROUP is not permitted when row pattern recognition is used
 LINE 5:     GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWI...
             ^
 HINT:  Use: ROWS instead
--- Expected: ERROR: FRAME must start at current row when row pattern recognition is used
+-- Expected: ERROR: FRAME option GROUP is not permitted when row pattern recognition is used
 -- Starting with N PRECEDING
 SELECT COUNT(*) OVER w
 FROM rpr_frame
@@ -496,7 +496,7 @@ WINDOW w AS (
 ERROR:  frame starting from current row cannot have preceding rows
 LINE 5:     ROWS BETWEEN CURRENT ROW AND 1 PRECEDING
                                          ^
--- Expected: ERROR: frame end cannot be before frame start
+-- Expected: ERROR: frame starting from current row cannot have preceding rows
 -- End before start: CURRENT ROW AND UNBOUNDED PRECEDING
 SELECT COUNT(*) OVER w
 FROM rpr_frame
@@ -509,7 +509,7 @@ WINDOW w AS (
 ERROR:  frame end cannot be UNBOUNDED PRECEDING
 LINE 5:     ROWS BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING
                                          ^
--- Expected: ERROR: frame end cannot be before frame start
+-- Expected: ERROR: frame end cannot be UNBOUNDED PRECEDING
 -- Single row frame: CURRENT ROW AND CURRENT ROW
 SELECT id, val, COUNT(*) OVER w as cnt
 FROM rpr_frame
@@ -594,14 +594,7 @@ ORDER BY id;
   6 |  30 |   1
 (6 rows)
 
--- RANGE: includes all rows with same ORDER BY value
--- Expected result: Includes peer rows (same val) in range calculation
--- id=1 (val=10): range [10,20] includes all val=10 and val=20 peers -> cnt=2 (only first in peer group gets match)
--- id=2,3 (val=10): already matched by id=1 -> cnt=0
--- id=4 (val=20): range [20,30] includes all val=20 and val=30 peers -> cnt=2
--- id=5 (val=20): already matched by id=4 -> cnt=0
--- id=6 (val=30): range [30,40] includes only val=30 -> cnt=1
--- Result: [2,0,0,2,0,1]
+-- RANGE frame with RPR (not permitted)
 SELECT id, val, COUNT(*) OVER w as cnt
 FROM rpr_frame
 WINDOW w AS (
@@ -616,14 +609,8 @@ ERROR:  FRAME option RANGE is not permitted when row pattern recognition is used
 LINE 5:     RANGE BETWEEN CURRENT ROW AND 10 FOLLOWING
             ^
 HINT:  Use: ROWS instead
--- GROUPS: treats rows with same value as one group (1 FOLLOWING = next group)
--- Expected result: 1 FOLLOWING means current group + 1 next group
--- id=1 (val=10): groups [val=10, val=20] -> cnt=2 (only first in group gets match)
--- id=2,3 (val=10): already matched by id=1 -> cnt=0
--- id=4 (val=20): groups [val=20, val=30] -> cnt=2
--- id=5 (val=20): already matched by id=4 -> cnt=0
--- id=6 (val=30): groups [val=30] (no next group) -> cnt=1
--- Result: [2,0,0,2,0,1]
+-- Expected: ERROR: FRAME option RANGE is not permitted when row pattern recognition is used
+-- GROUPS frame with RPR (not permitted)
 SELECT id, val, COUNT(*) OVER w as cnt
 FROM rpr_frame
 WINDOW w AS (
@@ -638,6 +625,7 @@ ERROR:  FRAME option GROUP is not permitted when row pattern recognition is used
 LINE 5:     GROUPS BETWEEN CURRENT ROW AND 1 FOLLOWING
             ^
 HINT:  Use: ROWS instead
+-- Expected: ERROR: FRAME option GROUP is not permitted when row pattern recognition is used
 DROP TABLE rpr_frame;
 -- ============================================================
 -- PARTITION BY + FRAME Tests
@@ -648,7 +636,6 @@ INSERT INTO rpr_partition VALUES
     (1, 1, 10), (2, 1, 20), (3, 1, 30),
     (4, 2, 15), (5, 2, 25), (6, 2, 35);
 -- PARTITION BY with ROWS frame
--- Expected: Pattern matching should reset for each partition
 SELECT id, grp, val, COUNT(*) OVER w as cnt
 FROM rpr_partition
 WINDOW w AS (
@@ -670,8 +657,8 @@ ORDER BY id;
   6 |   2 |  35 |   0
 (6 rows)
 
+-- Expected: Pattern matching should reset for each partition
 -- PARTITION BY with RANGE frame
--- Expected: Each partition processed independently
 SELECT id, grp, val, COUNT(*) OVER w as cnt
 FROM rpr_partition
 WINDOW w AS (
@@ -687,6 +674,7 @@ ERROR:  FRAME option RANGE is not permitted when row pattern recognition is used
 LINE 6:     RANGE BETWEEN CURRENT ROW AND 10 FOLLOWING
             ^
 HINT:  Use: ROWS instead
+-- Expected: ERROR: FRAME option RANGE is not permitted when row pattern recognition is used
 DROP TABLE rpr_partition;
 -- ============================================================
 -- PATTERN Syntax Tests
@@ -2659,7 +2647,7 @@ WINDOW w AS (
 ERROR:  invalid input syntax for type integer: "string"
 LINE 7:     DEFINE A AS val > 'string'
                               ^
--- Expected: ERROR: operator does not exist or type mismatch
+-- Expected: ERROR: invalid input syntax for type integer: "string"
 -- Aggregate function in DEFINE (if not allowed)
 SELECT COUNT(*) OVER w
 FROM rpr_err
@@ -2922,7 +2910,7 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
          ->  Seq Scan on rpr_plan
 (6 rows)
 
--- ALT flatten: (A | (B | C)) -> (a | b | c)
+-- ALT flatten: (A | (B | C))+ -> (a | b | c)+
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -3154,7 +3142,7 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
          ->  Seq Scan on rpr_plan
 (6 rows)
 
--- Combined optimization: A A (B B)+ B B C C C -> a{2} (b{2}){3,} c{3}
+-- Combined optimization: A A (B B)+ B B C C C -> a{2} (b{2}){2,} c{3}
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -3170,7 +3158,7 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
          ->  Seq Scan on rpr_plan
 (6 rows)
 
--- Consecutive GROUP merge with unbounded: (A+){2} -> (a+){2}
+-- Consecutive GROUP merge with unbounded: (A+) (A+) -> a{2,}
 -- Tests mergeConsecutiveGroups with child->max == INF
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
@@ -3355,8 +3343,8 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
          ->  Seq Scan on rpr_plan
 (6 rows)
 
--- Unwrap GROUP{1,1}: (A | B | C) -> A | B | C
--- Tests tryUnwrapGroup removing redundant GROUP
+-- Unwrap GROUP{1,1}: ((A | B | C)) -> (a | b | c)
+-- Tests tryUnwrapGroup removing redundant outer GROUP
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4040,7 +4028,6 @@ ORDER BY id;
 CREATE TABLE rpr_fallback (id INT, val INT);
 INSERT INTO rpr_fallback VALUES (1, 10), (2, 20);
 -- Test: min quantifier overflow causes optimization fallback (min == max case)
--- Expected: Fallback - pattern not merged due to min overflow (4000000000 > INT32_MAX)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -4059,8 +4046,8 @@ WINDOW w AS (
          ->  Seq Scan on rpr_fallback
 (6 rows)
 
+-- Expected: Fallback - pattern not merged due to min overflow (4000000000 > INT32_MAX)
 -- Test: max-only quantifier overflow causes optimization fallback
--- Expected: Fallback - min OK (2*1=2), but max overflow (2*2000000000 > INT32_MAX)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -4079,8 +4066,8 @@ WINDOW w AS (
          ->  Seq Scan on rpr_fallback
 (6 rows)
 
--- Test: min quantifier overflow causes optimization fallback (min != max case)
--- Expected: Fallback - pattern not merged due to min overflow
+-- Expected: Fallback - min OK (2*1=2), but max overflow (2*2000000000 > INT32_MAX)
+-- Test: max quantifier exceeds valid range (2147483647 = INT_MAX, limit is 2147483646)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -4092,8 +4079,8 @@ WINDOW w AS (
 ERROR:  quantifier bounds must be between 0 and 2147483646 with max >= 1
 LINE 6:     PATTERN ((A{2000000000,2147483647}){2})
                         ^
+-- Expected: ERROR at parse time before optimization
 -- Test: nested unbounded with large min causes overflow fallback
--- Expected: Fallback - min overflow (2000000000 * 2000000000 > INT32_MAX)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -4112,8 +4099,8 @@ WINDOW w AS (
          ->  Seq Scan on rpr_fallback
 (6 rows)
 
+-- Expected: Fallback - min overflow (2000000000 * 2000000000 > INT32_MAX)
 -- Test: prefix mismatch causes optimization fallback
--- Expected: Fallback - prefix elements don't match GROUP content
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -4132,6 +4119,7 @@ WINDOW w AS (
          ->  Seq Scan on rpr_fallback
 (6 rows)
 
+-- Expected: Fallback - prefix elements don't match GROUP content
 DROP TABLE rpr_fallback;
 -- ============================================================
 -- Planner Integration Tests
@@ -4288,6 +4276,7 @@ ORDER BY category;
 ERROR:  syntax error at or near "GROUP"
 LINE 12: GROUP BY category
          ^
+-- Expected: ERROR (GROUP BY with window RPR not supported)
 -- ============================================================
 -- Subquery and CTE Tests
 -- Files: planner.c, prepjointree.c
@@ -5015,7 +5004,6 @@ DROP TABLE rpr_stress;
 CREATE TABLE rpr_errors (id INT, val INT);
 INSERT INTO rpr_errors VALUES (1, 10), (2, 20);
 -- Test: PATTERN variable without DEFINE (A), DEFINE variable not in PATTERN (B)
--- Expected: Success - A is implicitly TRUE, B is filtered out
 SELECT id, val, COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -5030,8 +5018,8 @@ WINDOW w AS (
   2 |  20 |     0
 (2 rows)
 
+-- Expected: Success - A is implicitly TRUE, B is filtered out
 -- Test: 3 variables in PATTERN, 253 in DEFINE (DEFINE filtering test)
--- Expected: Success - unused DEFINE variables are filtered out
 SELECT COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -5071,8 +5059,8 @@ WINDOW w AS (
      0
 (2 rows)
 
--- Test: 251 variables in PATTERN, 252 in DEFINE (boundary - should succeed)
 -- Expected: Success - unused DEFINE variables are filtered out
+-- Test: 251 variables in PATTERN, 252 in DEFINE (boundary - should succeed)
 SELECT COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -5112,8 +5100,8 @@ WINDOW w AS (
      0
 (2 rows)
 
+-- Expected: Success - unused DEFINE variables are filtered out
 -- Test: 252 variables in PATTERN, 251 in DEFINE (exceeds limit with implicit TRUE)
--- Expected: ERROR - too many pattern variables (Maximum is 251)
 SELECT COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -5149,8 +5137,8 @@ WINDOW w AS (
 );
 ERROR:  too many pattern variables
 DETAIL:  Maximum is 251.
+-- Expected: ERROR - too many pattern variables (Maximum is 251)
 -- Test: Pattern nesting at maximum depth (depth 253)
--- Expected: Should succeed
 -- Note: 253 nested GROUP{3,7} quantifiers produce depth 253 after optimization
 SELECT id, val, COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
@@ -5165,8 +5153,8 @@ WINDOW w AS (
   2 |  20 |     0
 (2 rows)
 
+-- Expected: Should succeed
 -- Test: Pattern nesting depth exceeds maximum (depth 254)
--- Expected: ERROR - pattern nesting too deep
 -- Note: 254 nested GROUP{3,7} quantifiers produce depth 254 after optimization
 SELECT id, val, COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
@@ -5177,6 +5165,7 @@ WINDOW w AS (
 );
 ERROR:  pattern nesting too deep
 DETAIL:  Pattern nesting depth 254 exceeds maximum 253.
+-- Expected: ERROR - pattern nesting too deep
 DROP TABLE rpr_errors;
 -- ============================================================
 -- Jacob's Patterns
@@ -5398,6 +5387,31 @@ WINDOW w AS (
  10 | 100 | 0
 (10 rows)
 
+-- Test: (A+ | (A | B)+)* - nested alternation inside quantified group
+-- Previously caused infinite recursion in nfa_advance_alt when the inner
+-- BEGIN(+)'s skip jump was followed as an ALT branch pointer.
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM (VALUES
+    (1, ARRAY['A', 'B']),
+    (2, ARRAY['B']),
+    (3, ARRAY['C'])
+) AS t(id, flags)
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN ((A+ | (A | B)+)*)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A,B} |           1 |         2
+  2 | {B}   |             |          
+  3 | {C}   |             |          
+(3 rows)
+
 -- ============================================================
 -- Pathological Patterns
 -- ============================================================
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index ea75d62718e..f23d06f6d59 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -134,10 +134,11 @@ WINDOW w AS (
    Pattern: a b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 2 peak, 101 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 80 pruned
+   NFA Contexts: 2 peak, 101 total, 60 pruned
    NFA: 20 matched (len 2/2/2.0), 0 mismatched
+   NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_test (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Pattern with no matches - 0 matched
@@ -235,7 +236,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 20) AS s(v)
 WINDOW w AS (
@@ -243,13 +244,18 @@ WINDOW w AS (
     PATTERN (A (B | C))
     DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a (b | c)
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 3 peak, 28 total, 0 merged
+   NFA Contexts: 2 peak, 21 total, 6 pruned
+   NFA: 7 matched (len 2/2/2.0), 0 mismatched
+   NFA: 0 absorbed, 7 skipped (len 1/1/1.0)
+   ->  Function Scan on generate_series s (actual rows=20.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Regression test: Sequential alternations at same depth
@@ -270,7 +276,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 30) AS s(v)
 WINDOW w AS (
@@ -278,13 +284,17 @@ WINDOW w AS (
     PATTERN (A ((B | C) (D | E))*)
     DEFINE A AS v % 5 = 1, B AS v % 5 = 2, C AS v % 5 = 3, D AS v % 5 = 4, E AS v % 5 = 0
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=30.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a ((b | c) (d | e))*
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 4 peak, 49 total, 0 merged
+   NFA Contexts: 3 peak, 31 total, 24 pruned
+   NFA: 6 matched (len 1/1/1.0), 0 mismatched
+   ->  Function Scan on generate_series s (actual rows=30.00 loops=1)
+(8 rows)
 
 DROP VIEW rpr_v;
 -- ============================================================
@@ -366,10 +376,11 @@ WINDOW w AS (
    Pattern: (a | b | c) (d | e)
    Storage: Memory  Maximum Storage: NkB
    NFA States: 5 peak, 363 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 40 pruned
+   NFA Contexts: 3 peak, 101 total, 20 pruned
    NFA: 20 matched (len 2/2/2.0), 40 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_test (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Complex pattern with high state count
@@ -411,10 +422,11 @@ WINDOW w AS (
    Pattern: a+" b* c+
    Storage: Memory  Maximum Storage: NkB
    NFA States: 5 peak, 235 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 67 pruned
+   NFA Contexts: 3 peak, 101 total, 34 pruned
    NFA: 33 matched (len 3/3/3.0), 0 mismatched
+   NFA: 0 absorbed, 33 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Grouped pattern with quantifier - state merging
@@ -450,9 +462,9 @@ WINDOW w AS (
    Pattern: (a' b')+"
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 91 total, 0 merged
-   NFA Contexts: 3 peak, 61 total, 30 pruned
+   NFA Contexts: 2 peak, 61 total, 0 pruned
    NFA: 1 matched (len 60/60/60.0), 0 mismatched
-   NFA: 29 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 29 absorbed (len 1/1/1.0), 30 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
 (9 rows)
 
@@ -490,8 +502,8 @@ WINDOW w AS (
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b){8}
    Storage: Memory  Maximum Storage: NkB
-   NFA States: 17 peak, 632 total, 0 merged
-   NFA Contexts: 9 peak, 101 total, 1 pruned
+   NFA States: 16 peak, 548 total, 0 merged
+   NFA Contexts: 8 peak, 101 total, 1 pruned
    NFA: 12 matched (len 8/8/8.0), 3 mismatched (len 2/4/3.0)
    NFA: 0 absorbed, 84 skipped (len 1/7/4.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
@@ -516,7 +528,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -525,13 +537,18 @@ WINDOW w AS (
     PATTERN ((A | B) (A | B) (C | D))
     DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=40.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b){2} (c | d)
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 6 peak, 111 total, 0 merged
+   NFA Contexts: 3 peak, 41 total, 12 pruned
+   NFA: 9 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 18 skipped (len 1/2/1.5)
+   ->  Function Scan on generate_series s (actual rows=40.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Consecutive ALT merge followed by non-ALT element
@@ -552,7 +569,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -561,13 +578,18 @@ WINDOW w AS (
     PATTERN ((A | B) (A | B) C)
     DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=40.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b){2} c
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 5 peak, 109 total, 0 merged
+   NFA Contexts: 3 peak, 41 total, 2 pruned
+   NFA: 12 matched (len 3/3/3.0), 2 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 24 skipped (len 1/2/1.5)
+   ->  Function Scan on generate_series s (actual rows=40.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- ALT prefix/suffix absorbed into GROUP: (A|B) (A|B)+ (A|B) -> (A|B){3,}
@@ -587,7 +609,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -596,13 +618,18 @@ WINDOW w AS (
     PATTERN ((A | B) (A | B)+ (A | B))
     DEFINE A AS v % 2 = 0, B AS v % 2 = 1
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=40.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b){3,}
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 6 peak, 161 total, 0 merged
+   NFA Contexts: 3 peak, 41 total, 0 pruned
+   NFA: 1 matched (len 40/40/40.0), 0 mismatched
+   NFA: 0 absorbed, 39 skipped (len 1/2/1.0)
+   ->  Function Scan on generate_series s (actual rows=40.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- High state merging - alternation with plus quantifier
@@ -638,9 +665,9 @@ WINDOW w AS (
    Pattern: (a | b | c)+ d
    Storage: Memory  Maximum Storage: NkB
    NFA States: 15 peak, 753 total, 0 merged
-   NFA Contexts: 5 peak, 101 total, 25 pruned
+   NFA Contexts: 4 peak, 101 total, 0 pruned
    NFA: 25 matched (len 4/4/4.0), 0 mismatched
-   NFA: 0 absorbed, 50 skipped (len 2/3/2.5)
+   NFA: 0 absorbed, 75 skipped (len 1/3/2.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
 (9 rows)
 
@@ -677,10 +704,10 @@ WINDOW w AS (
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b)+
    Storage: Memory  Maximum Storage: NkB
-   NFA States: 8 peak, 4002 total, 0 merged
-   NFA Contexts: 4 peak, 1001 total, 333 pruned
+   NFA States: 5 peak, 3336 total, 0 merged
+   NFA Contexts: 3 peak, 1001 total, 333 pruned
    NFA: 334 matched (len 1/2/2.0), 0 mismatched
-   NFA: 0 absorbed, 333 skipped (len 2/2/2.0)
+   NFA: 0 absorbed, 333 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=1000.00 loops=1)
 (9 rows)
 
@@ -721,9 +748,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 91 total, 0 merged
-   NFA Contexts: 3 peak, 51 total, 10 pruned
+   NFA Contexts: 2 peak, 51 total, 0 pruned
    NFA: 10 matched (len 5/5/5.0), 0 mismatched
-   NFA: 30 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 30 absorbed (len 1/1/1.0), 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -761,9 +788,9 @@ WINDOW w AS (
    Pattern: a{2,4} b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 7 peak, 101 total, 0 merged
-   NFA Contexts: 6 peak, 51 total, 10 pruned
-   NFA: 10 matched (len 5/5/5.0), 10 mismatched (len 2/2/2.0)
-   NFA: 0 absorbed, 20 skipped (len 3/4/3.5)
+   NFA Contexts: 5 peak, 51 total, 0 pruned
+   NFA: 10 matched (len 5/5/5.0), 0 mismatched
+   NFA: 0 absorbed, 40 skipped (len 1/4/2.5)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -801,10 +828,11 @@ WINDOW w AS (
    Pattern: a b c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 2 peak, 101 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 90 pruned
+   NFA Contexts: 3 peak, 101 total, 80 pruned
    NFA: 10 matched (len 3/3/3.0), 0 mismatched
+   NFA: 0 absorbed, 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- High context absorption - unbounded group
@@ -840,10 +868,11 @@ WINDOW w AS (
    Pattern: (a' b')+" c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 134 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 67 pruned
+   NFA Contexts: 3 peak, 101 total, 34 pruned
    NFA: 33 matched (len 3/3/3.0), 0 mismatched
+   NFA: 0 absorbed, 33 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- ============================================================
@@ -886,10 +915,11 @@ WINDOW w AS (
    Pattern: a b c d e
    Storage: Memory  Maximum Storage: NkB
    NFA States: 2 peak, 101 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 80 pruned
+   NFA Contexts: 3 peak, 101 total, 60 pruned
    NFA: 20 matched (len 5/5/5.0), 0 mismatched
+   NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_test (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Variable length matches - min/max/avg differ
@@ -925,9 +955,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 191 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 10 pruned
+   NFA Contexts: 2 peak, 101 total, 0 pruned
    NFA: 10 matched (len 10/10/10.0), 0 mismatched
-   NFA: 80 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 80 absorbed (len 1/1/1.0), 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
 (9 rows)
 
@@ -965,9 +995,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 396 total, 0 merged
-   NFA Contexts: 3 peak, 201 total, 5 pruned
+   NFA Contexts: 2 peak, 201 total, 4 pruned
    NFA: 1 matched (len 196/196/196.0), 0 mismatched
-   NFA: 194 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 194 absorbed (len 1/1/1.0), 1 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=200.00 loops=1)
 (9 rows)
 
@@ -1009,9 +1039,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 171 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 30 pruned
+   NFA Contexts: 3 peak, 101 total, 25 pruned
    NFA: 5 matched (len 5/5/5.0), 5 mismatched (len 11/11/11.0)
-   NFA: 60 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 60 absorbed (len 1/1/1.0), 5 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
 (9 rows)
 
@@ -1067,9 +1097,9 @@ WINDOW w AS (
    Pattern: a+" b+ c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 151 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 70 pruned
+   NFA Contexts: 3 peak, 101 total, 60 pruned
    NFA: 10 matched (len 6/6/6.0), 0 mismatched
-   NFA: 20 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 20 absorbed (len 1/1/1.0), 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
 (9 rows)
 
@@ -1131,9 +1161,9 @@ WINDOW w AS (
    Pattern: a+" b+ c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 115 total, 0 merged
-   NFA Contexts: 3 peak, 61 total, 16 pruned
+   NFA Contexts: 3 peak, 61 total, 15 pruned
    NFA: 1 matched (len 30/30/30.0), 1 mismatched (len 26/26/26.0)
-   NFA: 42 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 42 absorbed (len 1/1/1.0), 1 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series i (actual rows=60.00 loops=1)
 (9 rows)
 
@@ -1188,13 +1218,16 @@ WINDOW w AS (
        "NFA Contexts Peak": 3,                                             +
        "NFA Contexts Total": 51,                                           +
        "NFA Contexts Absorbed": 0,                                         +
-       "NFA Contexts Skipped": 0,                                          +
-       "NFA Contexts Pruned": 33,                                          +
+       "NFA Contexts Skipped": 17,                                         +
+       "NFA Contexts Pruned": 16,                                          +
        "NFA Matched": 17,                                                  +
        "NFA Mismatched": 0,                                                +
        "NFA Match Length Min": 2,                                          +
        "NFA Match Length Max": 2,                                          +
        "NFA Match Length Avg": 2.0,                                        +
+       "NFA Skipped Length Min": 1,                                        +
+       "NFA Skipped Length Max": 1,                                        +
+       "NFA Skipped Length Avg": 1.0,                                      +
        "Plans": [                                                          +
          {                                                                 +
            "Node Type": "Function Scan",                                   +
@@ -1260,11 +1293,11 @@ WINDOW w AS (
        "NFA States Peak": 3,                                               +
        "NFA States Total": 191,                                            +
        "NFA States Merged": 0,                                             +
-       "NFA Contexts Peak": 3,                                             +
+       "NFA Contexts Peak": 2,                                             +
        "NFA Contexts Total": 101,                                          +
        "NFA Contexts Absorbed": 80,                                        +
-       "NFA Contexts Skipped": 0,                                          +
-       "NFA Contexts Pruned": 10,                                          +
+       "NFA Contexts Skipped": 10,                                         +
+       "NFA Contexts Pruned": 0,                                           +
        "NFA Matched": 10,                                                  +
        "NFA Mismatched": 0,                                                +
        "NFA Match Length Min": 10,                                         +
@@ -1273,6 +1306,9 @@ WINDOW w AS (
        "NFA Absorbed Length Min": 1,                                       +
        "NFA Absorbed Length Max": 1,                                       +
        "NFA Absorbed Length Avg": 1.0,                                     +
+       "NFA Skipped Length Min": 1,                                        +
+       "NFA Skipped Length Max": 1,                                        +
+       "NFA Skipped Length Avg": 1.0,                                      +
        "Plans": [                                                          +
          {                                                                 +
            "Node Type": "Function Scan",                                   +
@@ -1341,16 +1377,19 @@ WINDOW w AS (
        <NFA-States-Peak>2</NFA-States-Peak>                                    +
        <NFA-States-Total>31</NFA-States-Total>                                 +
        <NFA-States-Merged>0</NFA-States-Merged>                                +
-       <NFA-Contexts-Peak>3</NFA-Contexts-Peak>                                +
+       <NFA-Contexts-Peak>2</NFA-Contexts-Peak>                                +
        <NFA-Contexts-Total>31</NFA-Contexts-Total>                             +
        <NFA-Contexts-Absorbed>0</NFA-Contexts-Absorbed>                        +
-       <NFA-Contexts-Skipped>0</NFA-Contexts-Skipped>                          +
-       <NFA-Contexts-Pruned>15</NFA-Contexts-Pruned>                           +
+       <NFA-Contexts-Skipped>15</NFA-Contexts-Skipped>                         +
+       <NFA-Contexts-Pruned>0</NFA-Contexts-Pruned>                            +
        <NFA-Matched>15</NFA-Matched>                                           +
        <NFA-Mismatched>0</NFA-Mismatched>                                      +
        <NFA-Match-Length-Min>2</NFA-Match-Length-Min>                          +
        <NFA-Match-Length-Max>2</NFA-Match-Length-Max>                          +
        <NFA-Match-Length-Avg>2.0</NFA-Match-Length-Avg>                        +
+       <NFA-Skipped-Length-Min>1</NFA-Skipped-Length-Min>                      +
+       <NFA-Skipped-Length-Max>1</NFA-Skipped-Length-Max>                      +
+       <NFA-Skipped-Length-Avg>1.0</NFA-Skipped-Length-Avg>                    +
        <Plans>                                                                 +
          <Plan>                                                                +
            <Node-Type>Function Scan</Node-Type>                                +
@@ -1420,8 +1459,8 @@ WINDOW w AS (
        "NFA Contexts Peak": 3,                                             +
        "NFA Contexts Total": 10,                                           +
        "NFA Contexts Absorbed": 0,                                         +
-       "NFA Contexts Skipped": 0,                                          +
-       "NFA Contexts Pruned": 6,                                           +
+       "NFA Contexts Skipped": 1,                                          +
+       "NFA Contexts Pruned": 5,                                           +
        "NFA Matched": 1,                                                   +
        "NFA Mismatched": 2,                                                +
        "NFA Match Length Min": 3,                                          +
@@ -1430,6 +1469,9 @@ WINDOW w AS (
        "NFA Mismatch Length Min": 3,                                       +
        "NFA Mismatch Length Max": 3,                                       +
        "NFA Mismatch Length Avg": 3.0,                                     +
+       "NFA Skipped Length Min": 1,                                        +
+       "NFA Skipped Length Max": 1,                                        +
+       "NFA Skipped Length Avg": 1.0,                                      +
        "Plans": [                                                          +
          {                                                                 +
            "Node Type": "Values Scan",                                     +
@@ -1492,10 +1534,10 @@ WINDOW w AS (
        "Pattern": "(a | b){8}",                                            +
        "Storage": "Memory",                                                +
        "Maximum Storage": 0,                                               +
-       "NFA States Peak": 17,                                              +
-       "NFA States Total": 632,                                            +
+       "NFA States Peak": 16,                                              +
+       "NFA States Total": 548,                                            +
        "NFA States Merged": 0,                                             +
-       "NFA Contexts Peak": 9,                                             +
+       "NFA Contexts Peak": 8,                                             +
        "NFA Contexts Total": 101,                                          +
        "NFA Contexts Absorbed": 0,                                         +
        "NFA Contexts Skipped": 84,                                         +
@@ -1578,9 +1620,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 165 total, 0 merged
-   NFA Contexts: 3 peak, 93 total, 18 pruned
+   NFA Contexts: 2 peak, 93 total, 0 pruned
    NFA: 18 matched (len 5/5/5.0), 0 mismatched
-   NFA: 54 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 54 absorbed (len 1/1/1.0), 18 skipped (len 1/1/1.0)
    ->  Sort (actual rows=90.00 loops=1)
          Sort Key: p.p
          Sort Method: quicksort  Memory: 27kB
@@ -1635,9 +1677,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 77 total, 0 merged
-   NFA Contexts: 3 peak, 52 total, 26 pruned
+   NFA Contexts: 2 peak, 52 total, 21 pruned
    NFA: 5 matched (len 5/6/5.8), 0 mismatched
-   NFA: 19 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 19 absorbed (len 1/1/1.0), 5 skipped (len 1/1/1.0)
    ->  Sort (actual rows=50.00 loops=1)
          Sort Key: (CASE WHEN (v.v <= 25) THEN 1 ELSE 2 END)
          Sort Method: quicksort  Memory: 26kB
@@ -1841,9 +1883,9 @@ WINDOW w AS (
    Pattern: (a' b' c')+"
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 81 total, 0 merged
-   NFA Contexts: 3 peak, 61 total, 40 pruned
+   NFA Contexts: 3 peak, 61 total, 20 pruned
    NFA: 1 matched (len 60/60/60.0), 0 mismatched
-   NFA: 19 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 19 absorbed (len 1/1/1.0), 20 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
 (9 rows)
 
@@ -1885,10 +1927,11 @@ WINDOW w AS (
    Pattern: (a | b) (c | d | e)
    Storage: Memory  Maximum Storage: NkB
    NFA States: 5 peak, 282 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 60 pruned
+   NFA Contexts: 3 peak, 101 total, 40 pruned
    NFA: 20 matched (len 2/2/2.0), 20 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_test (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Optional elements
@@ -1924,10 +1967,11 @@ WINDOW w AS (
    Pattern: a b? c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 64 total, 0 merged
-   NFA Contexts: 3 peak, 51 total, 37 pruned
+   NFA Contexts: 3 peak, 51 total, 25 pruned
    NFA: 12 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 12 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Bounded quantifiers
@@ -1963,9 +2007,9 @@ WINDOW w AS (
    Pattern: a{2,5} b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 9 peak, 311 total, 0 merged
-   NFA Contexts: 7 peak, 101 total, 10 pruned
-   NFA: 10 matched (len 6/6/6.0), 50 mismatched (len 2/6/5.2)
-   NFA: 0 absorbed, 30 skipped (len 3/5/4.0)
+   NFA Contexts: 7 peak, 101 total, 0 pruned
+   NFA: 10 matched (len 6/6/6.0), 40 mismatched (len 6/6/6.0)
+   NFA: 0 absorbed, 50 skipped (len 1/5/3.0)
    ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
 (9 rows)
 
@@ -2003,10 +2047,11 @@ WINDOW w AS (
    Pattern: a b* c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 91 total, 0 merged
-   NFA Contexts: 3 peak, 51 total, 45 pruned
+   NFA Contexts: 3 peak, 51 total, 40 pruned
    NFA: 5 matched (len 9/9/9.0), 0 mismatched
+   NFA: 0 absorbed, 5 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- ============================================================
@@ -2045,9 +2090,9 @@ WINDOW w AS (
    Pattern: d+" u+
    Storage: Memory  Maximum Storage: NkB
    NFA States: 4 peak, 58 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 17 pruned
+   NFA Contexts: 3 peak, 31 total, 3 pruned
    NFA: 3 matched (len 3/14/8.0), 1 mismatched (len 3/3/3.0)
-   NFA: 9 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 9 absorbed (len 1/1/1.0), 14 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_complex (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -2085,9 +2130,9 @@ WINDOW w AS (
    Pattern: u+" s* d+
    Storage: Memory  Maximum Storage: NkB
    NFA States: 5 peak, 76 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 14 pruned
+   NFA Contexts: 3 peak, 31 total, 1 pruned
    NFA: 4 matched (len 3/11/7.2), 0 mismatched
-   NFA: 12 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 12 absorbed (len 1/1/1.0), 13 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_complex (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -2168,10 +2213,11 @@ WINDOW w AS (
    Pattern: a b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 2 peak, 1001 total, 0 merged
-   NFA Contexts: 3 peak, 1001 total, 500 pruned
+   NFA Contexts: 2 peak, 1001 total, 0 pruned
    NFA: 500 matched (len 2/2/2.0), 0 mismatched
+   NFA: 0 absorbed, 500 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=1000.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Large dataset with absorption
@@ -2207,9 +2253,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 1991 total, 0 merged
-   NFA Contexts: 3 peak, 1001 total, 10 pruned
+   NFA Contexts: 2 peak, 1001 total, 0 pruned
    NFA: 10 matched (len 100/100/100.0), 0 mismatched
-   NFA: 980 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 980 absorbed (len 1/1/1.0), 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=1000.00 loops=1)
 (9 rows)
 
@@ -2247,9 +2293,9 @@ WINDOW w AS (
    Pattern: (a | b)+ c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 8 peak, 2004 total, 0 merged
-   NFA Contexts: 4 peak, 501 total, 167 pruned
+   NFA Contexts: 3 peak, 501 total, 1 pruned
    NFA: 166 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0)
-   NFA: 0 absorbed, 166 skipped (len 2/2/2.0)
+   NFA: 0 absorbed, 332 skipped (len 1/2/1.5)
    ->  Function Scan on generate_series s (actual rows=500.00 loops=1)
 (9 rows)
 
@@ -2292,9 +2338,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 91 total, 0 merged
-   NFA Contexts: 3 peak, 51 total, 10 pruned
+   NFA Contexts: 2 peak, 51 total, 0 pruned
    NFA: 10 matched (len 5/5/5.0), 0 mismatched
-   NFA: 30 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 30 absorbed (len 1/1/1.0), 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -2332,9 +2378,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 91 total, 0 merged
-   NFA Contexts: 3 peak, 51 total, 10 pruned
+   NFA Contexts: 2 peak, 51 total, 0 pruned
    NFA: 10 matched (len 5/5/5.0), 0 mismatched
-   NFA: 30 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 30 absorbed (len 1/1/1.0), 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -2455,9 +2501,9 @@ WINDOW w AS (
    Pattern: a? b c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 4 peak, 82 total, 0 merged
-   NFA Contexts: 4 peak, 41 total, 20 pruned
+   NFA Contexts: 3 peak, 41 total, 10 pruned
    NFA: 10 matched (len 3/3/3.0), 0 mismatched
-   NFA: 0 absorbed, 10 skipped (len 2/2/2.0)
+   NFA: 0 absorbed, 20 skipped (len 1/2/1.5)
    ->  Function Scan on generate_series s (actual rows=40.00 loops=1)
 (9 rows)
 
@@ -2495,10 +2541,11 @@ WINDOW w AS (
    Pattern: a{3} b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 4 peak, 51 total, 0 merged
-   NFA Contexts: 5 peak, 51 total, 10 pruned
-   NFA: 10 matched (len 4/4/4.0), 30 mismatched (len 2/4/3.0)
+   NFA Contexts: 5 peak, 51 total, 0 pruned
+   NFA: 10 matched (len 4/4/4.0), 10 mismatched (len 4/4/4.0)
+   NFA: 0 absorbed, 30 skipped (len 1/3/2.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Range {n,m}
@@ -2534,9 +2581,9 @@ WINDOW w AS (
    Pattern: a{2,4} b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 7 peak, 101 total, 0 merged
-   NFA Contexts: 6 peak, 51 total, 10 pruned
-   NFA: 10 matched (len 5/5/5.0), 10 mismatched (len 2/2/2.0)
-   NFA: 0 absorbed, 20 skipped (len 3/4/3.5)
+   NFA Contexts: 5 peak, 51 total, 0 pruned
+   NFA: 10 matched (len 5/5/5.0), 0 mismatched
+   NFA: 0 absorbed, 40 skipped (len 1/4/2.5)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -2574,9 +2621,9 @@ WINDOW w AS (
    Pattern: a{3,}" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 86 total, 0 merged
-   NFA Contexts: 3 peak, 51 total, 5 pruned
+   NFA Contexts: 2 peak, 51 total, 0 pruned
    NFA: 5 matched (len 10/10/10.0), 0 mismatched
-   NFA: 40 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 40 absorbed (len 1/1/1.0), 5 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -2618,9 +2665,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 37 total, 0 merged
-   NFA Contexts: 3 peak, 21 total, 4 pruned
+   NFA Contexts: 2 peak, 21 total, 0 pruned
    NFA: 4 matched (len 5/5/5.0), 0 mismatched
-   NFA: 12 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 12 absorbed (len 1/1/1.0), 4 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=20.00 loops=1)
 (9 rows)
 
@@ -2658,9 +2705,9 @@ WINDOW w AS (
    Pattern: a+" b c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 52 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 9 pruned
+   NFA Contexts: 3 peak, 31 total, 6 pruned
    NFA: 3 matched (len 9/9/9.0), 0 mismatched
-   NFA: 18 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 18 absorbed (len 1/1/1.0), 3 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -2698,10 +2745,11 @@ WINDOW w AS (
    Pattern: a b c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 2 peak, 31 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 20 pruned
+   NFA Contexts: 3 peak, 31 total, 10 pruned
    NFA: 10 matched (len 3/3/3.0), 0 mismatched
+   NFA: 0 absorbed, 10 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=30.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- ============================================================
@@ -2740,10 +2788,11 @@ WINDOW w AS (
    Pattern: (a | b) c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 202 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 60 pruned
+   NFA Contexts: 3 peak, 101 total, 40 pruned
    NFA: 20 matched (len 2/2/2.0), 20 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_test (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Multiple items in alternation
@@ -2783,10 +2832,11 @@ WINDOW w AS (
    Pattern: (a | b | c | d) e
    Storage: Memory  Maximum Storage: NkB
    NFA States: 5 peak, 404 total, 0 merged
-   NFA Contexts: 3 peak, 101 total, 20 pruned
+   NFA Contexts: 3 peak, 101 total, 0 pruned
    NFA: 20 matched (len 2/2/2.0), 60 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
    ->  Seq Scan on nfa_test (actual rows=100.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Alternation with quantifiers
@@ -2822,9 +2872,9 @@ WINDOW w AS (
    Pattern: (a | b)+ c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 8 peak, 204 total, 0 merged
-   NFA Contexts: 4 peak, 51 total, 17 pruned
+   NFA Contexts: 3 peak, 51 total, 1 pruned
    NFA: 16 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0)
-   NFA: 0 absorbed, 16 skipped (len 2/2/2.0)
+   NFA: 0 absorbed, 32 skipped (len 1/2/1.5)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -2845,7 +2895,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 100) AS s(v)
 WINDOW w AS (
@@ -2853,13 +2903,17 @@ WINDOW w AS (
     PATTERN (A | B | C | D | E)
     DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                           
+-----------------------------------------------------------------------
+ WindowAgg (actual rows=100.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b | c | d | e)
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 6 peak, 505 total, 0 merged
+   NFA Contexts: 2 peak, 101 total, 0 pruned
+   NFA: 100 matched (len 1/1/1.0), 0 mismatched
+   ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
+(8 rows)
 
 DROP VIEW rpr_v;
 -- Alternation at start
@@ -2878,7 +2932,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -2886,13 +2940,18 @@ WINDOW w AS (
     PATTERN ((A | B) C D)
     DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=60.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b) c d
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 3 peak, 122 total, 0 merged
+   NFA Contexts: 3 peak, 61 total, 16 pruned
+   NFA: 15 matched (len 3/3/3.0), 14 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 15 skipped (len 1/1/1.0)
+   ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Multiple sequential alternations
@@ -2911,7 +2970,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 100) AS s(v)
 WINDOW w AS (
@@ -2919,13 +2978,17 @@ WINDOW w AS (
     PATTERN ((A | B) C (D | E) F)
     DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2, D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                           
+-----------------------------------------------------------------------
+ WindowAgg (actual rows=100.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b) c (d | e) f
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 4 peak, 219 total, 0 merged
+   NFA Contexts: 3 peak, 101 total, 67 pruned
+   NFA: 0 matched, 33 mismatched (len 2/4/3.0)
+   ->  Function Scan on generate_series s (actual rows=100.00 loops=1)
+(8 rows)
 
 DROP VIEW rpr_v;
 -- Quantified alternatives
@@ -2944,7 +3007,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -2952,13 +3015,18 @@ WINDOW w AS (
     PATTERN ((A+ | B+) C)
     DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=60.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a+" | b+") c
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 4 peak, 162 total, 0 merged
+   NFA Contexts: 3 peak, 61 total, 1 pruned
+   NFA: 20 matched (len 2/2/2.0), 19 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
+   ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Alternation at end
@@ -2977,7 +3045,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -2985,13 +3053,18 @@ WINDOW w AS (
     PATTERN (A B (C | D))
     DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=60.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a b (c | d)
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 3 peak, 75 total, 0 merged
+   NFA Contexts: 3 peak, 61 total, 32 pruned
+   NFA: 14 matched (len 3/3/3.0), 0 mismatched
+   NFA: 0 absorbed, 14 skipped (len 1/1/1.0)
+   ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Nested ALT at start of branch inside outer ALT
@@ -3011,7 +3084,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 20) AS s(v)
 WINDOW w AS (
@@ -3019,13 +3092,17 @@ WINDOW w AS (
     PATTERN (A ((B | C) D | E))
     DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a ((b | c) d | e)
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 4 peak, 29 total, 0 merged
+   NFA Contexts: 3 peak, 21 total, 17 pruned
+   NFA: 0 matched, 3 mismatched (len 3/3/3.0)
+   ->  Function Scan on generate_series s (actual rows=20.00 loops=1)
+(8 rows)
 
 DROP VIEW rpr_v;
 -- Nested ALT at end of branch inside outer ALT
@@ -3045,7 +3122,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 20) AS s(v)
 WINDOW w AS (
@@ -3053,13 +3130,17 @@ WINDOW w AS (
     PATTERN (C (A | B) | D)
     DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (c (a | b) | d)
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 4 peak, 47 total, 0 merged
+   NFA Contexts: 3 peak, 21 total, 10 pruned
+   NFA: 5 matched (len 1/1/1.0), 5 mismatched (len 2/2/2.0)
+   ->  Function Scan on generate_series s (actual rows=20.00 loops=1)
+(8 rows)
 
 DROP VIEW rpr_v;
 -- ============================================================
@@ -3098,9 +3179,9 @@ WINDOW w AS (
    Pattern: (a' b')+"
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 61 total, 0 merged
-   NFA Contexts: 3 peak, 41 total, 20 pruned
+   NFA Contexts: 2 peak, 41 total, 0 pruned
    NFA: 1 matched (len 40/40/40.0), 0 mismatched
-   NFA: 19 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 19 absorbed (len 1/1/1.0), 20 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=40.00 loops=1)
 (9 rows)
 
@@ -3137,10 +3218,10 @@ WINDOW w AS (
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a b){2,4}
    Storage: Memory  Maximum Storage: NkB
-   NFA States: 7 peak, 66 total, 0 merged
-   NFA Contexts: 6 peak, 41 total, 20 pruned
+   NFA States: 4 peak, 51 total, 0 merged
+   NFA Contexts: 3 peak, 41 total, 5 pruned
    NFA: 5 matched (len 8/8/8.0), 0 mismatched
-   NFA: 0 absorbed, 15 skipped (len 2/6/4.0)
+   NFA: 0 absorbed, 30 skipped (len 1/2/1.5)
    ->  Function Scan on generate_series s (actual rows=40.00 loops=1)
 (9 rows)
 
@@ -3177,10 +3258,10 @@ WINDOW w AS (
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: ((a b){2})+
    Storage: Memory  Maximum Storage: NkB
-   NFA States: 60 peak, 286 total, 0 merged
-   NFA Contexts: 32 peak, 61 total, 30 pruned
-   NFA: 1 matched (len 60/60/60.0), 1 mismatched (len 2/2/2.0)
-   NFA: 0 absorbed, 28 skipped (len 4/58/31.0)
+   NFA States: 5 peak, 76 total, 0 merged
+   NFA Contexts: 4 peak, 61 total, 15 pruned
+   NFA: 1 matched (len 60/60/60.0), 0 mismatched
+   NFA: 0 absorbed, 44 skipped (len 1/4/2.3)
    ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
 (9 rows)
 
@@ -3201,7 +3282,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -3209,13 +3290,18 @@ WINDOW w AS (
     PATTERN ((((A | B)+)+)+)
     DEFINE A AS v % 2 = 0, B AS v % 2 = 1
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=40.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b)+
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 5 peak, 162 total, 0 merged
+   NFA Contexts: 2 peak, 41 total, 0 pruned
+   NFA: 1 matched (len 40/40/40.0), 0 mismatched
+   NFA: 0 absorbed, 39 skipped (len 1/1/1.0)
+   ->  Function Scan on generate_series s (actual rows=40.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Bounded quantifier on alternation
@@ -3234,7 +3320,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -3242,13 +3328,18 @@ WINDOW w AS (
     PATTERN ((A | B){2,3} C)
     DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=60.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a | b){2,3} c
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 7 peak, 200 total, 0 merged
+   NFA Contexts: 3 peak, 61 total, 2 pruned
+   NFA: 19 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0)
+   NFA: 0 absorbed, 38 skipped (len 1/2/1.5)
+   ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Nested groups with quantifiers
@@ -3267,7 +3358,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -3275,13 +3366,18 @@ WINDOW w AS (
     PATTERN (((A B)+ C)*)
     DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=60.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: ((a' b')+" c)*
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 7 peak, 178 total, 0 merged
+   NFA Contexts: 4 peak, 61 total, 22 pruned
+   NFA: 1 matched (len 57/57/57.0), 0 mismatched
+   NFA: 0 absorbed, 37 skipped (len 1/3/2.0)
+   ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Partial nested quantification
@@ -3300,7 +3396,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
 (1 row)
 
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -3308,13 +3404,18 @@ WINDOW w AS (
     PATTERN ((A (B C)+)*)
     DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
 );');
-                        rpr_explain_filter                         
--------------------------------------------------------------------
- WindowAgg
+                          rpr_explain_filter                          
+----------------------------------------------------------------------
+ WindowAgg (actual rows=60.00 loops=1)
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: (a (b c)+)*
-   ->  Function Scan on generate_series s
-(4 rows)
+   Storage: Memory  Maximum Storage: NkB
+   NFA States: 5 peak, 160 total, 0 merged
+   NFA Contexts: 4 peak, 61 total, 22 pruned
+   NFA: 1 matched (len 57/57/57.0), 0 mismatched
+   NFA: 0 absorbed, 37 skipped (len 1/3/2.0)
+   ->  Function Scan on generate_series s (actual rows=60.00 loops=1)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- ============================================================
@@ -3353,9 +3454,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 55 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 6 pruned
+   NFA Contexts: 2 peak, 31 total, 0 pruned
    NFA: 6 matched (len 5/5/5.0), 0 mismatched
-   NFA: 18 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 18 absorbed (len 1/1/1.0), 6 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -3393,9 +3494,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 55 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 6 pruned
+   NFA Contexts: 2 peak, 31 total, 0 pruned
    NFA: 6 matched (len 5/5/5.0), 0 mismatched
-   NFA: 18 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 18 absorbed (len 1/1/1.0), 6 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -3433,9 +3534,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 55 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 6 pruned
+   NFA Contexts: 2 peak, 31 total, 0 pruned
    NFA: 6 matched (len 5/5/5.0), 0 mismatched
-   NFA: 18 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 18 absorbed (len 1/1/1.0), 6 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -3479,9 +3580,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 55 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 6 pruned
+   NFA Contexts: 2 peak, 31 total, 0 pruned
    NFA: 6 matched (len 5/5/5.0), 0 mismatched
-   NFA: 18 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 18 absorbed (len 1/1/1.0), 6 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -3526,9 +3627,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 78 total, 0 merged
-   NFA Contexts: 3 peak, 51 total, 23 pruned
+   NFA Contexts: 2 peak, 51 total, 6 pruned
    NFA: 17 matched (len 2/3/2.6), 0 mismatched
-   NFA: 10 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 10 absorbed (len 1/1/1.0), 17 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=50.00 loops=1)
 (9 rows)
 
@@ -3617,9 +3718,9 @@ WINDOW w AS (
    Pattern: a+" b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 55 total, 0 merged
-   NFA Contexts: 3 peak, 31 total, 6 pruned
+   NFA Contexts: 2 peak, 31 total, 0 pruned
    NFA: 6 matched (len 5/5/5.0), 0 mismatched
-   NFA: 18 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 18 absorbed (len 1/1/1.0), 6 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series v (actual rows=30.00 loops=1)
 (9 rows)
 
@@ -3660,9 +3761,9 @@ WINDOW w AS (
    Pattern: a+" b c
    Storage: Memory  Maximum Storage: NkB
    NFA States: 3 peak, 851 total, 0 merged
-   NFA Contexts: 3 peak, 501 total, 151 pruned
+   NFA Contexts: 3 peak, 501 total, 101 pruned
    NFA: 50 matched (len 8/9/9.0), 0 mismatched
-   NFA: 299 absorbed (len 1/1/1.0), 0 skipped
+   NFA: 299 absorbed (len 1/1/1.0), 50 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=500.00 loops=1)
 (9 rows)
 
@@ -3700,10 +3801,11 @@ WINDOW w AS (
    Pattern: a b
    Storage: Memory  Maximum Storage: NkB
    NFA States: 2 peak, 501 total, 0 merged
-   NFA Contexts: 3 peak, 501 total, 250 pruned
+   NFA Contexts: 2 peak, 501 total, 0 pruned
    NFA: 250 matched (len 2/2/2.0), 0 mismatched
+   NFA: 0 absorbed, 250 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=500.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- High skip count scenario
@@ -3749,10 +3851,11 @@ WINDOW w AS (
    Pattern: a b c d e
    Storage: Memory  Maximum Storage: NkB
    NFA States: 2 peak, 501 total, 0 merged
-   NFA Contexts: 3 peak, 501 total, 495 pruned
+   NFA Contexts: 3 peak, 501 total, 490 pruned
    NFA: 5 matched (len 5/5/5.0), 0 mismatched
+   NFA: 0 absorbed, 5 skipped (len 1/1/1.0)
    ->  Function Scan on generate_series s (actual rows=500.00 loops=1)
-(8 rows)
+(9 rows)
 
 DROP VIEW rpr_v;
 -- Cleanup
diff --git a/src/test/regress/expected/rpr_nfa.out b/src/test/regress/expected/rpr_nfa.out
new file mode 100644
index 00000000000..46a463c2597
--- /dev/null
+++ b/src/test/regress/expected/rpr_nfa.out
@@ -0,0 +1,2524 @@
+-- ============================================================
+-- RPR NFA Tests
+-- Tests for Row Pattern Recognition NFA Runtime Execution
+-- ============================================================
+--
+-- This test suite validates the NFA (Non-deterministic Finite
+-- Automaton) runtime execution engine in nodeWindowAgg.c,
+-- focusing on update_reduced_frame and related functions.
+--
+-- Test Strategy:
+--   Diagonal pattern style using ARRAY flags to explicitly
+--   control which pattern variables match at each row.
+--
+-- Test Coverage:
+--   Basic NFA Flow (match->absorb->advance)
+--   Absorption Optimization
+--   Context Lifecycle Management
+--   Advance Phase (Epsilon Transitions)
+--   Match Phase (Variable Matching)
+--   Frame Boundary Handling
+--   State Management (Deduplication)
+--   Statistics and Diagnostics
+--   Quantifier Runtime Behavior
+--   Pathological Pattern Protection
+--   Alternation Runtime Behavior
+--   Deep Nested Groups
+--   SKIP Options (Runtime)
+--   INITIAL Mode (Runtime)
+--   Frame Boundary Variations
+--   Special Partition Cases
+--   DEFINE Special Cases
+--   Absorption Dynamic Flags
+--   FIXME Issues (Known Limitations)
+--
+-- Responsibility:
+--   - NFA runtime execution paths
+--   - Context/State lifecycle management
+--   - Runtime boundary conditions and protections
+--
+-- NOT tested here (covered in other files):
+--   - Pattern parsing/optimization (rpr_base.sql)
+--   - EXPLAIN output (rpr_explain.sql)
+--   - PREV/NEXT semantics (rpr.sql)
+-- ============================================================
+-- ============================================================
+-- Basic NFA Flow
+-- ============================================================
+-- Simple sequential pattern
+WITH test_sequential AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['D']),
+        (5, ARRAY['_'])  -- No match
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_sequential
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {B}   |             |          
+  3 | {C}   |             |          
+  4 | {D}   |             |          
+  5 | {_}   |             |          
+(5 rows)
+
+-- Quantified pattern (A+ B+ C+)
+WITH test_quantified AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['B']),
+        (6, ARRAY['C']),
+        (7, ARRAY['C']),
+        (8, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_quantified
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B+ C+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         7
+  2 | {A}   |           2 |         7
+  3 | {A}   |           3 |         7
+  4 | {B}   |             |          
+  5 | {B}   |             |          
+  6 | {C}   |             |          
+  7 | {C}   |             |          
+  8 | {_}   |             |          
+(8 rows)
+
+-- Optional pattern (A B? C)
+WITH test_optional AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['C']),  -- B skipped
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C']),  -- B matched
+        (6, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_optional
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B? C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         2
+  2 | {C}   |             |          
+  3 | {A}   |           3 |         5
+  4 | {B}   |             |          
+  5 | {C}   |             |          
+  6 | {_}   |             |          
+(6 rows)
+
+-- Alternation pattern (A (B|C) D)
+WITH test_alternation AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),  -- First branch
+        (3, ARRAY['D']),
+        (4, ARRAY['A']),
+        (5, ARRAY['C']),  -- Second branch
+        (6, ARRAY['D']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alternation
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A (B | C) D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {B}   |             |          
+  3 | {D}   |             |          
+  4 | {A}   |           4 |         6
+  5 | {C}   |             |          
+  6 | {D}   |             |          
+  7 | {_}   |             |          
+(7 rows)
+
+-- ============================================================
+-- Absorption Optimization
+-- ============================================================
+-- Absorbable pattern (A+)
+WITH test_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {A}   |           4 |         4
+  5 | {_}   |             |          
+(5 rows)
+
+-- Mixed absorbable/non-absorbable ((A+) | B)
+WITH test_mixed_absorption AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_mixed_absorption
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A+) | B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {A}   |           2 |         3
+  3 | {A}   |           3 |         3
+  4 | {B}   |           4 |         4
+  5 | {_}   |             |          
+(5 rows)
+
+-- State coverage (same elemIdx, different count)
+WITH test_state_coverage AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_state_coverage
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{2,} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |             |          
+  4 | {B}   |             |          
+  5 | {_}   |             |          
+(5 rows)
+
+-- ============================================================
+-- Context Lifecycle
+-- ============================================================
+-- Multiple overlapping contexts (SKIP TO NEXT ROW)
+WITH test_overlapping_contexts AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_overlapping_contexts
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {B}   |             |          
+  5 | {_}   |             |          
+(5 rows)
+
+-- Failed context cleanup (early failure)
+WITH test_context_cleanup AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Pruned at first row
+        (2, ARRAY['A']),
+        (3, ARRAY['_']),  -- Mismatched after row 2
+        (4, ARRAY['A']),
+        (5, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_context_cleanup
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {_}   |             |          
+  2 | {A}   |             |          
+  3 | {_}   |             |          
+  4 | {A}   |           4 |         5
+  5 | {B}   |             |          
+(5 rows)
+
+-- Partition end (incomplete contexts)
+WITH test_partition_end AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A'])
+        -- Pattern requires B, but partition ends
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_partition_end
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {A}   |             |          
+  3 | {A}   |             |          
+(3 rows)
+
+-- Completed context encountered during processing
+-- Pattern (A | B C D): Ctx1 takes long B->C->D path, while Ctx2 takes
+-- short A path and completes first. Next row sees Ctx2
+-- with states=NULL and skips it.
+WITH test_completed_ctx AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['B', '_']),
+        (2, ARRAY['C', 'A']),
+        (3, ARRAY['D', '_']),
+        (4, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_completed_ctx
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A | B C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {B,_} |           1 |         3
+  2 | {C,A} |           2 |         2
+  3 | {D,_} |             |          
+  4 | {_,_} |             |          
+(4 rows)
+
+-- ============================================================
+-- Advance Phase (Epsilon Transitions)
+-- ============================================================
+-- Nested groups ((A B)+)
+WITH test_nested_groups AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_groups
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A B)+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         6
+  2 | {B}   |             |          
+  3 | {A}   |           3 |         6
+  4 | {B}   |             |          
+  5 | {A}   |           5 |         6
+  6 | {B}   |             |          
+  7 | {_}   |             |          
+(7 rows)
+
+-- Multiple alternation branches (A (B|C|D) E)
+WITH test_multi_alt AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['E']),
+        (4, ARRAY['A']),
+        (5, ARRAY['C']),
+        (6, ARRAY['E']),
+        (7, ARRAY['A']),
+        (8, ARRAY['D']),
+        (9, ARRAY['E'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_multi_alt
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A (B | C | D) E)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags),
+        E AS 'E' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {B}   |             |          
+  3 | {E}   |             |          
+  4 | {A}   |           4 |         6
+  5 | {C}   |             |          
+  6 | {E}   |             |          
+  7 | {A}   |           7 |         9
+  8 | {D}   |             |          
+  9 | {E}   |             |          
+(9 rows)
+
+-- Optional VAR at start (A? B C)
+WITH test_optional_var AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['B']),  -- A skipped
+        (2, ARRAY['C']),
+        (3, ARRAY['A']),  -- A matched
+        (4, ARRAY['B']),
+        (5, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_optional_var
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A? B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {B}   |           1 |         2
+  2 | {C}   |             |          
+  3 | {A}   |           3 |         5
+  4 | {B}   |           4 |         5
+  5 | {C}   |             |          
+(5 rows)
+
+-- Nested alternation ((A|B) (C|D))
+WITH test_nested_alt AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['C']),  -- A C
+        (3, ARRAY['A']),
+        (4, ARRAY['D']),  -- A D
+        (5, ARRAY['B']),
+        (6, ARRAY['C']),  -- B C
+        (7, ARRAY['B']),
+        (8, ARRAY['D'])   -- B D
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_alt
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A | B) (C | D))
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         2
+  2 | {C}   |             |          
+  3 | {A}   |           3 |         4
+  4 | {D}   |             |          
+  5 | {B}   |           5 |         6
+  6 | {C}   |             |          
+  7 | {B}   |           7 |         8
+  8 | {D}   |             |          
+(8 rows)
+
+-- ============================================================
+-- Match Phase
+-- ============================================================
+-- Simple VAR with END next (A B C all min=max=1)
+WITH test_simple_var AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_simple_var
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {B}   |             |          
+  3 | {C}   |             |          
+  4 | {_}   |             |          
+(4 rows)
+
+-- VAR max exceeded (A{2,3})
+WITH test_max_exceeded AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),  -- Max = 3
+        (4, ARRAY['A']),  -- Exceeds max, state removed
+        (5, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_max_exceeded
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{2,3} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {A}   |           2 |         5
+  3 | {A}   |           3 |         5
+  4 | {A}   |             |          
+  5 | {B}   |             |          
+(5 rows)
+
+-- Non-matching VAR (DEFINE false)
+WITH test_non_matching AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['_']),  -- B not matched (DEFINE false)
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),  -- B matched
+        (5, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_non_matching
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {_}   |             |          
+  3 | {A}   |           3 |         5
+  4 | {B}   |             |          
+  5 | {C}   |             |          
+(5 rows)
+
+-- ============================================================
+-- Frame Boundary Handling
+-- ============================================================
+-- Limited frame (ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING)
+WITH test_limited_frame AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),  -- Within 3 FOLLOWING
+        (5, ARRAY['B']),  -- Beyond 3 FOLLOWING from row 1
+        (6, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_limited_frame
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {B}   |             |          
+  5 | {B}   |             |          
+  6 | {_}   |             |          
+(6 rows)
+
+-- Unbounded frame (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+WITH test_unbounded_frame AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B'])  -- Far from start, but unbounded
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_unbounded_frame
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         6
+  2 | {A}   |           2 |         6
+  3 | {A}   |           3 |         6
+  4 | {A}   |           4 |         6
+  5 | {A}   |           5 |         6
+  6 | {B}   |             |          
+(6 rows)
+
+-- Match exceeds frame boundary
+WITH test_frame_exceeded AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A'])
+        -- Frame ends at row 3 (2 FOLLOWING), B never appears
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_frame_exceeded
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {A}   |             |          
+  3 | {A}   |             |          
+(3 rows)
+
+-- Frame boundary forced mismatch
+-- Limited frame with enough rows so that a context's frame boundary
+-- is exceeded while still processing.
+WITH test_frame_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_frame_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {A}   |             |          
+  3 | {A}   |             |          
+  4 | {A}   |           4 |         6
+  5 | {A}   |           5 |         6
+  6 | {B}   |             |          
+(6 rows)
+
+-- ============================================================
+-- State Management
+-- ============================================================
+-- Duplicate state creation
+WITH test_duplicate_states AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A', 'B']),  -- Both A and B match (creates duplicate states via different paths)
+        (2, ARRAY['C', '_']),
+        (3, ARRAY['D', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_duplicate_states
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A | B) C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A,B} |           1 |         3
+  2 | {C,_} |             |          
+  3 | {D,_} |             |          
+(3 rows)
+
+-- Large pattern (stress free list)
+WITH test_large_pattern AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['D']),
+        (5, ARRAY['E']),
+        (6, ARRAY['F']),
+        (7, ARRAY['G']),
+        (8, ARRAY['H']),
+        (9, ARRAY['I']),
+        (10, ARRAY['J'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_large_pattern
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C D E F G H I J)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags),
+        E AS 'E' = ANY(flags),
+        F AS 'F' = ANY(flags),
+        G AS 'G' = ANY(flags),
+        H AS 'H' = ANY(flags),
+        I AS 'I' = ANY(flags),
+        J AS 'J' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |        10
+  2 | {B}   |             |          
+  3 | {C}   |             |          
+  4 | {D}   |             |          
+  5 | {E}   |             |          
+  6 | {F}   |             |          
+  7 | {G}   |             |          
+  8 | {H}   |             |          
+  9 | {I}   |             |          
+ 10 | {J}   |             |          
+(10 rows)
+
+-- Reduced frame map reallocation (> 1024 rows)
+WITH test_map_realloc AS (
+    SELECT id, CASE WHEN id % 2 = 1 THEN ARRAY['A'] ELSE ARRAY['B'] END AS flags
+    FROM generate_series(1, 1100) AS id
+)
+SELECT count(*), min(match_start), max(match_end)
+FROM (
+    SELECT id, flags,
+           first_value(id) OVER w AS match_start,
+           last_value(id) OVER w AS match_end
+    FROM test_map_realloc
+    WINDOW w AS (
+        ORDER BY id
+        ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+        AFTER MATCH SKIP TO NEXT ROW
+        PATTERN (A B)
+        DEFINE
+            A AS 'A' = ANY(flags),
+            B AS 'B' = ANY(flags)
+    )
+) sub;
+ count | min | max  
+-------+-----+------
+  1100 |   1 | 1100
+(1 row)
+
+-- ============================================================
+-- Statistics and Diagnostics
+-- ============================================================
+-- Matched contexts
+WITH test_matched AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_matched
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         2
+  2 | {B}   |             |          
+  3 | {A}   |           3 |         4
+  4 | {B}   |             |          
+(4 rows)
+
+-- Pruned contexts (failed at first row)
+WITH test_pruned AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Pruned
+        (2, ARRAY['_']),  -- Pruned
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_pruned
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {_}   |             |          
+  2 | {_}   |             |          
+  3 | {A}   |           3 |         4
+  4 | {B}   |             |          
+(4 rows)
+
+-- Mismatched contexts (failed after multiple rows)
+WITH test_mismatched AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['_']),  -- Mismatched after 2 rows
+        (4, ARRAY['A']),
+        (5, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_mismatched
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {A}   |             |          
+  3 | {_}   |             |          
+  4 | {A}   |           4 |         5
+  5 | {B}   |             |          
+(5 rows)
+
+-- Absorbed contexts
+WITH test_absorbed AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorbed
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {A}   |           4 |         4
+  5 | {_}   |             |          
+(5 rows)
+
+-- Skipped contexts (SKIP TO NEXT ROW)
+WITH test_skipped AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])  -- Completes match starting at row 1
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skipped
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {B}   |             |          
+(4 rows)
+
+-- ============================================================
+-- Quantifier Runtime Behavior
+-- ============================================================
+-- Large count handling (A{100})
+WITH test_large_count AS (
+    SELECT i AS id, ARRAY['A'] AS flags
+    FROM generate_series(1, 105) i
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_large_count
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{100})
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id  | flags | match_start | match_end 
+-----+-------+-------------+-----------
+   1 | {A}   |           1 |       100
+   2 | {A}   |           2 |       101
+   3 | {A}   |           3 |       102
+   4 | {A}   |           4 |       103
+   5 | {A}   |           5 |       104
+   6 | {A}   |           6 |       105
+   7 | {A}   |             |          
+   8 | {A}   |             |          
+   9 | {A}   |             |          
+  10 | {A}   |             |          
+  11 | {A}   |             |          
+  12 | {A}   |             |          
+  13 | {A}   |             |          
+  14 | {A}   |             |          
+  15 | {A}   |             |          
+  16 | {A}   |             |          
+  17 | {A}   |             |          
+  18 | {A}   |             |          
+  19 | {A}   |             |          
+  20 | {A}   |             |          
+  21 | {A}   |             |          
+  22 | {A}   |             |          
+  23 | {A}   |             |          
+  24 | {A}   |             |          
+  25 | {A}   |             |          
+  26 | {A}   |             |          
+  27 | {A}   |             |          
+  28 | {A}   |             |          
+  29 | {A}   |             |          
+  30 | {A}   |             |          
+  31 | {A}   |             |          
+  32 | {A}   |             |          
+  33 | {A}   |             |          
+  34 | {A}   |             |          
+  35 | {A}   |             |          
+  36 | {A}   |             |          
+  37 | {A}   |             |          
+  38 | {A}   |             |          
+  39 | {A}   |             |          
+  40 | {A}   |             |          
+  41 | {A}   |             |          
+  42 | {A}   |             |          
+  43 | {A}   |             |          
+  44 | {A}   |             |          
+  45 | {A}   |             |          
+  46 | {A}   |             |          
+  47 | {A}   |             |          
+  48 | {A}   |             |          
+  49 | {A}   |             |          
+  50 | {A}   |             |          
+  51 | {A}   |             |          
+  52 | {A}   |             |          
+  53 | {A}   |             |          
+  54 | {A}   |             |          
+  55 | {A}   |             |          
+  56 | {A}   |             |          
+  57 | {A}   |             |          
+  58 | {A}   |             |          
+  59 | {A}   |             |          
+  60 | {A}   |             |          
+  61 | {A}   |             |          
+  62 | {A}   |             |          
+  63 | {A}   |             |          
+  64 | {A}   |             |          
+  65 | {A}   |             |          
+  66 | {A}   |             |          
+  67 | {A}   |             |          
+  68 | {A}   |             |          
+  69 | {A}   |             |          
+  70 | {A}   |             |          
+  71 | {A}   |             |          
+  72 | {A}   |             |          
+  73 | {A}   |             |          
+  74 | {A}   |             |          
+  75 | {A}   |             |          
+  76 | {A}   |             |          
+  77 | {A}   |             |          
+  78 | {A}   |             |          
+  79 | {A}   |             |          
+  80 | {A}   |             |          
+  81 | {A}   |             |          
+  82 | {A}   |             |          
+  83 | {A}   |             |          
+  84 | {A}   |             |          
+  85 | {A}   |             |          
+  86 | {A}   |             |          
+  87 | {A}   |             |          
+  88 | {A}   |             |          
+  89 | {A}   |             |          
+  90 | {A}   |             |          
+  91 | {A}   |             |          
+  92 | {A}   |             |          
+  93 | {A}   |             |          
+  94 | {A}   |             |          
+  95 | {A}   |             |          
+  96 | {A}   |             |          
+  97 | {A}   |             |          
+  98 | {A}   |             |          
+  99 | {A}   |             |          
+ 100 | {A}   |             |          
+ 101 | {A}   |             |          
+ 102 | {A}   |             |          
+ 103 | {A}   |             |          
+ 104 | {A}   |             |          
+ 105 | {A}   |             |          
+(105 rows)
+
+-- Unlimited quantifier (A{10,})
+WITH test_unlimited AS (
+    SELECT i AS id, ARRAY['A'] AS flags
+    FROM generate_series(1, 15) i
+    UNION ALL
+    SELECT 16, ARRAY['B']
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_unlimited
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{10,} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |        16
+  2 | {A}   |           2 |        16
+  3 | {A}   |           3 |        16
+  4 | {A}   |           4 |        16
+  5 | {A}   |           5 |        16
+  6 | {A}   |           6 |        16
+  7 | {A}   |             |          
+  8 | {A}   |             |          
+  9 | {A}   |             |          
+ 10 | {A}   |             |          
+ 11 | {A}   |             |          
+ 12 | {A}   |             |          
+ 13 | {A}   |             |          
+ 14 | {A}   |             |          
+ 15 | {A}   |             |          
+ 16 | {B}   |             |          
+(16 rows)
+
+-- Min boundary (A{3,5})
+WITH test_min_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),  -- Min=3 reached, exit path available
+        (4, ARRAY['B']),  -- Match ends at min
+        (5, ARRAY['A']),
+        (6, ARRAY['A']),
+        (7, ARRAY['A']),
+        (8, ARRAY['A']),
+        (9, ARRAY['A']),  -- Count=5, max reached
+        (10, ARRAY['B'])  -- Match ends at max
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_min_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{3,5} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |             |          
+  3 | {A}   |             |          
+  4 | {B}   |             |          
+  5 | {A}   |           5 |        10
+  6 | {A}   |           6 |        10
+  7 | {A}   |           7 |        10
+  8 | {A}   |             |          
+  9 | {A}   |             |          
+ 10 | {B}   |             |          
+(10 rows)
+
+-- Max boundary exceeded (A{3,5})
+WITH test_max_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['A']),  -- Count=6 > max=5, row 1 context removed
+        (7, ARRAY['B'])   -- Row 1 context: no match (exceeded max)
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_max_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{3,5} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {A}   |           2 |         7
+  3 | {A}   |           3 |         7
+  4 | {A}   |           4 |         7
+  5 | {A}   |             |          
+  6 | {A}   |             |          
+  7 | {B}   |             |          
+(7 rows)
+
+-- ============================================================
+-- Pathological Pattern Runtime Protection
+-- ============================================================
+-- Complex nested nullable ((A* B*)*) - Runtime protection
+WITH test_complex_nested AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['B']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_complex_nested
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A* B*)*)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {B}   |           3 |         4
+  4 | {B}   |           4 |         4
+  5 | {C}   |             |          
+(5 rows)
+
+-- Nested nullable with quantifier ((A{0,3})*)
+WITH test_nested_quantifier AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_quantifier
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A{0,3})*)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {A}   |           2 |         3
+  3 | {A}   |           3 |         3
+  4 | {B}   |             |          
+(4 rows)
+
+-- ============================================================
+-- Alternation Runtime Behavior
+-- ============================================================
+-- Multi-branch alternation (A (B|C|D|E) F)
+WITH test_multi_branch AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['F']),
+        (4, ARRAY['A']),
+        (5, ARRAY['C']),
+        (6, ARRAY['F']),
+        (7, ARRAY['A']),
+        (8, ARRAY['D']),
+        (9, ARRAY['F']),
+        (10, ARRAY['A']),
+        (11, ARRAY['E']),
+        (12, ARRAY['F'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_multi_branch
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A (B | C | D | E) F)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags),
+        E AS 'E' = ANY(flags),
+        F AS 'F' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {B}   |             |          
+  3 | {F}   |             |          
+  4 | {A}   |           4 |         6
+  5 | {C}   |             |          
+  6 | {F}   |             |          
+  7 | {A}   |           7 |         9
+  8 | {D}   |             |          
+  9 | {F}   |             |          
+ 10 | {A}   |          10 |        12
+ 11 | {E}   |             |          
+ 12 | {F}   |             |          
+(12 rows)
+
+-- Alternation with quantifiers (A+ | B+ | C+)
+WITH test_alt_quantifiers AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['B']),
+        (6, ARRAY['C']),
+        (7, ARRAY['C']),
+        (8, ARRAY['C']),
+        (9, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_quantifiers
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ | B+ | C+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {A}   |           2 |         3
+  3 | {A}   |           3 |         3
+  4 | {B}   |           4 |         5
+  5 | {B}   |           5 |         5
+  6 | {C}   |           6 |         9
+  7 | {C}   |           7 |         9
+  8 | {C}   |           8 |         9
+  9 | {C}   |           9 |         9
+(9 rows)
+
+-- altPriority replacement (A B C | D)
+-- D branch (higher altPriority) matches first at row 1,
+-- then A B C branch (lower altPriority) replaces it at row 3.
+WITH test_alt_replace AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A', 'D']),
+        (2, ARRAY['B', '_']),
+        (3, ARRAY['C', '_']),
+        (4, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_replace
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C | D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A,D} |           1 |         3
+  2 | {B,_} |             |          
+  3 | {C,_} |             |          
+  4 | {_,_} |             |          
+(4 rows)
+
+-- ============================================================
+-- Deep Nested Groups
+-- ============================================================
+-- Three-level nesting ((((A B)+)+)+)
+WITH test_deep_nesting AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_deep_nesting
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((((A B)+)+)+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         6
+  2 | {B}   |             |          
+  3 | {A}   |           3 |         6
+  4 | {B}   |             |          
+  5 | {A}   |           5 |         6
+  6 | {B}   |             |          
+  7 | {_}   |             |          
+(7 rows)
+
+-- Multiple groups in nesting (((A B) (C D))+)
+WITH test_nested_sequential AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['D']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['C']),
+        (8, ARRAY['D']),
+        (9, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_sequential
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A B) (C D))+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         8
+  2 | {B}   |             |          
+  3 | {C}   |             |          
+  4 | {D}   |             |          
+  5 | {A}   |           5 |         8
+  6 | {B}   |             |          
+  7 | {C}   |             |          
+  8 | {D}   |             |          
+  9 | {_}   |             |          
+(9 rows)
+
+-- Nested END→END max reached
+-- Inner group (A B){2} reaches max=2 → exits to outer END
+WITH test_end_nested_max AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['A']),
+        (8, ARRAY['B']),
+        (9, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_end_nested_max
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A B){2})+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         8
+  2 | {B}   |             |          
+  3 | {A}   |           3 |         6
+  4 | {B}   |             |          
+  5 | {A}   |           5 |         8
+  6 | {B}   |             |          
+  7 | {A}   |             |          
+  8 | {B}   |             |          
+  9 | {_}   |             |          
+(9 rows)
+
+-- Nested END→END between min/max
+-- Inner group (A B){1,3} exits between min/max → outer END count++
+WITH test_end_nested_mid AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['A']),
+        (8, ARRAY['B']),
+        (9, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_end_nested_mid
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A B){1,3})+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         8
+  2 | {B}   |             |          
+  3 | {A}   |           3 |         8
+  4 | {B}   |             |          
+  5 | {A}   |           5 |         8
+  6 | {B}   |             |          
+  7 | {A}   |           7 |         8
+  8 | {B}   |             |          
+  9 | {_}   |             |          
+(9 rows)
+
+-- ============================================================
+-- SKIP Options (Runtime)
+-- ============================================================
+-- SKIP PAST LAST ROW (non-overlapping matches)
+WITH test_skip_past AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_past
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |             |          
+  3 | {A}   |             |          
+  4 | {A}   |             |          
+  5 | {_}   |             |          
+(5 rows)
+
+-- SKIP TO NEXT ROW (overlapping matches)
+WITH test_skip_next AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_next
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {A}   |           4 |         4
+  5 | {_}   |             |          
+(5 rows)
+
+-- SKIP difference verification
+WITH test_skip_diff AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT 'SKIP PAST' AS mode, id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_diff
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+)
+UNION ALL
+SELECT 'SKIP NEXT' AS mode, id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_diff
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+)
+ORDER BY mode, id;
+   mode    | id | flags | match_start | match_end 
+-----------+----+-------+-------------+-----------
+ SKIP NEXT |  1 | {A}   |           1 |         2
+ SKIP NEXT |  2 | {B}   |             |          
+ SKIP NEXT |  3 | {A}   |           3 |         4
+ SKIP NEXT |  4 | {B}   |             |          
+ SKIP PAST |  1 | {A}   |           1 |         2
+ SKIP PAST |  2 | {B}   |             |          
+ SKIP PAST |  3 | {A}   |           3 |         4
+ SKIP PAST |  4 | {B}   |             |          
+(8 rows)
+
+-- ============================================================
+-- INITIAL Mode (Runtime)
+-- ============================================================
+-- INITIAL mode (not yet supported - produces syntax error)
+WITH test_initial_mode AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Unmatched
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['_']),  -- Unmatched
+        (5, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_initial_mode
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    INITIAL
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ERROR:  syntax error at or near "AFTER"
+LINE 18:     AFTER MATCH SKIP TO NEXT ROW
+             ^
+-- Default mode (include all rows)
+WITH test_default_mode AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Unmatched, but included
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['_']),  -- Unmatched, but included
+        (5, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_default_mode
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {_}   |             |          
+  2 | {A}   |           2 |         3
+  3 | {A}   |           3 |         3
+  4 | {_}   |             |          
+  5 | {A}   |           5 |         5
+(5 rows)
+
+-- Mode difference verification (INITIAL not yet supported - produces syntax error)
+WITH test_mode_diff AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),
+        (2, ARRAY['A']),
+        (3, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT 'INITIAL' AS mode, COUNT(*) AS row_count
+FROM (
+    SELECT id FROM test_mode_diff
+    WINDOW w AS (
+        ORDER BY id
+        ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+        INITIAL
+        AFTER MATCH SKIP TO NEXT ROW
+        PATTERN (A)
+        DEFINE A AS 'A' = ANY(flags)
+    )
+) sub
+UNION ALL
+SELECT 'DEFAULT' AS mode, COUNT(*) AS row_count
+FROM (
+    SELECT id FROM test_mode_diff
+    WINDOW w AS (
+        ORDER BY id
+        ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+        AFTER MATCH SKIP TO NEXT ROW
+        PATTERN (A)
+        DEFINE A AS 'A' = ANY(flags)
+    )
+) sub
+ORDER BY mode;
+ERROR:  syntax error at or near "AFTER"
+LINE 15:         AFTER MATCH SKIP TO NEXT ROW
+                 ^
+-- ============================================================
+-- Frame Boundary Variations
+-- ============================================================
+-- Very limited frame (1 FOLLOWING)
+WITH test_one_following AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),  -- Within 1 FOLLOWING
+        (3, ARRAY['A']),  -- Beyond 1 FOLLOWING from row 1
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_one_following
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         2
+  2 | {B}   |             |          
+  3 | {A}   |           3 |         4
+  4 | {B}   |             |          
+(4 rows)
+
+-- Medium frame (10 FOLLOWING)
+WITH test_ten_following AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['A']),
+        (7, ARRAY['A']),
+        (8, ARRAY['A']),
+        (9, ARRAY['A']),
+        (10, ARRAY['A']),
+        (11, ARRAY['B']),  -- Within 10 FOLLOWING from row 1
+        (12, ARRAY['A']),
+        (13, ARRAY['B'])   -- Beyond 10 FOLLOWING from row 1
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_ten_following
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 10 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |        11
+  2 | {A}   |           2 |        11
+  3 | {A}   |           3 |        11
+  4 | {A}   |           4 |        11
+  5 | {A}   |           5 |        11
+  6 | {A}   |           6 |        11
+  7 | {A}   |           7 |        11
+  8 | {A}   |           8 |        11
+  9 | {A}   |           9 |        11
+ 10 | {A}   |          10 |        11
+ 11 | {B}   |             |          
+ 12 | {A}   |          12 |        13
+ 13 | {B}   |             |          
+(13 rows)
+
+-- Exact boundary match
+WITH test_exact_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['B'])   -- Exactly at 4 FOLLOWING (frame end)
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_exact_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         5
+  2 | {A}   |           2 |         5
+  3 | {A}   |           3 |         5
+  4 | {A}   |           4 |         5
+  5 | {B}   |             |          
+(5 rows)
+
+-- ============================================================
+-- Special Partition Cases
+-- ============================================================
+-- Empty partition (0 rows)
+WITH test_empty_partition AS (
+    SELECT * FROM (VALUES
+        (1, 1, ARRAY['A']),
+        (2, 2, ARRAY['_'])  -- Different partition
+    ) AS t(id, part, flags)
+)
+SELECT id, part, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_empty_partition
+WHERE part = 99  -- No rows match
+WINDOW w AS (
+    PARTITION BY part
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | part | flags | match_start | match_end 
+----+------+-------+-------------+-----------
+(0 rows)
+
+-- Single row partition
+WITH test_single_row AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_single_row
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         1
+(1 row)
+
+-- All rows fail matching (all DEFINE false)
+WITH test_all_fail AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_all_fail
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS false  -- All rows fail
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |             |          
+  2 | {A}   |             |          
+  3 | {A}   |             |          
+(3 rows)
+
+-- Partition end with absorbable pattern
+-- SKIP PAST LAST ROW + unbounded frame + all rows match A
+-- Triggers absorb in !rowExists path at partition boundary.
+WITH test_absorb_partition_end AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorb_partition_end
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         5
+  2 | {A}   |             |          
+  3 | {A}   |             |          
+  4 | {A}   |             |          
+  5 | {A}   |             |          
+(5 rows)
+
+-- ============================================================
+-- DEFINE Special Cases
+-- ============================================================
+-- Undefined variable in DEFINE
+WITH test_undefined_var AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['X']),  -- B not defined, defaults to TRUE
+        (3, ARRAY['C']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_']),  -- B defaults to TRUE, but no flags
+        (6, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_undefined_var
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        -- B is undefined, defaults to TRUE
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {X}   |             |          
+  3 | {C}   |             |          
+  4 | {A}   |           4 |         6
+  5 | {_}   |             |          
+  6 | {C}   |             |          
+(6 rows)
+
+-- ============================================================
+-- Absorption Dynamic Flags
+-- ============================================================
+-- Partial absorbable pattern ((A+) B)
+WITH test_partial_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_partial_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A+) B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         4
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {B}   |             |          
+  5 | {_}   |             |          
+(5 rows)
+
+-- Dynamic flag update ((A+) | B)
+WITH test_dynamic_flags AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_dynamic_flags
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A+) | B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {A}   |           2 |         3
+  3 | {A}   |           3 |         3
+  4 | {B}   |           4 |         4
+  5 | {A}   |           5 |         5
+  6 | {B}   |           6 |         6
+(6 rows)
+
+-- Non-absorbable context during absorption
+-- Pattern (A B)+ C: A,B in absorbable group, C is not.
+-- When END exits to C via nfa_state_create, isAbsorbable becomes false.
+WITH test_non_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C']),
+        (6, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_non_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN ((A B)+ C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         5
+  2 | {B}   |             |          
+  3 | {A}   |             |          
+  4 | {B}   |             |          
+  5 | {C}   |             |          
+  6 | {_}   |             |          
+(6 rows)
+
+-- Absorption flags early return (!hasAbsorbableState)
+-- Pattern (A B)+ C D with SKIP PAST LAST ROW
+-- After reaching C (non-absorbable), hasAbsorbableState becomes false.
+-- On next row (D), the early return fires.
+WITH test_absorption_early_return AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C']),
+        (6, ARRAY['D']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorption_early_return
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN ((A B)+ C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         6
+  2 | {B}   |             |          
+  3 | {A}   |             |          
+  4 | {B}   |             |          
+  5 | {C}   |             |          
+  6 | {D}   |             |          
+  7 | {_}   |             |          
+(7 rows)
+
+-- Coverage failure: older can't cover newer's states
+-- Pattern A+ | B+ with SKIP PAST LAST ROW.
+-- Row 1: only A → Ctx1 takes A branch only (B fails).
+-- Row 2: A and B → Ctx2 takes both branches.
+-- Absorption: Ctx1 has A but no B → can't cover Ctx2's B state → fails.
+WITH test_coverage_fail AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A', '_']),
+        (2, ARRAY['A', 'B']),
+        (3, ARRAY['A', '_']),
+        (4, ARRAY['A', '_']),
+        (5, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_coverage_fail
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+ | B+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A,_} |           1 |         4
+  2 | {A,B} |             |          
+  3 | {A,_} |             |          
+  4 | {A,_} |             |          
+  5 | {_,_} |             |          
+(5 rows)
+
+-- Absorb skips completed context (older->states==NULL)
+-- Pattern A+ | B+ with SKIP PAST LAST ROW.
+-- Row 1: A only → Ctx1 takes A branch. Row 2: B only → Ctx1 A fails (completed).
+-- Ctx2 takes B branch. Absorption: Ctx1 states==NULL → skip.
+WITH test_older_completed AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['B']),
+        (4, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_older_completed
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+ | B+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         1
+  2 | {B}   |           2 |         3
+  3 | {B}   |             |          
+  4 | {_}   |             |          
+(4 rows)
+
+-- Absorb skips non-absorbable context (!hasAbsorbableState)
+-- Pattern A+ | B C with SKIP PAST LAST ROW (only A+ branch absorbable).
+-- Row 1: B only → Ctx1 takes B branch (non-absorbable), advances to C.
+-- Row 2: C,A → Ctx1 C matches (hasAbsorbableState=false). Ctx2 takes A (absorbable).
+-- Absorption: Ctx1 !hasAbsorbableState → skip.
+WITH test_older_non_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['B', '_']),
+        (2, ARRAY['C', 'A']),
+        (3, ARRAY['_', 'A']),
+        (4, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_older_non_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+ | B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {B,_} |           1 |         2
+  2 | {C,A} |             |          
+  3 | {_,A} |           3 |         3
+  4 | {_,_} |             |          
+(4 rows)
+
+-- ============================================================
+-- FIXME Issues - Known Limitations
+-- ============================================================
+-- FIXME 1 - altPriority lexical order
+WITH test_alt_priority_repeated AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A','B']),  -- Both A and B match
+        (2, ARRAY['A','B']),
+        (3, ARRAY['A','B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_priority_repeated
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A | B)+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A,B} |           1 |         3
+  2 | {A,B} |           2 |         3
+  3 | {A,B} |           3 |         3
+(3 rows)
+
+-- FIXME 1 - Nested ALT lexical order
+WITH test_alt_priority_nested AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A','B']),
+        (2, ARRAY['C','D']),
+        (3, ARRAY['A','B']),
+        (4, ARRAY['C','D'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_priority_nested
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A | B) (C | D))+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A,B} |           1 |         4
+  2 | {C,D} |             |          
+  3 | {A,B} |           3 |         4
+  4 | {C,D} |             |          
+(4 rows)
+
+-- FIXME 2 - Cycle prevention at count > 0
+WITH test_cycle_nonzero AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])  -- Inner A* matches 0, cycles at count=3
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_cycle_nonzero
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A*)*)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         3
+  2 | {A}   |           2 |         3
+  3 | {A}   |           3 |         3
+  4 | {B}   |             |          
+(4 rows)
+
+-- FIXME 2 - Cycle with mixed nullables
+WITH test_cycle_mixed AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_cycle_mixed
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A* B*)*)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end 
+----+-------+-------------+-----------
+  1 | {A}   |           1 |         2
+  2 | {B}   |           2 |         2
+  3 | {A}   |           3 |         3
+  4 | {C}   |             |          
+(4 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fc61d90ebaf..2bf141ff7d5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -107,7 +107,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # ----------
 # Row Pattern Recognition tests
 # ----------
-test: rpr rpr_base rpr_explain
+test: rpr rpr_base rpr_explain rpr_nfa
 
 # ----------
 # Another group of parallel tests (JSON related)
diff --git a/src/test/regress/sql/rpr.sql b/src/test/regress/sql/rpr.sql
index 1071dacd687..7690c5611c0 100644
--- a/src/test/regress/sql/rpr.sql
+++ b/src/test/regress/sql/rpr.sql
@@ -323,6 +323,23 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
   A AS TRUE
 );
 
+-- nth_value beyond reduced frame (no IGNORE NULLS)
+-- Tests WinGetSlotInFrame/WinGetFuncArgInFrame out-of-frame with RPR
+SELECT company, tdate, price,
+ nth_value(price, 5) OVER w AS nth_5
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START UP+ DOWN+)
+ DEFINE
+  START AS TRUE,
+  UP AS price > PREV(price),
+  DOWN AS price < PREV(price)
+);
+
 -- backtracking with reclassification of rows
 -- using AFTER MATCH SKIP PAST LAST ROW
 SELECT company, tdate, price, first_value(tdate) OVER w, last_value(tdate) OVER w
@@ -499,6 +516,25 @@ count(*) OVER w
   DOWN AS price < PREV(price)
 );
 
+-- ReScan test: LATERAL join forces WindowAgg rescan with RPR
+-- Tests ExecReScanWindowAgg clearing prev_slot/next_slot
+SELECT g.x, sub.*
+FROM generate_series(1, 2) g(x),
+LATERAL (
+  SELECT id, price, count(*) OVER w AS c
+  FROM (VALUES (1, 100), (2, 200), (3, 150)) AS t(id, price)
+  WHERE id <= g.x + 1
+  WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (START UP+)
+    DEFINE
+      START AS TRUE,
+      UP AS price > PREV(price)
+  )
+) sub
+ORDER BY g.x, sub.id;
+
 -- PREV has multiple column reference
 CREATE TEMP TABLE rpr1 (id INTEGER, i SERIAL, j INTEGER);
 INSERT INTO rpr1(id, j) SELECT 1, g*2 FROM generate_series(1, 10) AS g;
@@ -624,6 +660,23 @@ WITH data AS (
    DEFINE A AS gid < 13
   );
 
+-- nth_value beyond reduced frame with IGNORE NULLS
+-- Tests ignorenulls_getfuncarginframe early out-of-frame check
+SELECT company, tdate, price,
+ nth_value(price, 5) IGNORE NULLS OVER w AS nth_5_in
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START UP+ DOWN+)
+ DEFINE
+  START AS TRUE,
+  UP AS price > PREV(price),
+  DOWN AS price < PREV(price)
+);
+
 -- View and pg_get_viewdef tests.
 CREATE TEMP VIEW v_window AS
 SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w,
@@ -1358,11 +1411,10 @@ WINDOW w AS (
 --   Row 3: C D E F (3-6) <- ends last!
 
 -- Test 1b: Longer pattern FAILS, shorter pattern should survive
--- Pattern: A+ B C D | A+ B (greedy at front for absorption)
--- Data: A B C X (D expected but X found)
--- A+ B C D fails at row 4 (X instead of D)
--- A+ B succeeds at row 2
--- Result should be match 1-2 (A+ B)
+-- Pattern: A+ B C D E | B+ C
+-- A+ B C D E fails (no E found in sequence)
+-- B+ C matches at rows 2-3
+-- Result: match 2-3 (B+ C)
 WITH test_overlap1b AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -1585,8 +1637,7 @@ WINDOW w AS (
     DEFINE A AS 'A' = ANY(flags)
 );
 -- Pattern A+ is absorbable (unbounded first element, only one unbounded)
--- Without absorption: 4 matches (1-4, 2-4, 3-4, 4-4)
--- With absorption: Row 1 match (1-4), rows 2-4 absorbed
+-- 4 matches: (1-4, 2-4, 3-4, 4-4)
 
 -- Test absorption 2: A+ B pattern - absorption with fixed suffix
 WITH test_absorb_suffix AS (
@@ -1611,7 +1662,7 @@ WINDOW w AS (
 );
 -- Pattern A+ B is absorbable (A+ unbounded first, B bounded suffix)
 -- All potential matches end at same row (row 4 with B)
--- With absorption: Row 1 match (1-4), rows 2-3 absorbed
+-- 3 matches: (1-4, 2-4, 3-4)
 
 -- Test absorption 3: Per-branch absorption with ALT (B+ C | B+ D)
 WITH test_absorb_alt AS (
@@ -1636,8 +1687,7 @@ WINDOW w AS (
         D AS 'D' = ANY(flags)
 );
 -- Both branches B+ C and B+ D are absorbable (B+ unbounded first)
--- B+ D branch matches: potential 1-4, 2-4, 3-4
--- Row 1 (1-4) absorbs Row 2 (2-4) and Row 3 (3-4) - same endpoint
+-- B+ D branch matches: 3 matches (1-4, 2-4, 3-4)
 
 -- Test absorption 4: Non-absorbable pattern (A B+ - unbounded not first)
 WITH test_no_absorb AS (
@@ -1687,8 +1737,7 @@ WINDOW w AS (
         B AS 'B' = ANY(flags)
 );
 -- Pattern optimized: (A B) (A B)+ -> (A B){2,}
--- Potential matches: 1-6 (3 reps), 3-6 (2 reps), 5-6 needs 2 reps (fail)
--- Row 1 (1-6) absorbs Row 3 (3-6) - same endpoint, top-level unbounded GROUP
+-- 2 matches: 1-6 (3 reps), 3-6 (2 reps)
 
 -- Test absorption 6: Multiple unbounded - first element unbounded enables absorption
 WITH test_multi_unbounded AS (
@@ -1711,7 +1760,7 @@ WINDOW w AS (
         A AS 'A' = ANY(flags),
         B AS 'B' = ANY(flags)
 );
--- Row 1: 1-4, Row 2: absorbed (same endpoint 4)
+-- 2 matches: 1-4, 2-4 (same endpoint 4)
 
 -- ============================================
 -- Jacob's RPR Patterns (from jacob branch)
@@ -1963,7 +2012,6 @@ WINDOW w AS (
 -- ============================================
 
 -- Test: (A | B)+ C - alternation inside quantified group followed by C
--- BUG: Currently returns NULL instead of matching 1-4
 WITH jacob_alt_quant AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -1987,7 +2035,6 @@ WINDOW w AS (
 -- Expected: 1-4 (A B A C)
 
 -- Test: ((A | B) C)+ - alternation inside group with outer quantifier
--- Currently returns two separate matches (1-2, 3-4) instead of single 1-4
 WITH jacob_alt_group AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -2015,8 +2062,7 @@ WINDOW w AS (
 -- RELUCTANT quantifiers (not yet supported)
 -- ============================================
 
--- Test: A+? B (reluctant) - CURRENTLY ACTS LIKE A+ (GREEDY)
--- Parser accepts the syntax but executor doesn't differentiate
+-- Test: A+? B (reluctant) - parser rejects with ERROR
 WITH jacob_reluctant AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -2036,10 +2082,9 @@ WINDOW w AS (
         A AS 'A' = ANY(flags),
         B AS 'B' = ANY(flags)
 );
--- Current: 1-4 (A A A B) - greedy behavior
--- Expected when RELUCTANT implemented: 3-4 (A B) - shortest A before B
+-- Expected: ERROR (reluctant quantifiers not yet supported)
 
--- Test: A{1,3}? B (reluctant bounded)
+-- Test: A{1,3}? B (reluctant bounded) - parser rejects with ERROR
 WITH jacob_reluctant_bounded AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -2059,8 +2104,7 @@ WINDOW w AS (
         A AS 'A' = ANY(flags),
         B AS 'B' = ANY(flags)
 );
--- Current: 1-4 (greedy, takes max A before B)
--- Expected when RELUCTANT implemented: 3-4 (takes min A before B)
+-- Expected: ERROR (reluctant quantifiers not yet supported)
 
 -- ============================================
 -- Nested quantifiers (pathological patterns)
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index 6f38cd1ae92..879f5fe48a2 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -188,7 +188,7 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS id > 0, A AS id < 10
 );
--- Expected: ERROR: row pattern definition variable name "A" appears more than once in DEFINE clause
+-- Expected: ERROR: row pattern definition variable name "a" appears more than once in DEFINE clause
 
 DROP TABLE rpr_dup;
 
@@ -355,7 +355,7 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS val > 0
 );
--- Expected: ERROR: FRAME must start at current row when row pattern recognition is used
+-- Expected: ERROR: FRAME option RANGE is not permitted when row pattern recognition is used
 
 -- GROUPS frame not starting at CURRENT ROW
 SELECT COUNT(*) OVER w
@@ -366,7 +366,7 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS val > 0
 );
--- Expected: ERROR: FRAME must start at current row when row pattern recognition is used
+-- Expected: ERROR: FRAME option GROUP is not permitted when row pattern recognition is used
 
 -- Starting with N PRECEDING
 SELECT COUNT(*) OVER w
@@ -401,7 +401,7 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS val > 0
 );
--- Expected: ERROR: frame end cannot be before frame start
+-- Expected: ERROR: frame starting from current row cannot have preceding rows
 
 -- End before start: CURRENT ROW AND UNBOUNDED PRECEDING
 SELECT COUNT(*) OVER w
@@ -412,7 +412,7 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS val > 0
 );
--- Expected: ERROR: frame end cannot be before frame start
+-- Expected: ERROR: frame end cannot be UNBOUNDED PRECEDING
 
 -- Single row frame: CURRENT ROW AND CURRENT ROW
 SELECT id, val, COUNT(*) OVER w as cnt
@@ -462,14 +462,7 @@ WINDOW w AS (
 )
 ORDER BY id;
 
--- RANGE: includes all rows with same ORDER BY value
--- Expected result: Includes peer rows (same val) in range calculation
--- id=1 (val=10): range [10,20] includes all val=10 and val=20 peers -> cnt=2 (only first in peer group gets match)
--- id=2,3 (val=10): already matched by id=1 -> cnt=0
--- id=4 (val=20): range [20,30] includes all val=20 and val=30 peers -> cnt=2
--- id=5 (val=20): already matched by id=4 -> cnt=0
--- id=6 (val=30): range [30,40] includes only val=30 -> cnt=1
--- Result: [2,0,0,2,0,1]
+-- RANGE frame with RPR (not permitted)
 SELECT id, val, COUNT(*) OVER w as cnt
 FROM rpr_frame
 WINDOW w AS (
@@ -480,15 +473,9 @@ WINDOW w AS (
     DEFINE A AS val >= 0, B AS val >= 0
 )
 ORDER BY id;
+-- Expected: ERROR: FRAME option RANGE is not permitted when row pattern recognition is used
 
--- GROUPS: treats rows with same value as one group (1 FOLLOWING = next group)
--- Expected result: 1 FOLLOWING means current group + 1 next group
--- id=1 (val=10): groups [val=10, val=20] -> cnt=2 (only first in group gets match)
--- id=2,3 (val=10): already matched by id=1 -> cnt=0
--- id=4 (val=20): groups [val=20, val=30] -> cnt=2
--- id=5 (val=20): already matched by id=4 -> cnt=0
--- id=6 (val=30): groups [val=30] (no next group) -> cnt=1
--- Result: [2,0,0,2,0,1]
+-- GROUPS frame with RPR (not permitted)
 SELECT id, val, COUNT(*) OVER w as cnt
 FROM rpr_frame
 WINDOW w AS (
@@ -499,6 +486,7 @@ WINDOW w AS (
     DEFINE A AS val >= 0, B AS val >= 0
 )
 ORDER BY id;
+-- Expected: ERROR: FRAME option GROUP is not permitted when row pattern recognition is used
 
 DROP TABLE rpr_frame;
 
@@ -513,7 +501,6 @@ INSERT INTO rpr_partition VALUES
     (4, 2, 15), (5, 2, 25), (6, 2, 35);
 
 -- PARTITION BY with ROWS frame
--- Expected: Pattern matching should reset for each partition
 SELECT id, grp, val, COUNT(*) OVER w as cnt
 FROM rpr_partition
 WINDOW w AS (
@@ -525,9 +512,9 @@ WINDOW w AS (
     DEFINE A AS val >= 10, B AS val > 15
 )
 ORDER BY id;
+-- Expected: Pattern matching should reset for each partition
 
 -- PARTITION BY with RANGE frame
--- Expected: Each partition processed independently
 SELECT id, grp, val, COUNT(*) OVER w as cnt
 FROM rpr_partition
 WINDOW w AS (
@@ -539,6 +526,7 @@ WINDOW w AS (
     DEFINE A AS val >= 10, B AS val >= 20
 )
 ORDER BY id;
+-- Expected: ERROR: FRAME option RANGE is not permitted when row pattern recognition is used
 
 DROP TABLE rpr_partition;
 
@@ -1911,7 +1899,7 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS val > 'string'
 );
--- Expected: ERROR: operator does not exist or type mismatch
+-- Expected: ERROR: invalid input syntax for type integer: "string"
 
 -- Aggregate function in DEFINE (if not allowed)
 SELECT COUNT(*) OVER w
@@ -2065,7 +2053,7 @@ SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN (A ((B) (C))) DEFINE A AS val <= 30, B AS val <= 60, C AS val > 60);
 
--- ALT flatten: (A | (B | C)) -> (a | b | c)
+-- ALT flatten: (A | (B | C))+ -> (a | b | c)+
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2162,14 +2150,14 @@ SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN ((A B)) DEFINE A AS val <= 50, B AS val > 50);
 
--- Combined optimization: A A (B B)+ B B C C C -> a{2} (b{2}){3,} c{3}
+-- Combined optimization: A A (B B)+ B B C C C -> a{2} (b{2}){2,} c{3}
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN (A A (B B)+ B B C C C)
              DEFINE A AS val <= 20, B AS val > 20 AND val <= 70, C AS val > 70);
 
--- Consecutive GROUP merge with unbounded: (A+){2} -> (a+){2}
+-- Consecutive GROUP merge with unbounded: (A+) (A+) -> a{2,}
 -- Tests mergeConsecutiveGroups with child->max == INF
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
@@ -2255,8 +2243,8 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN ((A B*)+ A B*)
              DEFINE A AS val <= 50, B AS val > 50);
 
--- Unwrap GROUP{1,1}: (A | B | C) -> A | B | C
--- Tests tryUnwrapGroup removing redundant GROUP
+-- Unwrap GROUP{1,1}: ((A | B | C)) -> (a | b | c)
+-- Tests tryUnwrapGroup removing redundant outer GROUP
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2611,7 +2599,6 @@ CREATE TABLE rpr_fallback (id INT, val INT);
 INSERT INTO rpr_fallback VALUES (1, 10), (2, 20);
 
 -- Test: min quantifier overflow causes optimization fallback (min == max case)
--- Expected: Fallback - pattern not merged due to min overflow (4000000000 > INT32_MAX)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -2620,9 +2607,9 @@ WINDOW w AS (
     PATTERN ((A{2000000000}){2})
     DEFINE A AS val > 0
 );
+-- Expected: Fallback - pattern not merged due to min overflow (4000000000 > INT32_MAX)
 
 -- Test: max-only quantifier overflow causes optimization fallback
--- Expected: Fallback - min OK (2*1=2), but max overflow (2*2000000000 > INT32_MAX)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -2631,9 +2618,9 @@ WINDOW w AS (
     PATTERN ((A{1,2000000000}){2})
     DEFINE A AS val > 0
 );
+-- Expected: Fallback - min OK (2*1=2), but max overflow (2*2000000000 > INT32_MAX)
 
--- Test: min quantifier overflow causes optimization fallback (min != max case)
--- Expected: Fallback - pattern not merged due to min overflow
+-- Test: max quantifier exceeds valid range (2147483647 = INT_MAX, limit is 2147483646)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -2642,9 +2629,9 @@ WINDOW w AS (
     PATTERN ((A{2000000000,2147483647}){2})
     DEFINE A AS val > 0
 );
+-- Expected: ERROR at parse time before optimization
 
 -- Test: nested unbounded with large min causes overflow fallback
--- Expected: Fallback - min overflow (2000000000 * 2000000000 > INT32_MAX)
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -2653,9 +2640,9 @@ WINDOW w AS (
     PATTERN ((A{2000000000,}){2000000000,})
     DEFINE A AS val > 0
 );
+-- Expected: Fallback - min overflow (2000000000 * 2000000000 > INT32_MAX)
 
 -- Test: prefix mismatch causes optimization fallback
--- Expected: Fallback - prefix elements don't match GROUP content
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_fallback
 WINDOW w AS (
@@ -2664,6 +2651,7 @@ WINDOW w AS (
     PATTERN (A B (C D)+)
     DEFINE A AS val > 0, B AS val > 5, C AS val > 10, D AS val > 15
 );
+-- Expected: Fallback - prefix elements don't match GROUP content
 
 DROP TABLE rpr_fallback;
 
@@ -2752,7 +2740,6 @@ FROM rpr_planner
 ORDER BY id;
 
 -- Window with Aggregate Functions
-
 SELECT category,
        COUNT(*) OVER w as window_cnt,
        COUNT(*) as agg_cnt
@@ -2766,6 +2753,7 @@ WINDOW w AS (
 )
 GROUP BY category
 ORDER BY category;
+-- Expected: ERROR (GROUP BY with window RPR not supported)
 
 -- ============================================================
 -- Subquery and CTE Tests
@@ -3320,7 +3308,6 @@ CREATE TABLE rpr_errors (id INT, val INT);
 INSERT INTO rpr_errors VALUES (1, 10), (2, 20);
 
 -- Test: PATTERN variable without DEFINE (A), DEFINE variable not in PATTERN (B)
--- Expected: Success - A is implicitly TRUE, B is filtered out
 SELECT id, val, COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -3329,9 +3316,9 @@ WINDOW w AS (
     DEFINE
       B AS TRUE
 );
+-- Expected: Success - A is implicitly TRUE, B is filtered out
 
 -- Test: 3 variables in PATTERN, 253 in DEFINE (DEFINE filtering test)
--- Expected: Success - unused DEFINE variables are filtered out
 SELECT COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -3365,9 +3352,9 @@ WINDOW w AS (
     V241 AS val > 0, V242 AS val > 0, V243 AS val > 0, V244 AS val > 0, V245 AS val > 0, V246 AS val > 0, V247 AS val > 0, V248 AS val > 0, V249 AS val > 0, V250 AS val > 0,
     V251 AS val > 0, V252 AS val > 0, V253 AS val > 0
 );
+-- Expected: Success - unused DEFINE variables are filtered out
 
 -- Test: 251 variables in PATTERN, 252 in DEFINE (boundary - should succeed)
--- Expected: Success - unused DEFINE variables are filtered out
 SELECT COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -3401,10 +3388,9 @@ WINDOW w AS (
     V241 AS val > 0, V242 AS val > 0, V243 AS val > 0, V244 AS val > 0, V245 AS val > 0, V246 AS val > 0, V247 AS val > 0, V248 AS val > 0, V249 AS val > 0, V250 AS val > 0,
     V251 AS val > 0, V252 AS val > 0
 );
-
+-- Expected: Success - unused DEFINE variables are filtered out
 
 -- Test: 252 variables in PATTERN, 251 in DEFINE (exceeds limit with implicit TRUE)
--- Expected: ERROR - too many pattern variables (Maximum is 251)
 SELECT COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
     ORDER BY id
@@ -3438,9 +3424,9 @@ WINDOW w AS (
     V241 AS val > 0, V242 AS val > 0, V243 AS val > 0, V244 AS val > 0, V245 AS val > 0, V246 AS val > 0, V247 AS val > 0, V248 AS val > 0, V249 AS val > 0, V250 AS val > 0,
     V251 AS val > 0
 );
+-- Expected: ERROR - too many pattern variables (Maximum is 251)
 
 -- Test: Pattern nesting at maximum depth (depth 253)
--- Expected: Should succeed
 -- Note: 253 nested GROUP{3,7} quantifiers produce depth 253 after optimization
 SELECT id, val, COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
@@ -3449,9 +3435,9 @@ WINDOW w AS (
     PATTERN ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((A{3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7})
     DEFINE A AS val > 0
 );
+-- Expected: Should succeed
 
 -- Test: Pattern nesting depth exceeds maximum (depth 254)
--- Expected: ERROR - pattern nesting too deep
 -- Note: 254 nested GROUP{3,7} quantifiers produce depth 254 after optimization
 SELECT id, val, COUNT(*) OVER w FROM rpr_errors
 WINDOW w AS (
@@ -3460,6 +3446,7 @@ WINDOW w AS (
     PATTERN (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((A{3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7}){3,7})
     DEFINE A AS val > 0
 );
+-- Expected: ERROR - pattern nesting too deep
 
 DROP TABLE rpr_errors;
 
@@ -3567,6 +3554,25 @@ WINDOW w AS (
     DEFINE A AS val <= 30, B AS val BETWEEN 31 AND 60, C AS val > 80
 );
 
+-- Test: (A+ | (A | B)+)* - nested alternation inside quantified group
+-- Previously caused infinite recursion in nfa_advance_alt when the inner
+-- BEGIN(+)'s skip jump was followed as an ALT branch pointer.
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM (VALUES
+    (1, ARRAY['A', 'B']),
+    (2, ARRAY['B']),
+    (3, ARRAY['C'])
+) AS t(id, flags)
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN ((A+ | (A | B)+)*)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
 -- ============================================================
 -- Pathological Patterns
 -- ============================================================
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index 80088ab18e2..f8c8f62e594 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -189,7 +189,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 20) AS s(v)
 WINDOW w AS (
@@ -212,7 +212,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 30) AS s(v)
 WINDOW w AS (
@@ -365,7 +365,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -389,7 +389,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -412,7 +412,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -1680,7 +1680,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 100) AS s(v)
 WINDOW w AS (
@@ -1701,7 +1701,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -1722,7 +1722,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 100) AS s(v)
 WINDOW w AS (
@@ -1743,7 +1743,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -1764,7 +1764,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -1786,7 +1786,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 20) AS s(v)
 WINDOW w AS (
@@ -1808,7 +1808,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 20) AS s(v)
 WINDOW w AS (
@@ -1902,7 +1902,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
 WINDOW w AS (
@@ -1923,7 +1923,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -1944,7 +1944,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
@@ -1965,7 +1965,7 @@ WINDOW w AS (
 );
 SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
 SELECT rpr_explain_filter('
-EXPLAIN (COSTS OFF)
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
 SELECT count(*) OVER w
 FROM generate_series(1, 60) AS s(v)
 WINDOW w AS (
diff --git a/src/test/regress/sql/rpr_nfa.sql b/src/test/regress/sql/rpr_nfa.sql
new file mode 100644
index 00000000000..9573d1dab3b
--- /dev/null
+++ b/src/test/regress/sql/rpr_nfa.sql
@@ -0,0 +1,1865 @@
+-- ============================================================
+-- RPR NFA Tests
+-- Tests for Row Pattern Recognition NFA Runtime Execution
+-- ============================================================
+--
+-- This test suite validates the NFA (Non-deterministic Finite
+-- Automaton) runtime execution engine in nodeWindowAgg.c,
+-- focusing on update_reduced_frame and related functions.
+--
+-- Test Strategy:
+--   Diagonal pattern style using ARRAY flags to explicitly
+--   control which pattern variables match at each row.
+--
+-- Test Coverage:
+--   Basic NFA Flow (match->absorb->advance)
+--   Absorption Optimization
+--   Context Lifecycle Management
+--   Advance Phase (Epsilon Transitions)
+--   Match Phase (Variable Matching)
+--   Frame Boundary Handling
+--   State Management (Deduplication)
+--   Statistics and Diagnostics
+--   Quantifier Runtime Behavior
+--   Pathological Pattern Protection
+--   Alternation Runtime Behavior
+--   Deep Nested Groups
+--   SKIP Options (Runtime)
+--   INITIAL Mode (Runtime)
+--   Frame Boundary Variations
+--   Special Partition Cases
+--   DEFINE Special Cases
+--   Absorption Dynamic Flags
+--   FIXME Issues (Known Limitations)
+--
+-- Responsibility:
+--   - NFA runtime execution paths
+--   - Context/State lifecycle management
+--   - Runtime boundary conditions and protections
+--
+-- NOT tested here (covered in other files):
+--   - Pattern parsing/optimization (rpr_base.sql)
+--   - EXPLAIN output (rpr_explain.sql)
+--   - PREV/NEXT semantics (rpr.sql)
+-- ============================================================
+
+-- ============================================================
+-- Basic NFA Flow
+-- ============================================================
+
+-- Simple sequential pattern
+WITH test_sequential AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['D']),
+        (5, ARRAY['_'])  -- No match
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_sequential
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- Quantified pattern (A+ B+ C+)
+WITH test_quantified AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['B']),
+        (6, ARRAY['C']),
+        (7, ARRAY['C']),
+        (8, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_quantified
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B+ C+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- Optional pattern (A B? C)
+WITH test_optional AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['C']),  -- B skipped
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C']),  -- B matched
+        (6, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_optional
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B? C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- Alternation pattern (A (B|C) D)
+WITH test_alternation AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),  -- First branch
+        (3, ARRAY['D']),
+        (4, ARRAY['A']),
+        (5, ARRAY['C']),  -- Second branch
+        (6, ARRAY['D']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alternation
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A (B | C) D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- ============================================================
+-- Absorption Optimization
+-- ============================================================
+
+-- Absorbable pattern (A+)
+WITH test_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- Mixed absorbable/non-absorbable ((A+) | B)
+WITH test_mixed_absorption AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_mixed_absorption
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A+) | B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- State coverage (same elemIdx, different count)
+WITH test_state_coverage AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_state_coverage
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{2,} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- ============================================================
+-- Context Lifecycle
+-- ============================================================
+
+-- Multiple overlapping contexts (SKIP TO NEXT ROW)
+WITH test_overlapping_contexts AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_overlapping_contexts
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Failed context cleanup (early failure)
+WITH test_context_cleanup AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Pruned at first row
+        (2, ARRAY['A']),
+        (3, ARRAY['_']),  -- Mismatched after row 2
+        (4, ARRAY['A']),
+        (5, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_context_cleanup
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Partition end (incomplete contexts)
+WITH test_partition_end AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A'])
+        -- Pattern requires B, but partition ends
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_partition_end
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Completed context encountered during processing
+-- Pattern (A | B C D): Ctx1 takes long B->C->D path, while Ctx2 takes
+-- short A path and completes first. Next row sees Ctx2
+-- with states=NULL and skips it.
+WITH test_completed_ctx AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['B', '_']),
+        (2, ARRAY['C', 'A']),
+        (3, ARRAY['D', '_']),
+        (4, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_completed_ctx
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A | B C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- ============================================================
+-- Advance Phase (Epsilon Transitions)
+-- ============================================================
+
+-- Nested groups ((A B)+)
+WITH test_nested_groups AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_groups
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A B)+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Multiple alternation branches (A (B|C|D) E)
+WITH test_multi_alt AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['E']),
+        (4, ARRAY['A']),
+        (5, ARRAY['C']),
+        (6, ARRAY['E']),
+        (7, ARRAY['A']),
+        (8, ARRAY['D']),
+        (9, ARRAY['E'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_multi_alt
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A (B | C | D) E)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags),
+        E AS 'E' = ANY(flags)
+);
+
+-- Optional VAR at start (A? B C)
+WITH test_optional_var AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['B']),  -- A skipped
+        (2, ARRAY['C']),
+        (3, ARRAY['A']),  -- A matched
+        (4, ARRAY['B']),
+        (5, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_optional_var
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A? B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- Nested alternation ((A|B) (C|D))
+WITH test_nested_alt AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['C']),  -- A C
+        (3, ARRAY['A']),
+        (4, ARRAY['D']),  -- A D
+        (5, ARRAY['B']),
+        (6, ARRAY['C']),  -- B C
+        (7, ARRAY['B']),
+        (8, ARRAY['D'])   -- B D
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_alt
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A | B) (C | D))
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- ============================================================
+-- Match Phase
+-- ============================================================
+
+-- Simple VAR with END next (A B C all min=max=1)
+WITH test_simple_var AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_simple_var
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- VAR max exceeded (A{2,3})
+WITH test_max_exceeded AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),  -- Max = 3
+        (4, ARRAY['A']),  -- Exceeds max, state removed
+        (5, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_max_exceeded
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{2,3} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Non-matching VAR (DEFINE false)
+WITH test_non_matching AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['_']),  -- B not matched (DEFINE false)
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),  -- B matched
+        (5, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_non_matching
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- ============================================================
+-- Frame Boundary Handling
+-- ============================================================
+
+-- Limited frame (ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING)
+WITH test_limited_frame AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),  -- Within 3 FOLLOWING
+        (5, ARRAY['B']),  -- Beyond 3 FOLLOWING from row 1
+        (6, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_limited_frame
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Unbounded frame (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+WITH test_unbounded_frame AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B'])  -- Far from start, but unbounded
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_unbounded_frame
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Match exceeds frame boundary
+WITH test_frame_exceeded AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A'])
+        -- Frame ends at row 3 (2 FOLLOWING), B never appears
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_frame_exceeded
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Frame boundary forced mismatch
+-- Limited frame with enough rows so that a context's frame boundary
+-- is exceeded while still processing.
+WITH test_frame_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_frame_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- ============================================================
+-- State Management
+-- ============================================================
+
+-- Duplicate state creation
+WITH test_duplicate_states AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A', 'B']),  -- Both A and B match (creates duplicate states via different paths)
+        (2, ARRAY['C', '_']),
+        (3, ARRAY['D', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_duplicate_states
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A | B) C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- Large pattern (stress free list)
+WITH test_large_pattern AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['D']),
+        (5, ARRAY['E']),
+        (6, ARRAY['F']),
+        (7, ARRAY['G']),
+        (8, ARRAY['H']),
+        (9, ARRAY['I']),
+        (10, ARRAY['J'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_large_pattern
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C D E F G H I J)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags),
+        E AS 'E' = ANY(flags),
+        F AS 'F' = ANY(flags),
+        G AS 'G' = ANY(flags),
+        H AS 'H' = ANY(flags),
+        I AS 'I' = ANY(flags),
+        J AS 'J' = ANY(flags)
+);
+
+-- Reduced frame map reallocation (> 1024 rows)
+WITH test_map_realloc AS (
+    SELECT id, CASE WHEN id % 2 = 1 THEN ARRAY['A'] ELSE ARRAY['B'] END AS flags
+    FROM generate_series(1, 1100) AS id
+)
+SELECT count(*), min(match_start), max(match_end)
+FROM (
+    SELECT id, flags,
+           first_value(id) OVER w AS match_start,
+           last_value(id) OVER w AS match_end
+    FROM test_map_realloc
+    WINDOW w AS (
+        ORDER BY id
+        ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+        AFTER MATCH SKIP TO NEXT ROW
+        PATTERN (A B)
+        DEFINE
+            A AS 'A' = ANY(flags),
+            B AS 'B' = ANY(flags)
+    )
+) sub;
+
+-- ============================================================
+-- Statistics and Diagnostics
+-- ============================================================
+
+-- Matched contexts
+WITH test_matched AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_matched
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Pruned contexts (failed at first row)
+WITH test_pruned AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Pruned
+        (2, ARRAY['_']),  -- Pruned
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_pruned
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Mismatched contexts (failed after multiple rows)
+WITH test_mismatched AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['_']),  -- Mismatched after 2 rows
+        (4, ARRAY['A']),
+        (5, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_mismatched
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Absorbed contexts
+WITH test_absorbed AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorbed
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- Skipped contexts (SKIP TO NEXT ROW)
+WITH test_skipped AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])  -- Completes match starting at row 1
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skipped
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- ============================================================
+-- Quantifier Runtime Behavior
+-- ============================================================
+
+-- Large count handling (A{100})
+WITH test_large_count AS (
+    SELECT i AS id, ARRAY['A'] AS flags
+    FROM generate_series(1, 105) i
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_large_count
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{100})
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- Unlimited quantifier (A{10,})
+WITH test_unlimited AS (
+    SELECT i AS id, ARRAY['A'] AS flags
+    FROM generate_series(1, 15) i
+    UNION ALL
+    SELECT 16, ARRAY['B']
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_unlimited
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{10,} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Min boundary (A{3,5})
+WITH test_min_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),  -- Min=3 reached, exit path available
+        (4, ARRAY['B']),  -- Match ends at min
+        (5, ARRAY['A']),
+        (6, ARRAY['A']),
+        (7, ARRAY['A']),
+        (8, ARRAY['A']),
+        (9, ARRAY['A']),  -- Count=5, max reached
+        (10, ARRAY['B'])  -- Match ends at max
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_min_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{3,5} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Max boundary exceeded (A{3,5})
+WITH test_max_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['A']),  -- Count=6 > max=5, row 1 context removed
+        (7, ARRAY['B'])   -- Row 1 context: no match (exceeded max)
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_max_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A{3,5} B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- ============================================================
+-- Pathological Pattern Runtime Protection
+-- ============================================================
+
+-- Complex nested nullable ((A* B*)*) - Runtime protection
+WITH test_complex_nested AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['B']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_complex_nested
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A* B*)*)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Nested nullable with quantifier ((A{0,3})*)
+WITH test_nested_quantifier AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_quantifier
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A{0,3})*)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- ============================================================
+-- Alternation Runtime Behavior
+-- ============================================================
+
+-- Multi-branch alternation (A (B|C|D|E) F)
+WITH test_multi_branch AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['F']),
+        (4, ARRAY['A']),
+        (5, ARRAY['C']),
+        (6, ARRAY['F']),
+        (7, ARRAY['A']),
+        (8, ARRAY['D']),
+        (9, ARRAY['F']),
+        (10, ARRAY['A']),
+        (11, ARRAY['E']),
+        (12, ARRAY['F'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_multi_branch
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A (B | C | D | E) F)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags),
+        E AS 'E' = ANY(flags),
+        F AS 'F' = ANY(flags)
+);
+
+-- Alternation with quantifiers (A+ | B+ | C+)
+WITH test_alt_quantifiers AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['B']),
+        (6, ARRAY['C']),
+        (7, ARRAY['C']),
+        (8, ARRAY['C']),
+        (9, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_quantifiers
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ | B+ | C+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- altPriority replacement (A B C | D)
+-- D branch (higher altPriority) matches first at row 1,
+-- then A B C branch (lower altPriority) replaces it at row 3.
+WITH test_alt_replace AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A', 'D']),
+        (2, ARRAY['B', '_']),
+        (3, ARRAY['C', '_']),
+        (4, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_replace
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C | D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- ============================================================
+-- Deep Nested Groups
+-- ============================================================
+
+-- Three-level nesting ((((A B)+)+)+)
+WITH test_deep_nesting AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_deep_nesting
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((((A B)+)+)+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Multiple groups in nesting (((A B) (C D))+)
+WITH test_nested_sequential AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['C']),
+        (4, ARRAY['D']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['C']),
+        (8, ARRAY['D']),
+        (9, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_nested_sequential
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A B) (C D))+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- Nested END→END max reached
+-- Inner group (A B){2} reaches max=2 → exits to outer END
+WITH test_end_nested_max AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['A']),
+        (8, ARRAY['B']),
+        (9, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_end_nested_max
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A B){2})+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Nested END→END between min/max
+-- Inner group (A B){1,3} exits between min/max → outer END count++
+WITH test_end_nested_mid AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B']),
+        (7, ARRAY['A']),
+        (8, ARRAY['B']),
+        (9, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_end_nested_mid
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A B){1,3})+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- ============================================================
+-- SKIP Options (Runtime)
+-- ============================================================
+
+-- SKIP PAST LAST ROW (non-overlapping matches)
+WITH test_skip_past AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_past
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- SKIP TO NEXT ROW (overlapping matches)
+WITH test_skip_next AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_next
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- SKIP difference verification
+WITH test_skip_diff AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT 'SKIP PAST' AS mode, id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_diff
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+)
+UNION ALL
+SELECT 'SKIP NEXT' AS mode, id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_skip_diff
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+)
+ORDER BY mode, id;
+
+-- ============================================================
+-- INITIAL Mode (Runtime)
+-- ============================================================
+
+-- INITIAL mode (not yet supported - produces syntax error)
+WITH test_initial_mode AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Unmatched
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['_']),  -- Unmatched
+        (5, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_initial_mode
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    INITIAL
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- Default mode (include all rows)
+WITH test_default_mode AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),  -- Unmatched, but included
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['_']),  -- Unmatched, but included
+        (5, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_default_mode
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- Mode difference verification (INITIAL not yet supported - produces syntax error)
+WITH test_mode_diff AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['_']),
+        (2, ARRAY['A']),
+        (3, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT 'INITIAL' AS mode, COUNT(*) AS row_count
+FROM (
+    SELECT id FROM test_mode_diff
+    WINDOW w AS (
+        ORDER BY id
+        ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+        INITIAL
+        AFTER MATCH SKIP TO NEXT ROW
+        PATTERN (A)
+        DEFINE A AS 'A' = ANY(flags)
+    )
+) sub
+UNION ALL
+SELECT 'DEFAULT' AS mode, COUNT(*) AS row_count
+FROM (
+    SELECT id FROM test_mode_diff
+    WINDOW w AS (
+        ORDER BY id
+        ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+        AFTER MATCH SKIP TO NEXT ROW
+        PATTERN (A)
+        DEFINE A AS 'A' = ANY(flags)
+    )
+) sub
+ORDER BY mode;
+
+-- ============================================================
+-- Frame Boundary Variations
+-- ============================================================
+
+-- Very limited frame (1 FOLLOWING)
+WITH test_one_following AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),  -- Within 1 FOLLOWING
+        (3, ARRAY['A']),  -- Beyond 1 FOLLOWING from row 1
+        (4, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_one_following
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Medium frame (10 FOLLOWING)
+WITH test_ten_following AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A']),
+        (6, ARRAY['A']),
+        (7, ARRAY['A']),
+        (8, ARRAY['A']),
+        (9, ARRAY['A']),
+        (10, ARRAY['A']),
+        (11, ARRAY['B']),  -- Within 10 FOLLOWING from row 1
+        (12, ARRAY['A']),
+        (13, ARRAY['B'])   -- Beyond 10 FOLLOWING from row 1
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_ten_following
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 10 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Exact boundary match
+WITH test_exact_boundary AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['B'])   -- Exactly at 4 FOLLOWING (frame end)
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_exact_boundary
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+ B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- ============================================================
+-- Special Partition Cases
+-- ============================================================
+
+-- Empty partition (0 rows)
+WITH test_empty_partition AS (
+    SELECT * FROM (VALUES
+        (1, 1, ARRAY['A']),
+        (2, 2, ARRAY['_'])  -- Different partition
+    ) AS t(id, part, flags)
+)
+SELECT id, part, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_empty_partition
+WHERE part = 99  -- No rows match
+WINDOW w AS (
+    PARTITION BY part
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- Single row partition
+WITH test_single_row AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_single_row
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- All rows fail matching (all DEFINE false)
+WITH test_all_fail AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_all_fail
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A+)
+    DEFINE
+        A AS false  -- All rows fail
+);
+
+-- Partition end with absorbable pattern
+-- SKIP PAST LAST ROW + unbounded frame + all rows match A
+-- Triggers absorb in !rowExists path at partition boundary.
+WITH test_absorb_partition_end AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['A']),
+        (5, ARRAY['A'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorb_partition_end
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- ============================================================
+-- DEFINE Special Cases
+-- ============================================================
+
+-- Undefined variable in DEFINE
+WITH test_undefined_var AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['X']),  -- B not defined, defaults to TRUE
+        (3, ARRAY['C']),
+        (4, ARRAY['A']),
+        (5, ARRAY['_']),  -- B defaults to TRUE, but no flags
+        (6, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_undefined_var
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (A B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        -- B is undefined, defaults to TRUE
+        C AS 'C' = ANY(flags)
+);
+
+-- ============================================================
+-- Absorption Dynamic Flags
+-- ============================================================
+
+-- Partial absorbable pattern ((A+) B)
+WITH test_partial_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_partial_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A+) B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Dynamic flag update ((A+) | B)
+WITH test_dynamic_flags AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['A']),
+        (6, ARRAY['B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_dynamic_flags
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A+) | B)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Non-absorbable context during absorption
+-- Pattern (A B)+ C: A,B in absorbable group, C is not.
+-- When END exits to C via nfa_state_create, isAbsorbable becomes false.
+WITH test_non_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C']),
+        (6, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_non_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN ((A B)+ C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- Absorption flags early return (!hasAbsorbableState)
+-- Pattern (A B)+ C D with SKIP PAST LAST ROW
+-- After reaching C (non-absorbable), hasAbsorbableState becomes false.
+-- On next row (D), the early return fires.
+WITH test_absorption_early_return AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B']),
+        (5, ARRAY['C']),
+        (6, ARRAY['D']),
+        (7, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_absorption_early_return
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN ((A B)+ C D)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- Coverage failure: older can't cover newer's states
+-- Pattern A+ | B+ with SKIP PAST LAST ROW.
+-- Row 1: only A → Ctx1 takes A branch only (B fails).
+-- Row 2: A and B → Ctx2 takes both branches.
+-- Absorption: Ctx1 has A but no B → can't cover Ctx2's B state → fails.
+WITH test_coverage_fail AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A', '_']),
+        (2, ARRAY['A', 'B']),
+        (3, ARRAY['A', '_']),
+        (4, ARRAY['A', '_']),
+        (5, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_coverage_fail
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+ | B+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Absorb skips completed context (older->states==NULL)
+-- Pattern A+ | B+ with SKIP PAST LAST ROW.
+-- Row 1: A only → Ctx1 takes A branch. Row 2: B only → Ctx1 A fails (completed).
+-- Ctx2 takes B branch. Absorption: Ctx1 states==NULL → skip.
+WITH test_older_completed AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['B']),
+        (4, ARRAY['_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_older_completed
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+ | B+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- Absorb skips non-absorbable context (!hasAbsorbableState)
+-- Pattern A+ | B C with SKIP PAST LAST ROW (only A+ branch absorbable).
+-- Row 1: B only → Ctx1 takes B branch (non-absorbable), advances to C.
+-- Row 2: C,A → Ctx1 C matches (hasAbsorbableState=false). Ctx2 takes A (absorbable).
+-- Absorption: Ctx1 !hasAbsorbableState → skip.
+WITH test_older_non_absorbable AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['B', '_']),
+        (2, ARRAY['C', 'A']),
+        (3, ARRAY['_', 'A']),
+        (4, ARRAY['_', '_'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_older_non_absorbable
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+ | B C)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags)
+);
+
+-- ============================================================
+-- FIXME Issues - Known Limitations
+-- ============================================================
+
+-- FIXME 1 - altPriority lexical order
+WITH test_alt_priority_repeated AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A','B']),  -- Both A and B match
+        (2, ARRAY['A','B']),
+        (3, ARRAY['A','B'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_priority_repeated
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A | B)+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
+
+-- FIXME 1 - Nested ALT lexical order
+WITH test_alt_priority_nested AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A','B']),
+        (2, ARRAY['C','D']),
+        (3, ARRAY['A','B']),
+        (4, ARRAY['C','D'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_alt_priority_nested
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN (((A | B) (C | D))+)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags),
+        C AS 'C' = ANY(flags),
+        D AS 'D' = ANY(flags)
+);
+
+-- FIXME 2 - Cycle prevention at count > 0
+WITH test_cycle_nonzero AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['A']),
+        (3, ARRAY['A']),
+        (4, ARRAY['B'])  -- Inner A* matches 0, cycles at count=3
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_cycle_nonzero
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A*)*)
+    DEFINE
+        A AS 'A' = ANY(flags)
+);
+
+-- FIXME 2 - Cycle with mixed nullables
+WITH test_cycle_mixed AS (
+    SELECT * FROM (VALUES
+        (1, ARRAY['A']),
+        (2, ARRAY['B']),
+        (3, ARRAY['A']),
+        (4, ARRAY['C'])
+    ) AS t(id, flags)
+)
+SELECT id, flags,
+       first_value(id) OVER w AS match_start,
+       last_value(id) OVER w AS match_end
+FROM test_cycle_mixed
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP TO NEXT ROW
+    PATTERN ((A* B*)*)
+    DEFINE
+        A AS 'A' = ANY(flags),
+        B AS 'B' = ANY(flags)
+);
-- 
2.50.1 (Apple Git-155)