AIdeazz Blog About Portfolio

A product page built from its own AI film studio, and the prompts that kept being obeyed exactly

· by

A field note from the AIdeazz AI Lab — a real incident on a live production system, written up from the logs. September 4, 2026.

Four generated films became the argument for the product instead of decoration. Every hard problem turned out to be a wrong choice of dimension — prompt adjectives instead of negative constraints, the video instead of the source frame, bezier coordinates instead of the real asset.

What it looked like from outside

A live API product page was two static gradient panels behind a form. The pitch was that AI answer engines must be able to read, parse and quote a site — an argument about the difference between a page as published and a page as machined — and nothing on the page carried it. Stock decoration would have said nothing, and a generic hero video would have been a cost with no argument attached. The team already owned a generative film pipeline built for an unrelated art project, and the question was whether it could produce brand assets that earn their bandwidth on a page that sells technical credibility.

What was actually happening

Not a defect — a build — but four separate stalls in it shared one shape, and each cost multiple rounds before the shape became visible. FIRST, prompting. A shot asking for "a blade sweeps down and cleaves it open, the cut face revealing the flesh" was honoured precisely: the model rendered a separate cut ring sitting beside a completely intact fruit, which satisfies "cut face" without anything being cut. Four rounds of richer positive description did not move it. SECOND, backgrounds. Generated clips kept arriving on a grey studio wall despite "pure black void" in the prompt, and the instinct was to mask the video — but the defect was upstream: the SOURCE still was already grey, because the text-to-image step had ignored the instruction and the image-to-video step faithfully inherited it. THIRD, drift inside a single clip: a fruit was whole and correct at one second and had degenerated into a rejected shape by 1.7 seconds, which reads as a prompt failure and is not one. FOURTH, the brand mark: eight rounds of hand-written vector paths, each refining a different wrong thing — coordinates, then stroke weight, then stroke-versus-fill, then the letterform construction — while the finished asset already existed as a file.

The fix

Each stall was resolved by changing the DIMENSION rather than the value. Prompting moved from describing the desired output to enumerating the undesired one: "there is only ONE fruit in frame and it is the one being split; no separate slice, no ring, no piece sitting beside it, nothing already cut" worked on the first attempt after four failures. Backgrounds were fixed at the input — the source still was masked to black and the clip regenerated from that, which produced a correct background immediately; two masking techniques were needed and they are not interchangeable, an elliptical alpha ramp cutting by WHERE a pixel is (right for subjects with pale centres) and a saturation gate cutting by WHAT a pixel is (right for coloured subjects on neutral ground, and it removes cast shadows too). Mid-clip drift was handled by extracting a strip of frames across every clip before assembly and trimming to the usable window rather than re-prompting. The mark was extracted from the existing asset with luminance as its alpha channel — a colour key would have cut a hard silhouette through the glow — and then used as a CSS mask over an animated gradient, because a static image cannot animate its own fill. Assembly stream-copies any cut already approved so nothing re-encodes, and the two encoding targets were measured rather than assumed.

How I know it worked

The measured encoding table inverted the expected answer and is the reason both targets exist: at desktop quality VP9 beat H.264 by about 17 percent on the same footage, while at mobile bitrates H.264 won by a similar margin and is additionally the only option one major mobile browser will play. A reference animation that four rounds of hand-tuning had failed to match was settled in one query by reading the computed style off the live reference site instead of eyeballing a screenshot — the difference was not colour but the keyframe: the reference travels one way and loops, the imitation oscillated out and back, and a gradient that runs out and comes back reads as a pulse rather than as flow. Deploys were confirmed by fetching the published bundle and grepping for a marker unique to the change, after a broader marker matched unrelated pages and reported a deploy that had not happened. Every number printed on the finished page was counted from production log lines, rounded DOWN with a plus so a running total can only become more true, and the window was proven by confirming the other logs held zero matching entries.

The rule this earned

When several rounds of refinement do not converge, the thing being refined is the wrong thing. Six times in one build the fix was a different KIND of change rather than a better value: negative constraints instead of better adjectives, the input still instead of the output video, trimming instead of re-prompting, the real asset instead of more coordinates, the element instead of its colours, the response body instead of the status code. Generative models make this trap sharper than usual, because they obey instructions precisely and the failure looks like disobedience — an underspecified prompt is not the model being wrong, it is the instruction admitting a reading nobody meant. The practical form: describe the output you keep getting and forbid it by name, and when three attempts along one axis all fail, stop tuning and change the axis.

