THE CRITIC'S FACE: he brings his own portraits now
Five moods generated on the Cloudflare Workers AI FLUX lane (10k free neurons a day — scripts/cf-flux.py is the tap): a gaunt man in a black three-piece suit, burgundy scarf, small round glasses, tiny notebook and fountain pen, same claymation family as the judge and unmistakably not him. Fixed seed, expression-only prompt deltas, so he is the same figure across all five — the horrified one recoils mid-note, the impressed one wears the small smile of a man betrayed by a good meal. The grayscale filter is gone: the scorecard and the corner eye both route by critic day (critic_* / judge_* prefix), demos and dailies stay the judge's own. Also fixed 'the the count betrayed you' — his rows already start with THE and he would never stammer in print. Verified live: day 20 corner eye critic_neutral, scorecard critic_impressed at 10.0 and critic_disappointed at 4.9 (C stamp, his lines), day 21 back to the judge everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9ca713b5c9
commit
720e4dd5df
BIN
public/assets/img/critic_disappointed.png
Normal file
BIN
public/assets/img/critic_disappointed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
BIN
public/assets/img/critic_horrified.png
Normal file
BIN
public/assets/img/critic_horrified.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 548 KiB |
BIN
public/assets/img/critic_impressed.png
Normal file
BIN
public/assets/img/critic_impressed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 500 KiB |
BIN
public/assets/img/critic_intrigued.png
Normal file
BIN
public/assets/img/critic_intrigued.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 515 KiB |
BIN
public/assets/img/critic_neutral.png
Normal file
BIN
public/assets/img/critic_neutral.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 504 KiB |
32
scripts/cf-flux.py
Executable file
32
scripts/cf-flux.py
Executable file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
"""One FLUX-1-schnell image via Cloudflare Workers AI (10k free neurons/day).
|
||||
Usage: cf-flux.py <outfile.png> <prompt> [steps] [seed]
|
||||
Creds come from backnforth/.env — never printed, never committed."""
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
out, prompt = sys.argv[1], sys.argv[2]
|
||||
steps = int(sys.argv[3]) if len(sys.argv) > 3 else 8
|
||||
seed = int(sys.argv[4]) if len(sys.argv) > 4 else 0
|
||||
|
||||
env = {}
|
||||
with open(os.path.expanduser('~/Documents/backnforth/.env')) as f:
|
||||
for line in f:
|
||||
if '=' in line and not line.startswith('#'):
|
||||
k, _, v = line.strip().partition('=')
|
||||
env[k] = v
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"https://api.cloudflare.com/client/v4/accounts/{env['CLOUDFLARE_ACCOUNT_ID']}/ai/run/@cf/black-forest-labs/flux-1-schnell",
|
||||
data=json.dumps({'prompt': prompt, 'steps': steps, 'seed': seed}).encode(),
|
||||
headers={'Authorization': f"Bearer {env['CLOUDFLARE_API_TOKEN']}", 'Content-Type': 'application/json'},
|
||||
)
|
||||
r = json.load(urllib.request.urlopen(req, timeout=120))
|
||||
if not r.get('success'):
|
||||
sys.exit('CF error: ' + json.dumps(r.get('errors')))
|
||||
with open(out, 'wb') as f:
|
||||
f.write(base64.b64decode(r['result']['image']))
|
||||
print(out)
|
||||
@ -641,7 +641,8 @@ export class Game {
|
||||
}
|
||||
|
||||
beginDay(): void {
|
||||
eye.show(); // he watches you cook
|
||||
eye.critic = !this.dailyDate && !this.demoMode && this.save.day % 10 === 0;
|
||||
eye.show(); // he watches you cook — or on the tenth day, HE does
|
||||
document.body.classList.toggle('shadow', this.shadowMode);
|
||||
const day = this.save.day;
|
||||
// Procedural days are seeded BY the day, not by a running stream: a reload
|
||||
@ -999,7 +1000,7 @@ export class Game {
|
||||
if (v.grade === 'S') extras.push('The Critic: I came to write a takedown. I cannot. This is the worse outcome — for me.');
|
||||
else if (v.grade === 'A') extras.push('The Critic: competent. The word has never once appeared in a good review.');
|
||||
else if (v.grade === 'F') extras.push('The Critic: I will be gentle in print. This kitchen has suffered enough today.');
|
||||
else extras.push(`The Critic: the ${v.worst.label.toLowerCase()} betrayed you. It will be the headline.`);
|
||||
else extras.push(`The Critic: the ${v.worst.label.toLowerCase().replace(/^the /, '')} betrayed you. It will be the headline.`);
|
||||
}
|
||||
// THE MOTHER: a lively starter firms the crumb on toaster days. A dead
|
||||
// one is on the shelf where he can see it, and he can smell a coward.
|
||||
|
||||
@ -33,7 +33,7 @@ export class JudgeView implements View {
|
||||
onNext: (() => void) | null = null;
|
||||
/** One-shot lines the game adds to this verdict (streaks, regulars' memory). */
|
||||
extraLines: string[] = [];
|
||||
/** Every 10th day THE CRITIC seats himself — same face, drained of warmth. */
|
||||
/** Every 10th day THE CRITIC seats himself — his own face, his own notebook. */
|
||||
critic = false;
|
||||
/** The one-line result SHARE copies to the clipboard. */
|
||||
shareText = '';
|
||||
@ -265,8 +265,7 @@ export class JudgeView implements View {
|
||||
this.extraLines = [];
|
||||
this.lastCaption = { day: order.day, grade: verdict.grade, total: verdict.total, line: lines[1] ?? lines[0] ?? '' };
|
||||
|
||||
this.faceEl.src = assetUrl(`/assets/img/judge_${judgeFace(verdict.grade)}.png`);
|
||||
this.faceEl.style.filter = this.critic ? 'grayscale(1) contrast(1.25)' : '';
|
||||
this.faceEl.src = assetUrl(`/assets/img/${this.critic ? 'critic' : 'judge'}_${judgeFace(verdict.grade)}.png`);
|
||||
this.stampEl.textContent = verdict.grade;
|
||||
this.stampEl.className = `judge-stamp grade-${verdict.grade}`;
|
||||
// re-trigger the stamp animation
|
||||
|
||||
@ -9,6 +9,8 @@ export type Mood = 'neutral' | 'intrigued' | 'impressed' | 'disappointed' | 'hor
|
||||
*/
|
||||
class Eye {
|
||||
private el: HTMLImageElement | null = null;
|
||||
/** Critic days: the corner watcher is the man with the notebook. */
|
||||
critic = false;
|
||||
private timer: number | null = null;
|
||||
private current: Mood = 'neutral';
|
||||
|
||||
@ -32,7 +34,7 @@ class Eye {
|
||||
|
||||
private set(m: Mood): void {
|
||||
this.current = m;
|
||||
this.ensure().src = assetUrl(`/assets/img/judge_${m}.png`);
|
||||
this.ensure().src = assetUrl(`/assets/img/${this.critic ? 'critic' : 'judge'}_${m}.png`);
|
||||
}
|
||||
|
||||
show(): void {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user