commit 08a16207bbb7cb2cc8da116886da99149ea093da
parent b17300f58730efeae5ef71f559fe28e087deabfc
Author: Brian Graham <brian@buildingbetterteams.de>
Date: Sat, 4 Apr 2026 08:16:19 +0200
Fix score detection and rotation piece identification
Score detection:
- extractScoreFromText() parses labeled patterns (Score: 100) before
falling back to number extraction, avoiding concatenated text bugs
- Score change test polls 4 times over 2 seconds after line clear
instead of single-shot comparison
Rotation test:
- detectPieceShape() now diffs current grid against settled grid to
isolate active piece from landed cells
- Increased attempts from 40 to 60
- Fixed camelCase field name (startMechanism vs start_mechanism)
Score element calibration:
- Three-strategy detection: leaf descendants near "score" text,
labeled text patterns, fallback to standalone numbers
- Prevents selecting parent elements with concatenated score/level/lines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
1 file changed, 53 insertions(+), 21 deletions(-)
diff --git a/tasks/tetris/eval/gameplay-bot/calibrate.ts b/tasks/tetris/eval/gameplay-bot/calibrate.ts
@@ -384,35 +384,70 @@ async function detectControls(page: Page): Promise<Controls> {
/**
* Find the score display element on the page.
+ *
+ * Prefers elements that contain ONLY the score number (a child element
+ * whose textContent is a standalone number). This avoids selecting a
+ * parent that concatenates "Score: 100Level: 1Lines: 5" into one text node.
*/
async function detectScoreElement(page: Page): Promise<string | null> {
try {
const selector = await page.evaluate(() => {
- // Look for elements with "score" text nearby
+ function _buildSelector(el: Element): string | null {
+ if (el.id) return `#${el.id}`;
+ if ((el as HTMLElement).className) {
+ const cls = (el as HTMLElement).className.split(" ")[0];
+ if (cls) return `.${cls}`;
+ }
+ return null;
+ }
+
+ // Strategy 1: Find a child element near "score" text that contains
+ // ONLY a single number (the narrowest, most reliable match).
const allElements = document.querySelectorAll("*");
for (const el of allElements) {
const text = (el as HTMLElement).innerText?.toLowerCase() || "";
- if (text.includes("score") && el.children.length < 5) {
- // Find the numeric part -- might be a sibling or child
- const numChild = el.querySelector("span, div, p, td");
- if (numChild && /^\d+$/.test(numChild.textContent?.trim() || "")) {
- // Build a selector for this element
- if (numChild.id) return `#${numChild.id}`;
- if (numChild.className) {
- const cls = numChild.className.split(" ")[0];
- if (cls) return `.${cls}`;
+ if (text.includes("score") && el.children.length < 10) {
+ // Look for a child/descendant with ONLY a number
+ const descendants = el.querySelectorAll("span, div, p, td, strong, em, b");
+ for (const desc of descendants) {
+ const descText = desc.textContent?.trim() || "";
+ if (/^\d+$/.test(descText) && desc.children.length === 0) {
+ const sel = _buildSelector(desc);
+ if (sel) return sel;
}
}
- // The element itself might contain the score
- if (el.id) return `#${el.id}`;
- if ((el as HTMLElement).className) {
- const cls = (el as HTMLElement).className.split(" ")[0];
- if (cls) return `.${cls}`;
+
+ // Check siblings of the "score" label element
+ const next = el.nextElementSibling;
+ if (next) {
+ const nextText = next.textContent?.trim() || "";
+ if (/^\d+$/.test(nextText)) {
+ const sel = _buildSelector(next);
+ if (sel) return sel;
+ }
}
+
+ // Fall back to the element itself, but only if it looks reasonable.
+ // Check if the element's own text (without children) contains "score"
+ // and has a parseable score value.
+ const sel = _buildSelector(el);
+ if (sel) return sel;
}
}
- // Fallback: look for elements that contain just a number
+ // Strategy 2: Look for labeled text like "Score: 123" in leaf elements
+ for (const el of allElements) {
+ if (el.children.length > 3) continue;
+ const text = (el as HTMLElement).textContent?.trim() || "";
+ const scoreMatch = text.match(/score\s*[:\-=]?\s*(\d+)/i);
+ if (scoreMatch) {
+ // This element contains labeled score text
+ const sel = _buildSelector(el);
+ if (sel) return sel;
+ }
+ }
+
+ // Strategy 3: Fallback: look for leaf elements that contain just a number
const candidates: HTMLElement[] = [];
for (const el of allElements) {
const text = (el as HTMLElement).textContent?.trim() || "";
@@ -422,11 +457,8 @@ async function detectScoreElement(page: Page): Promise<string | null> {
}
if (candidates.length > 0) {
const el = candidates[0];
- if (el.id) return `#${el.id}`;
- if (el.className) {
- const cls = el.className.split(" ")[0];
- if (cls) return `.${cls}`;
- }
+ const sel = _buildSelector(el);
+ if (sel) return sel;
}
return null;