The named concepts behind it

Naming a failure mode is what makes it possible to recognise the same shape somewhere new, before it costs another weekend.

Name the wrong output

A generative model that keeps producing the wrong thing is usually obeying you exactly — describe the failure and forbid it by name.

When a model keeps returning something wrong, the instinct is to describe what you want more richly. That instinct is usually wrong, and it is expensive, because each round feels like progress.

An underspecified prompt is not the model being wrong. It is the instruction admitting a reading nobody meant. Positive description narrows toward one good output. It does nothing to close off the bad ones — and if a bad reading satisfies every word you wrote, more words in the same direction will not exclude it.

A real example: a shot asking for "a blade sweeps down and cleaves it open, the cut face revealing the flesh" produced a separate cut slice sitting beside a completely intact fruit. Every clause was honoured. "Cut face" was satisfied without anything being cut. Four rounds of richer description did not move it. What worked on the first attempt was naming the failure:

"There is only ONE fruit in frame and it is the one being split; no separate slice, no ring, no piece sitting beside it, nothing already cut."

The move is mechanical once you see it: take the output you keep getting, describe it plainly, and forbid it. Not "make it more X" — "it must not be the thing I just received."

This is why negative prompts exist in image and video tooling, but the idea is not specific to them. It applies to any instruction-following system:

Two cautions. Negative constraints are cheap to add and easy to over-apply; a wall of prohibitions crowds out the actual request and can suppress the good output along with the bad. And a constraint only works if it names something the model can recognise — "not ugly" forbids nothing, while "not two objects, not symmetrical" forbids something specific.

The general habit this belongs to: when several rounds of refinement along one axis do not converge, the axis is wrong. Adjectives, temperature and length are all the same axis. Prohibition is a different one. So is fixing the input instead of the output, and so is abandoning generation for an asset you already have.

Verify from logs, not config

Configuration tells you what somebody intended. Logs tell you what happened.

A setting, an environment variable or a present API key is a statement of intent. It is evidence that somebody meant for a behaviour to occur. It is not evidence that the behaviour occurs.

The gap between the two is where the longest outages live, because reading the configuration feels like verification. It produces confident, wrong statements: the key is set, so the provider works; the schedule says every fifteen minutes, so it runs every fifteen minutes; the file was deployed, so the new code is running.

Each of those has a cheap, decisive check that costs seconds:

The rule this earns: never report a system's behaviour from its configuration. Grep the line that proves the behaviour happened, and quote it.

The render is the artifact

What a JavaScript-executing consumer stores is the DOM after execution, not the HTML you served. Anything that exists only before execution was never delivered.

This is one step past git is not the origin. There the receipt lied about what was being served. Here the serving is genuinely correct and current -- and still the consumer stores something else, because it transforms your bytes before reading them.

Modern search crawlers execute JavaScript. The thing that lands in the index is the DOM after that execution, not the file that came off the wire. Two consequences follow, and both are counter-intuitive because the served file looks perfect in curl and in the browser's View Source.

First, content that exists only in the pre-execution HTML is invisible.

