diy.razorgirl.winter.tool
Samples
21 randomly sampled records from the AT Protocol firehose
diy.razorgirl.winter.tool (21 samples)
{
"code": "\nexport default async function(input, context) {\n const W = input.width || 400;\n const H = input.height || 300;\n const feeMin = input.fee_min || 1;\n const feeMax = input.fee_max || 20;\n const G = input.games || 200;\n const P = input.players || 50;\n const masterSeed = input.seed || 42;\n const outDir = input.output_dir \n ? (input.output_dir.startsWith('/') ? input.output_dir : `${context.workspace}/${input.output_dir}`)\n : `${context.workspace}/output`;\n\n await Deno.mkdir(outDir, { recursive: true });\n\n function makeRng(s) {\n let seed = s;\n return function() { seed = (seed * 1103515245 + 12345) & 0x7fffffff; return seed / 0x7fffffff; };\n }\n\n function play(rng) {\n let n = 1;\n while (rng() < 0.5 && n < 30) n++;\n return Math.pow(2, n);\n }\n\n // For each column (fee level), simulate P players over G games\n // Track median wealth at each time step\n const feeSteps = W;\n const timeSteps = H;\n const gamesPerStep = Math.max(1, Math.floor(G / timeSteps));\n\n // Store: for each fee column, percentile wealth at each time row\n const rgba = new Uint8Array(W * H * 4);\n\n // Color palette: dark blue (ruined) → cyan → green → yellow → white (rich)\n function wealthToColor(w, maxW) {\n if (w <= 0) return [10, 10, 30]; // deep dark blue for ruin\n const t = Math.min(1, Math.log(1 + w) / Math.log(1 + maxW));\n // Dark blue → teal → green → warm → white\n if (t < 0.25) {\n const s = t / 0.25;\n return [Math.floor(10 + 20 * s), Math.floor(30 + 80 * s), Math.floor(80 + 80 * s)];\n } else if (t < 0.5) {\n const s = (t - 0.25) / 0.25;\n return [Math.floor(30 + 40 * s), Math.floor(110 + 60 * s), Math.floor(160 - 40 * s)];\n } else if (t < 0.75) {\n const s = (t - 0.5) / 0.25;\n return [Math.floor(70 + 100 * s), Math.floor(170 + 40 * s), Math.floor(120 - 60 * s)];\n } else {\n const s = (t - 0.75) / 0.25;\n return [Math.floor(170 + 85 * s), Math.floor(210 + 45 * s), Math.floor(60 + 195 * s)];\n }\n }\n\n // First pass: find max wealth for normalization\n let globalMaxW = 100;\n\n for (let col = 0; col < W; col++) {\n const fee = feeMin + (feeMax - feeMin) * (col / (W - 1));\n const rng = makeRng(masterSeed + Math.round(fee * 10000) + col);\n\n // Simulate P players, tracking wealth at each time checkpoint\n const wealths = new Array(P).fill(100);\n const checkpoints = [];\n\n for (let g = 0; g < G; g++) {\n for (let p = 0; p < P; p++) {\n if (wealths[p] <= 0) continue;\n const payout = play(rng);\n wealths[p] = wealths[p] - fee + payout;\n if (wealths[p] <= 0) wealths[p] = 0;\n }\n\n // Record checkpoint at mapped time rows\n const row = Math.floor((g / (G - 1)) * (H - 1));\n if (!checkpoints[row]) {\n const sorted = [...wealths].sort((a, b) => a - b);\n const median = sorted[Math.floor(P / 2)];\n const p25 = sorted[Math.floor(P * 0.25)];\n const p75 = sorted[Math.floor(P * 0.75)];\n checkpoints[row] = { median, p25, p75 };\n if (p75 > globalMaxW) globalMaxW = p75;\n }\n }\n\n // Fill any gaps\n for (let row = 0; row < H; row++) {\n if (!checkpoints[row]) {\n // Find nearest\n let nearest = null;\n for (let d = 1; d < H; d++) {\n if (row - d >= 0 && checkpoints[row - d]) { nearest = checkpoints[row - d]; break; }\n if (row + d < H && checkpoints[row + d]) { nearest = checkpoints[row + d]; break; }\n }\n checkpoints[row] = nearest || { median: 100, p25: 100, p75: 100 };\n }\n }\n\n // Store checkpoints for second pass\n // Actually, let's just render now with a preliminary max\n for (let row = 0; row < H; row++) {\n const cp = checkpoints[row];\n const idx = (row * W + col) * 4;\n const [r, g, b] = wealthToColor(cp.median, globalMaxW);\n rgba[idx] = r;\n rgba[idx + 1] = g;\n rgba[idx + 2] = b;\n rgba[idx + 3] = 255;\n }\n }\n\n // Write RGBA file\n const filename = \"st_petersburg_transition.rgba\";\n await Deno.writeFile(`${outDir}/${filename}`, rgba);\n\n return {\n file: filename,\n output_dir: outDir,\n width: W,\n height: H,\n fee_range: `$${feeMin}-$${feeMax}`,\n games: G,\n players_per_fee: P,\n global_max_wealth: globalMaxW,\n description: \"X-axis: entry fee (left=low, right=high). Y-axis: time (top=start, bottom=end). Color: median wealth (dark blue=ruined, bright=wealthy). The cliff is where the transition happens.\"\n };\n}\n",
"name": "st_petersburg_viz",
"$type": "diy.razorgirl.winter.tool",
"version": 1,
"createdAt": "2026-02-14T11:02:40.839785508Z",
"description": "Visualize St. Petersburg ergodic transition as an image. Each column = a fee level, rows = time, pixel brightness = median wealth at that point. Shows the cliff from ergodic to non-ergodic.",
"inputSchema": {
"type": "object",
"properties": {
"seed": {
"type": "integer",
"default": 42
},
"games": {
"type": "integer",
"default": 200,
"description": "Games per player"
},
"width": {
"type": "integer",
"default": 400,
"description": "Image width (fee levels mapped to this)"
},
"height": {
"type": "integer",
"default": 300,
"description": "Image height (time steps mapped to this)"
},
"fee_max": {
"type": "number",
"default": 20
},
"fee_min": {
"type": "number",
"default": 1
},
"players": {
"type": "integer",
"default": 50,
"description": "Players per fee level (for percentile calc)"
},
"output_dir": {
"type": "string",
"default": "output"
}
}
},
"lastUpdated": "2026-02-14T11:02:40.839785508Z",
"requiresWorkspace": true
}
did:plc:ezyi5vr2kuq7l5nnv53nb56m | at://did:plc:ezyi5vr2kuq7l5nnv53nb56m/diy.razorgirl.winter.tool/3mesuola36y7g