diff --git a/aiAssets/arr_mip_grhpl_easylevelprogression_01_real_nougc_noseason_en_full_na_al.html b/aiAssets/arr_mip_grhpl_easylevelprogression_01_real_nougc_noseason_en_full_na_al.html
deleted file mode 100644
index 52d623d..0000000
--- a/aiAssets/arr_mip_grhpl_easylevelprogression_01_real_nougc_noseason_en_full_na_al.html
+++ /dev/null
@@ -1,6381 +0,0 @@
-
-
-
-
-
- Arrow Puzzle
-
-
-
-
-
-
-
diff --git a/aiAssets/wrd_mip_grhpl_simplegameplaysounddark_01_real_nougc_noseason_en_full_na_al.html b/aiAssets/wrd_mip_grhpl_simplegameplaysounddark_01_real_nougc_noseason_en_full_na_al.html
deleted file mode 100644
index 6022461..0000000
--- a/aiAssets/wrd_mip_grhpl_simplegameplaysounddark_01_real_nougc_noseason_en_full_na_al.html
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
- Wordle
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/manifest.json b/data/manifest.json
similarity index 100%
rename from manifest.json
rename to data/manifest.json
diff --git a/build.ps1 b/scripts/build.ps1
similarity index 100%
rename from build.ps1
rename to scripts/build.ps1
diff --git a/scripts/check-dogcat3-playwright.mjs b/scripts/check-dogcat3-playwright.mjs
deleted file mode 100644
index 367f730..0000000
--- a/scripts/check-dogcat3-playwright.mjs
+++ /dev/null
@@ -1,177 +0,0 @@
-import { chromium } from 'playwright';
-import { createServer } from 'node:http';
-import { execFileSync } from 'node:child_process';
-import { mkdtemp, readdir, readFile, rm } from 'node:fs/promises';
-import { existsSync } from 'node:fs';
-import { tmpdir } from 'node:os';
-import path from 'node:path';
-
-const root = process.cwd();
-const dogCatRoot = process.env.DOGCAT_ROOT || path.join(root, 'aiAssets', 'DogCat3');
-const waitMs = Number(process.env.RUNTIME_WAIT_MS || 9000);
-
-const mraidStub = `
-window.mraid = window.mraid || {
- getState: function(){ return 'default'; },
- isViewable: function(){ return true; },
- addEventListener: function(name, cb){ if (name === 'ready') setTimeout(cb, 0); },
- removeEventListener: function(){},
- open: function(url){ console.log('[stub mraid.open]', url); },
- close: function(){ console.log('[stub mraid.close]'); },
- supports: function(){ return false; }
-};
-`;
-
-const exitApiStub = `
-window.ExitApi = window.ExitApi || {
- exit: function(){ console.log('[stub ExitApi.exit]'); }
-};
-`;
-
-async function collectCases(tempDir) {
- const cases = [];
- const networkFolders = [
- 'Applovin',
- 'Facebook',
- 'Ironsource',
- 'Moloco',
- 'Unity',
- 'GoogleAds',
- 'Mintegral',
- 'Vungle',
- ];
- for (const name of networkFolders) {
- const dir = path.join(dogCatRoot, name);
- if (!existsSync(dir)) continue;
- const entries = await readdir(dir);
- const htmlName = entries.find((entry) => /\.html?$/i.test(entry));
- if (htmlName) {
- cases.push([name, await readFile(path.join(dir, htmlName))]);
- continue;
- }
- const zipName = entries.find((entry) => /\.zip$/i.test(entry));
- if (!zipName) continue;
- const zip = path.join(dir, zipName);
- const out = path.join(tempDir, name);
- execFileSync('powershell', [
- '-NoProfile',
- '-Command',
- `Expand-Archive -LiteralPath '${zip.replaceAll("'", "''")}' -DestinationPath '${out.replaceAll("'", "''")}' -Force`,
- ], { stdio: 'ignore' });
- const expanded = await readdir(out);
- const indexName = expanded.find((entry) => entry.toLowerCase() === 'index.html');
- if (indexName) cases.push([name, await readFile(path.join(out, indexName))]);
- }
- return cases;
-}
-
-async function startServer(cases) {
- const html = new Map(cases.map(([name, body]) => [`/${name}/index.html`, body]));
- const server = createServer((req, res) => {
- const url = new URL(req.url || '/', 'http://127.0.0.1');
- if (url.pathname.endsWith('/mraid.js')) {
- res.writeHead(200, { 'content-type': 'application/javascript' });
- res.end(mraidStub);
- return;
- }
- if (url.pathname.endsWith('/exitapi.js')) {
- res.writeHead(200, { 'content-type': 'application/javascript' });
- res.end(exitApiStub);
- return;
- }
- const body = html.get(url.pathname);
- if (body) {
- res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
- res.end(body);
- return;
- }
- res.writeHead(404, { 'content-type': 'text/plain' });
- res.end('not found');
- });
- await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
- return {
- baseUrl: `http://127.0.0.1:${server.address().port}`,
- close: () => new Promise((resolve) => server.close(resolve)),
- };
-}
-
-function simplifyUrl(url) {
- return url.replace(/^https?:\/\/127\.0\.0\.1:\d+\//, '/');
-}
-
-async function runCase(browser, baseUrl, name) {
- const page = await browser.newPage({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2, isMobile: true });
- const issues = [];
-
- page.on('pageerror', (error) => {
- issues.push({ type: 'pageerror', text: error.stack || error.message });
- });
- page.on('console', (message) => {
- if (['error', 'assert'].includes(message.type())) {
- const location = message.location();
- const suffix = location.url ? ` @ ${simplifyUrl(location.url)}:${location.lineNumber}:${location.columnNumber}` : '';
- issues.push({ type: `console.${message.type()}`, text: `${message.text()}${suffix}` });
- }
- });
- page.on('requestfailed', (request) => {
- issues.push({
- type: 'requestfailed',
- text: `${simplifyUrl(request.url())} :: ${request.failure()?.errorText || 'failed'}`,
- });
- });
- page.on('response', (response) => {
- if (response.status() >= 400) {
- issues.push({
- type: `http.${response.status()}`,
- text: simplifyUrl(response.url()),
- });
- }
- });
-
- await page.goto(`${baseUrl}/${name}/index.html`, { waitUntil: 'domcontentloaded', timeout: 20000 });
- await page.waitForTimeout(waitMs);
-
- const bodyText = await page.locator('body').evaluate((body) => body.innerText.slice(0, 2000)).catch(() => '');
- const canvasCount = await page.locator('canvas').count().catch(() => 0);
- const title = await page.title().catch(() => '');
- await page.close();
-
- return { issues, canvasCount, title, bodyText };
-}
-
-async function main() {
- const tempDir = await mkdtemp(path.join(tmpdir(), 'dogcat3-playwright-'));
- let server;
- let browser;
- try {
- const cases = await collectCases(tempDir);
- server = await startServer(cases);
- browser = await chromium.launch({ channel: 'msedge', headless: true });
-
- for (const [name] of cases) {
- const result = await runCase(browser, server.baseUrl, name);
- console.log(`\n## ${name}`);
- console.log(`canvas=${result.canvasCount}`);
- if (result.title) console.log(`title=${result.title}`);
- if (!result.issues.length) {
- console.log('No page errors, console errors, failed requests, or HTTP 4xx/5xx responses captured.');
- } else {
- for (const issue of result.issues) {
- console.log(`- ${issue.type}: ${issue.text.replace(/\s+/g, ' ').slice(0, 700)}`);
- }
- }
- if (result.bodyText && /error|exception|cannot|failed/i.test(result.bodyText)) {
- console.log(`body_error_text=${result.bodyText.replace(/\s+/g, ' ').slice(0, 700)}`);
- }
- }
- } finally {
- if (browser) await browser.close();
- if (server) await server.close();
- await rm(tempDir, { recursive: true, force: true });
- }
-}
-
-main().catch((error) => {
- console.error(error);
- process.exitCode = 1;
-});
diff --git a/run-standalone.ps1 b/scripts/run-standalone.ps1
similarity index 100%
rename from run-standalone.ps1
rename to scripts/run-standalone.ps1