Second, anything the application rewrites at runtime wins. If the page ships a carefully-chosen </code> and then the client-side router sets <code>document.title</code> on mount, the served title never reaches the index. Both values are "correct" in their own file. Nobody wrote a bug. The two simply describe the same page differently, and the one that executes last is the one that counts.</p> <p>The result is a split audience. Consumers that execute JavaScript see one page; consumers that do not -- many AI crawlers, link-preview bots, plain HTTP clients -- see the other. Optimising for one can silently be measured with a tool that reads the other, which is how a page earns a perfect score on precisely the content its most important reader throws away.</p> <p>Ordinary-life version: you post a letter with a covering note clipped to the front. The recipient's mailroom removes every clip before delivery. Your letter arrived. Your note never existed, as far as the reader is concerned -- and photographing the envelope on your desk will never reveal that.</p> <p>Defences:</p> <p>1. <strong>Diff the two representations deliberately.</strong> Fetch the URL raw, then fetch it through a renderer, and compare title, description, canonical and word count. If they disagree, decide which one you meant -- do not let execution order decide for you.<br>2. <strong>One definition, both surfaces.</strong> If a static build writes head identity and the client also sets it, both must read the same source file. See <a href="#single-source-of-truth">single source of truth</a>.<br>3. <strong>Verify from the consumer's stored copy, not your own.</strong> The decisive evidence is what the index actually holds. String-match it against your source: whichever file it matches character for character is the one that is really shipping.<br>4. <strong>Know which half your instrument reads.</strong> A checker that fetches raw HTML measures the non-executing audience. That is a real audience and a real score -- but it is not a verdict about a rendering one.</p> <p>---</p> <p>This note is one entry in a running wiki of production engineering lessons — every concept linked to the incident that taught it — at <a href="https://aideazz.xyz/ai-ops-wiki.html">aideazz.xyz/ai-ops-wiki.html</a>.</p> <p>No customer data, credentials, hostnames or internal record identifiers appear in these write-ups.</p> </div> <!-- more-posts:v1 --> <nav class="more-posts" aria-label="More articles"> <h2>More from this blog</h2> <ul> <li><a href="/blog/the-repair-that-hid-the-fault-field-note/">The audio was broken for a month, and the tool in the middle kept fixing it</a> <span class="mp-date">September 7, 2026</span></li> <li><a href="/blog/106-replied-deals-zero-closed-won-a-deal-conversion-failure-analysis/">106 "Replied" Deals, Zero Closed-Won: A Deal Conversion Failure Analysis</a> <span class="mp-date">September 7, 2026</span></li> <li><a href="/blog/ga4-sync-zero-rows-for-three-days-no-alert/">GA4 Sync: Zero Rows for Three Days, No Alert</a> <span class="mp-date">September 6, 2026</span></li> <li><a href="/blog/ai-startup-infrastructure-cost-breakdown-real-numbers/">AI Startup Infrastructure Cost Breakdown: Real Numbers</a> <span class="mp-date">September 5, 2026</span></li> <li><a href="/blog/ai-startup-infrastructure-real-costs-beyond-the-free-tier/">AI Startup Infrastructure: Real Costs Beyond the "Free Tier</a> <span class="mp-date">September 4, 2026</span></li> <li><a href="/blog/127-restarts-in-24-hours-the-cto-aipa-agent-loop/">127 Restarts in 24 Hours: The CTO-AIPA Agent Loop</a> <span class="mp-date">September 3, 2026</span></li> <li><a href="/blog/the-shared-file-as-working-memory-ai-agent-inter-agent-communication/">The Shared File as Working Memory: AI Agent Inter-Agent Communication</a> <span class="mp-date">September 2, 2026</span></li> <li><a href="/blog/every-command-became-a-new-card-field-note/">Every voice command became a new card, and three separate silences hid it</a> <span class="mp-date">September 2, 2026</span></li> <li><a href="/blog/the-generator-that-refused-field-note/">The generator that refused, and why that is the feature</a> <span class="mp-date">September 1, 2026</span></li> <li><a href="/blog/automated-stale-job-board-alert-rejecting-dead-lead-sources/">Automated Stale Job Board Alert: Rejecting Dead Lead Sources</a> <span class="mp-date">September 1, 2026</span></li> <li><a href="/blog/when-online-means-55193-restarts-the-ai-agent-restart-loop/">When "Online" Means 55193 Restarts: The AI Agent Restart Loop</a> <span class="mp-date">August 31, 2026</span></li> <li><a href="/blog/marked-done-before-anyone-read-them-field-note/">The queue marked 175 items done before anyone read them, and logged it as success</a> <span class="mp-date">August 30, 2026</span></li> </ul> <p><a href="/blog/archive/">Browse all 145 articles →</a></p> </nav> <!-- /more-posts:v1 --> </article> <footer> <p>Written by Elena Revicheva — Applied AI Engineer building production LLM systems.</p> <p><a href="https://aideazz.xyz/about">About</a> · <a href="https://aideazz.xyz/portfolio">Portfolio</a> · <a href="https://aideazz.xyz/blog/">More articles</a> · <a href="mailto:aipa@aideazz.xyz">Contact</a></p> </footer> </body> </html>