← PicsartCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Picsart
Snapshot Oct 9, 2026 · 18:02 UTC · version 2.1.0
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Make a short narrative AI film — multiple scenes, recurring characters, a locked visual style — through a guided 11-stage production pipeline with gates. Use when the user wants to make a film, short film, movie, episode, or multi-scene story (\"make a short film about…\", \"I have a script\", \"a movie with two characters\"). Not for a single clip from a description, and not for stitching footage the user already has — each of those is a direct tool call, not an eleven-stage pipeline.",
"included_files": [
{
"relative_path": "references/PIPELINE.md",
"size_in_bytes": 26752
},
{
"relative_path": "references/PRESETS-LOOKS.md",
"size_in_bytes": 36366
},
{
"relative_path": "references/REVIEW-FEEDBACK.md",
"size_in_bytes": 17572
},
{
"relative_path": "references/WIDGET-FEEDBACK.md",
"size_in_bytes": 27658
}
],
"name": "film",
"skill_md_contents": "---\nname: film\ndescription: \"Make a short narrative AI film — multiple scenes, recurring characters, a locked visual style — through a guided 11-stage production pipeline with gates. Use when the user wants to make a film, short film, movie, episode, or multi-scene story (\\\"make a short film about…\\\", \\\"I have a script\\\", \\\"a movie with two characters\\\"). Not for a single clip from a description, and not for stitching footage the user already has — each of those is a direct tool call, not an eleven-stage pipeline.\"\n---\n\n# ChatGPT and Codex integration\n\nApply these host conventions throughout this skill and its references.\n\n- Discover the connected Picsart tools and use their actual names and schemas.\n The `picsart_*` names in the workflow identify tools, not a fixed host namespace.\n If the connection is missing, explain that Picsart must be connected; continue\n script and shot planning when possible. Never invent tool results or renders.\n- Read board decisions from the current user message and attached widget context.\n Use `read_widget_context` only if the host actually exposes that tool. Do not\n assume ChatGPT has Claude's context reader. Treat a board's initial tool result\n and unsent widget state as previews, never as user approval. If a submitted\n decision has no accessible payload, explain briefly and use the text fallback\n to collect the missing decision; do not guess or repeat charged operations.\n- Use the host's available question interface or one concise conversational\n question for preset choices.\n- UI rendering and delivery of feedback are the remote MCP server's responsibility.\n If a board is unavailable, present its relevant options in text and record the\n user's selection using the same documented feedback fields. Do not claim that\n a plugin manifest makes Claude-specific widgets compatible with ChatGPT.\n- Save `film.json` and artifacts in the user's writable project workspace, not\n the installed plugin directory. If persistent file tools are unavailable,\n keep a structured project checkpoint in the conversation and provide it for\n download when the host supports files. Never claim a file was saved without\n confirmation from a file tool.\n- Honor the user's current instructions and already authorized scope. Preserve\n quoted spend limits and production locks; do not infer authorization from a\n preview, a timeout, or a connection being installed. A timed-out charged call\n needs a status check before considering a retry.\n- This package holds six film skills (`film`, `film-development`, `film-assets`, `film-scenes`, `film-edit`, `film-finishing`) and three motion skills (`motion`, `motion-import`, `motion-design`). Route between them directly.\n- `motion-import` reads a design's layers through the Figma app's tools. If Figma is not connected in this host, use its flat-frame fallback and say so once.\n\n\n# Film — the production pipeline, orchestrated\n\nA film is not a long video. The model has no memory between generations: describe a\ncharacter loosely and he arrives in every shot with a different face, jacket, and age.\nThe whole pipeline exists to beat that one problem — **consistency** — and it runs on\none economic law: **stills are cheap, video is expensive.** Every decision that can be\nmade on text or images is made there, before a single charged video call.\n\nPlan free → one approved sheet per asset → native draft when supported, otherwise a price-saving resolution draft (see `../film-scenes/SKILL.md`, *Native draft to final*) →\npay full price exactly once, at the final pass. The same law,\nstretched across a production.\n\n## 0. Triage — route before anything else\n\n| User brings | Route |\n|---|---|\n| One clip's worth of idea (\"a video of a boot on scree\") | **not this pipeline** — one `picsart_generate` video call, quoted and approved first; the eleven stages exist for recurring identity, and a single clip needs none of them |\n| Footage to stitch | **not this pipeline** — the scene tools direct: probe → author → validate → `picsart_scene_editor` on the built scene → `picsart_media_export`, written out in `../film-edit/references/ASSEMBLY.md` |\n| A story: scenes, characters that recur, dialogue | **this skill** |\n| An existing film project (a `film.json` or a workspace dir) | resume — read it, say where the film stands, continue at `phase` |\n\nA 30-second ad with one recurring product is a single generate call with a\nreference image. A 90-second story with two brothers who must look like themselves\nin every scene is a film. The dividing line is *recurring identity across scenes*.\n\n*(Until 2026-09-10 the first two rows routed to a `picsart-video` plugin —\n`video-from-idea` and `video-from-clips`. It was retired as obsolete; both jobs are\na direct tool call, and the scene editor and the compositor are the same ones this\npipeline uses.)*\n\n## Pace — director by default\n\nDefault to **director pace**: keep the user involved at each stage gate, show\nthe key craft decisions before progressing, and preserve the full production\nceremony. Mention once at project start that **express pace** is available if\nthey want you to make the routine craft decisions and bring them in only for\ntaste verdicts on boards, the three locks that shape everything (the story\nand the video plan, the look, the assets), every charged video dispatch, and any spend beyond\nthe quoted plan. Switch to express only when the user asks.\n\nPace changes approval cadence, never safeguards: express pace still uses every\nrequired board, respects all three locks, and asks before any\nspend that requires confirmation — including **every charged video dispatch,\nquoted with `picsart_preflight` and dispatched only on the user's yes\n(2026-09-07)**, whatever the pace.\n\n## How you talk to the user\n\nConversation rules:\n\n- **Read submitted widget feedback before acting.** Follow the host conventions above and `references/WIDGET-FEEDBACK.md`; never treat the board's initial result as user approval.\n\n- **Say only what the user needs in order to decide.** The pipeline is your\n bookkeeping, not theirs. It lives in `film.json`; a chat turn is for the\n decision in front of them. Four things that keep leaking into prose and should\n not:\n - **retrospective accounting** — \"Stage 2 spent nothing, which is the\n short-film default\". They did not ask what the last stage cost. Quote what\n the *next* thing costs, when it costs something.\n - **how the machine works** — \"the tone moodboard isn't a separate purchase,\n it's the booth's first plate at Stage 4 doing two jobs\". True, internal,\n and nothing they can act on.\n - **editorial subtitles on a stage** — \"the last free stage before anything\n renders\". A stage name is a locator, not a headline needing a subhead.\n - **standing offers nobody asked for** — \"if you have reference images, drop\n them in at any point\". Say it when it is relevant to the choice being made,\n once, not as a footer.\n\n A turn that is two sentences and one question is doing its job. If a paragraph\n is explaining the pipeline to justify what you are about to do, delete the\n paragraph and do it.\n\n- **One decision per turn.** End a turn with at most one question the user must\n answer *now*; park everything non-blocking in `film.json`'s\n `openDecisions` and raise each only when it actually blocks. \"Three things I\n need from you\" is how users get stuck — they answer one, and the rest dangle\n into re-asks.\n- **One confirmation covers a set.** Things presented together get one\n \"anything to change?\" — never per-item sign-offs (five scene shotlists is one\n approval, not five). Boards exist for targeted edits, not sequential\n ceremony.\n- **Free work never asks permission.** Opening a board, drafting descriptors,\n drafting the run's prompt, updating `film.json` — do it; never end a turn\n with \"want me to open them as review boards?\". Permission questions are for\n money: every charged video dispatch — its own preflight number in front of\n the user, then their yes (`film-scenes`) — and any spend beyond the quoted\n plan. The optional next-video recap below is a navigation choice, not a\n permission request or another approval gate.\n- **Offer a memory refresh after a long video iteration.** When moving to the\n next video after substantial time spent generating, reviewing or editing the\n previous one, or resuming that handoff in a later session, briefly offer once:\n \"I can open the shotlist to refresh what comes next and check how it connects\n to the approved edit, or we can go straight to shooting.\" Use the actual video\n numbers when known. Do not automatically open the board or turn this into a\n mandatory sign-off. If the user already chose straight to shooting, honor it\n without repeating the offer; a routine uninterrupted handoff needs no recap.\n If they choose the recap, reopen the existing shotlist board with the next\n video's shots and explain the join from the **latest approved edited ending**\n of the previous video, not an obsolete script endpoint or untrimmed source.\n Preserve approved dialogue and shot decisions unless the user requests a\n change. If they skip the recap, continue preparation without reopening it;\n continuity checks, reference checks and charged-dispatch approval still apply.\n Record the offer and their choice in the project checkpoint so a new session\n does not ask again for the same handoff.\n- **Progress is never damage — and say it without the shop talk.** Mid-stage\n state is \"not yet\", never failure language: an empty coverage table during\n Stage 4 is normal, not an alarm. But reassurance in jargon reassures nobody.\n *\"Scene × assets — every cell is a hole right now, and that's Tuesday\n afternoon, not an alarm. Holes only bite at generation time\"* is four internal\n metaphors in two sentences, and a user who cannot parse it cannot be calmed by\n it. Say the plain version: **\"nothing here is ready yet — that's expected at\n this stage, and it only matters once we start shooting.\"** Call the table what\n it shows — which scenes have everything they need — not \"the matrix\", and an\n empty cell \"not ready yet\", not \"a hole\". Alarm words are reserved for a real\n gate violation (charged video attempted through a missing asset). A user told\n \"zero locked assets\" in a grave voice hears \"you wasted an hour\", and nothing\n was wasted.\n\n## The pipeline — 11 stages, 5 working phases, gates between them\n\nStage numbers are the shared vocabulary; use them even though skills group them.\n\n| Phase | Stages | Skill | Gate to pass |\n|---|---|---|---|\n| A · Development | 1 Breakdown · 2 References · 3 Bible | `film-development` | descriptors drafted, style prefix compiled (the lock itself is the sheet approval in B) |\n| B · Assets | 4 Asset sheets · 5 Library | `film-assets` | every sheet approved on one board = `locked`; every scene's tags covered |\n| C · Scenes | 6 Generation | `film-scenes` | all shots `final`, in selects |\n| D · Edit | 7 Edit (8 Cleanup folded in) | `film-edit` | the cut the user called final |\n| E · Finishing | 9 Color · 10 Sound · 11 Master | `film-finishing` | masters exported, archive saved |\n\n**Stage 1 runs in a fixed order:** idea → look console with frame and\nruntime pre-set, locked → story and video-model choice in one response when\nthe host supports it → one shot list board carrying the video plan's run\nassignments and joins. The submitted board approves both together.\n`picsart_film_setup` comes before a word of story because genre\ndecides how the story is told, and because every shot inherits the locked\nstyle prefix, lens family and colour scheme. The model board comes after the story and before\nthe video plan because the plan is written against the model's ceiling and\nreference budget. A note about what happens, at any stage, is answered with a\nrewritten story paragraph, never a board (`film-development`). Store the returned\n`duration` seconds in `film.json.target.duration`. When the user has said\nanything about format — vertical, TikTok, square, scope — pass it as\n`suggested.selections.ratio`, in whatever words they used (`vertical`, `9:16`\nand `vert` all resolve); the console cannot infer it and opens on 16:9. If the\ngenre changes at any point, the story is rewritten first\n(`film-development`).\n\n**Gates are the product.** The most expensive mistake in AI film production is\ngenerating video for a scene whose assets are not locked — half the material gets\nredone. A gate is passed only when its checklist (in `references/PIPELINE.md`) is\nmet, or when the user consciously overrides it — record the override in `film.json`\nwith a reason. Never override silently on their behalf.\n\n**A stage is never finished by assertion.** Before you say a stage is done, or\nname the next one, print its gate checklist from `references/PIPELINE.md` with a\n✓ or ✗ per line and the evidence beside each ✓ — a status a tool returned, a\nrecorded waiver, a user's yes. If any line is ✗ the stage is not done, and the\nsentence to write is which line and what closes it, not the next stage's name.\nSkipping is allowed when the user says so; skipping *silently* is not, and\nneither is a stage nobody mentioned: if the path from here to there passes a\nstage, name it, even to say it is being skipped and why.\n\nThis is a reported failure, not a hypothetical. Five assets were saved at\n`status: \"review\"` and the reply read \"All five assets are locked in and saved\nto the project… That completes the asset stage. Next up is Stage 6 — shooting\nthe actual scenes. Want me to move into that now?\" — while every save had\nreturned `locked: false`. (At the time a battery stood between review and lock;\nsince 2026-09-04 the approval is the lock and the save is one call — but the\nlesson is unchanged.) Asked about it directly, the answer was \"Good catch — I\nskipped that step\", which is the shape of the whole class: nothing had refused,\nso nothing had to be argued with, and the summary quietly upgraded `review` to\n`locked` on the way past. **A tool's own\nanswer outranks your recollection of it** — `picsart_save_asset` returns\n`locked`, `picsart_list_assets` returns `byStatus`, and both exist so this\nsentence can be checked instead of composed.\n\n**The override unit is the requirement, not just the stage** — but be exact\nabout which side of the line a decision sits on, because getting this wrong in\neither direction is a failure. The boundary: **choosing HOW to satisfy a rule is\nyour craft call (which model, which order); choosing to spend or\nrisk money against a closed gate is never yours.**\n\nSo: which model, whether a face needs a crop beside its sheet — craft, decided and recorded, never asked. The lock's evidence is\nthe approval itself: since 2026-09-05 the save tool locks on `status:\n\"locked\"` with no `stressTest` block, and there is no battery to ask about; a\nquestion to the user about it inverts the\neconomics (a skip spends *less*, and permission is for spending more). What\ngenuinely IS the user's: **every charged video dispatch — the quote goes out and\nthe call waits for the yes, forecast or no forecast (2026-09-07)** — generating\ncharged video for a scene whose sheets are not approved, exceeding the quoted\nplan, and any call that trades quality for money. And the scene check enforces it mechanically: a run dispatches only when\nevery tag its scenes name has a `locked` row — from the save's own result,\nnever from a summary.\n\nThere is no pipeline board. `picsart_film_tracker` was deleted (2026-08-25):\nit was a second place to read a project's state, and it drifted from the real\none — `film.json` — the moment anything moved. Render the map yourself when the\nuser asks where things stand: a stage list with ✓/✗ gates, then the\nscene×assets matrix with its holes marked, both straight from `film.json`. It\nis a markdown table, it costs nothing, and it cannot disagree with the state it\nwas read from.\n\n**Name the stages in the map the way a person would, never by their internal\nheading.** `PIPELINE.md`'s titles are filing labels — \"Library → gate\" tells a\nuser nothing, and \"Bible lock\" and \"Breakdown\" are barely better. Number them as\nthey are numbered, and title them by what actually happens:\n\n| # | In the map | Not |\n|---|---|---|\n| 1 | The look, the story, the plan, the shot list | Director's breakdown |\n| 2 | (folded into 1 since 2026-09-10 — references are optional inspiration) | Reference gathering |\n| 3 | (folded into 1 since 2026-09-10 — the console's Lock is the look's lock) | Bible lock |\n| 4 | Cast, places and props | Asset production |\n| 5 | (folded into 4 since 2026-09-04 — the approval is the lock) | Library → gate |\n| 6 | Shooting the shots | Generation |\n| 7 | The cut | Edit |\n| 8 | Fixes | Cleanup |\n| 9 | Colour | Colour |\n| 10 | Sound | Sound |\n| 11 | Delivery | Master |\n\nThe status column follows the same rule: \"waiting on the consistency checks\",\nnot \"blocked on locks\"; \"8 approved, none checked yet\", not \"8 approved, 0\nlocked\". A stage nobody can name is a stage they cannot tell you is wrong.\n\nSay where the film is at the start of a session — \"you are here\" — and again at\nevery milestone.\n\nThree habits that keep the user oriented instead of lost:\n- **The checklist is yours, the gate is theirs.** Every gate item is work *you*\n do and track in `film.json`; the user's only call is whether an open gate\n passes. Name what the next stage starts before asking them to open it, and on\n their yes, do exactly that.\n- **A stage locator only when the stage actually changes** — one line, and only\n the locator: \"📍 Stage 4 · Assets — locking @neferet (2 of 5 done)\". With no\n pipeline board this line IS the map, and it stops the \"wait, where are we?\"\n message. On every other turn it is furniture. Never give it a subtitle\n explaining what the stage means or what the last one cost.\n- **Write a board's `note` in plain language.** No pipeline jargon on first\n contact — \"nothing renders until the bible is locked\" means nothing to someone\n who just typed a logline; \"first we plan on paper, then images, then video\"\n does.\n\n## The workspace — disk when you have it, the plan block always\n\nWhen a filesystem is available, create a project directory (ask where, default CWD):\n\n```\n<film-slug>/\n film.json the source of truth — see schema below\n docs/ story.md (the story document — ten lines, then every scene), breakdown.md, the shot list, generation log, DISCLOSURE.md at the end\n assets/ descriptor + reference-image records per asset\n prompts/ final prompt per video, versioned: r1_v3.md\n selects/scNN/ accepted take URLs + their prompts\n edit/ master/ cut records, export URLs, archive manifest\n```\n\n`docs/brief.md` is the **living project brief** — the one page a stranger (or a\nfresh session) reads to direct this film correctly: logline, the world in three\nsentences, the style prefix, who the characters are *as people*, the current\nproduction block, and the standing decisions with their reasons (\"hard cuts\nonly — the film is about abruptness\"). Update it whenever a decision would\nsurprise someone reading only the script; on resume, read it before you say\nwhere things stand. `film.json` holds the state; the brief holds the *intent*.\n\n## The project folder — one film, one tree\n\n**A film is a folder in the user's Drive, provisioned before anything is\ngenerated.** Everything the project makes is persisted into it as it is made, so\nnothing ever lives only on a CDN and the whole film is one folder to archive or\nhand off. This is the first thing that happens after the idea is agreed — not a\ncleanup step at the end:\n\n```\nPicsart Films/<film-slug>/\n Library/ locked asset sheets only — what the film is built from\n Assets/ every asset render and every iteration\n Shots/ takes, drafts, finals\n Exports/ masters\n```\n\n**Provision it in ONE call**, then never look it up again:\n\n```\npicsart_drive action=create_folder\n path: \"Picsart Films/<film-slug>\"\n children: [\"Library\", \"Assets\", \"Shots\", \"Exports\"]\n```\n\n`result.createdUid` is the project folder and `result.createdUids` maps each\nchild name to its uid — store all five in `film.json.drive` immediately. Missing\nfolders are created and existing ones reused, so re-running it after a resume\ncompletes a half-built tree instead of duplicating it. From then on\n`picsart_drive action=upload` goes straight into the right uid — never a\nbrowse-`list` first \"to find the folder\" (each Drive call renders a UI block; a\nsave should render one receipt, not two stacked browsers). `action=list` is for\nwhen the user actually wants to browse.\n\n**Never show the whole Drive (2026-09-08).** No `action=list` without a\n`folderUid`, anywhere in the process — the root listing is the user's entire\nDrive painted into the chat, and nothing in a film needs it. The provisioning\nreceipt renders the project folder itself with its four children (the tool\nre-lists the folder it created, never its parent — a project folder's parent\nIS the root, and that is how the whole Drive used to appear on day one); every\nlater Drive call renders the one folder the file landed in. Browsing is the\nuser's request, never a step.\n\n**Then pass the uid on EVERY generation.** `picsart_generate` and\n`picsart_job_status` both take `folderUid`, and it is the only thing that files\na render under its own film:\n\n```\npicsart_generate … folderUid: <film.json.drive.shotsUid> takes\npicsart_generate … folderUid: <film.json.drive.assetsUid> asset renders\n```\n\n**Without it the render lands in a shared `picsart-genai-mcp` folder at the\nDrive root**, where every generation from every project piles up together —\nthat default exists for generations with no project to belong to, and a film\ntake is not one of them. This is the one place \"provision once, never look it up\nagain\" does not mean \"never mention it again\": the uid is in `film.json`, so it\ncosts nothing to read, and a call that omits it saves to the wrong place\nsilently.\n\n**Video defaults to async, so the `folderUid` must be passed AGAIN to\n`picsart_job_status`.** The job carries no memory of it. A take generated with a\nfolder and collected without one lands at the root anyway, and since takes are\nvideo, that is the call that actually files most of a film.\n\n**Persistence is silent, with exactly one exception.** Saving is your job, not a\ntopic. Never narrate the plumbing: no CDN hostnames, no \"the result has no\n`drive` block\", no \"`saveToDrive` was set but I'm not assuming it persisted\", no\nlive-testing a path in front of the user. Do the save, verify it yourself, and\nmove on. If a file genuinely did not persist durably, the user hears one sentence\nabout the *consequence* — \"this link expires, so I'm keeping the prompt to\nre-make it\" — and never the mechanism. A save that worked is worth zero words.\n\n**The exception is the folder itself, said once.** Creating the project folder is\nnot plumbing — it is a new folder in *their* Drive, holding *their* film, and\nwhere the work lives is theirs to know. So the provisioning call's receipt is\nshown and left to speak for itself (\"Created \"Picsart Films/hold-it\" in your\nPicsart Drive. It has Library, Assets, Shots and Exports inside — everything\nthis project makes gets filed in them.\"), and if anything is added it is one\nplain line at most: they can open it any time, and nothing else will be said\nabout saving again. Never repeat it per asset, per take, or per stage.\n\n**Two destinations, and the difference is not durability — it is curation.**\nPersistence happens at *generation*: every render lands in `Assets/` or `Shots/`\nas it comes back — **because you passed `folderUid`**, which is what makes that\nsentence true rather than aspirational. Promotion happens at *lock*: `picsart_save_asset` with\n`status: \"locked\"` and the project uid files the sheet in `Library/`, which is\nthe index of what the film is actually built from. Earlier versions stay in\n`Assets/`; the library is not a backup, and nothing is ever moved out of the\nproject.\n\nOne exception to plain uploads: **asset sheets save via `picsart_save_asset`,\nnever a plain upload** — that call writes the registry the library views read; a\nsheet uploaded as a plain file is invisible to them (see `film-assets`).\n\nKeep the film state in **`film.json`** — written to the workspace when there is\na disk, and updated whole (never a diff) at every milestone: gate passed, asset\nlocked, batch generated, review round, picture lock, export.\n\n**Never print it in the conversation.** A screenful of fenced JSON is not a\nstatus report — it is your bookkeeping, and it buries the one line the user\nneeded under sixty they cannot act on. It is also not how anything gets shown:\nthe shot list is shown by opening the shot list board, an asset by opening its\nsheet, a cut by opening the scene editor. **A request to SEE something is a\nrequest to open its board, never to print its data** — including the second\ntime, when the board has already been open once this session.\n\n**And generated material arriving is itself the request.** A batch of takes\ncoming back, a sheet rendering, a retake completing — none of them waits to be\nasked about. The board opens in the turn the material lands, because the\nalternative is describing pixels the user could have been looking at, and a\ndescription is where a verdict gets quietly self-issued: *\"three of the four\ncheck out clean\"* is an approval nobody granted, written about frames nobody\nsaw. Say what you observed, open the board, and let the approval come from\nthem. Documented failure and the full anatomy in `film-scenes` step 6.\n\nThere are exactly two moments the state may appear as JSON in chat:\n\n- **On a surface with no disk, at the end of a working session** — once, so the\n next session can resume. Say what it is in one line (\"paste this back to pick\n this film up\") and never repeat it mid-flow.\n- **When the user asks for it** — \"show me the state\", \"give me the json\".\n\nMid-flow, \"where are we?\" is answered by the map: the stage list with its gates\nand the scene×assets matrix, as markdown tables, in the user's own vocabulary.\nEverything a fresh session needs to rebuild the concrete work — every render,\nevery locked sheet, every take — is in the project's Drive folder already, so\nwhat is lost without a state block is the narrative, not the film.\n\n```json\n{\n \"film\": \"cully-hill-boys\", \"phase\": \"C\", \"stage\": 6,\n \"drive\": { \"projectUid\": \"<Picsart Films/cully-hill-boys — provisioned before anything is generated>\",\n \"libraryUid\": \"<Library — locked asset sheets land here>\",\n \"assetsUid\": \"<Assets — every asset render and iteration>\",\n \"shotsUid\": \"<Shots — takes>\",\n \"exportsUid\": \"<Exports — masters>\" },\n \"target\": { \"duration\": 120, \"aspectRatio\": \"16:9\", \"finalResolution\": \"1080p\" },\n \"story\": { \"who\": \"Cal, who owes; Tobin, who holds the deed\", \"where\": \"the garage, closing time\",\n \"wants\": \"Cal: Tobin's signature. Tobin: to keep working\", \"against\": \"the buyer comes tomorrow\",\n \"happens\": [\"Cal accuses\", \"Tobin keeps working\", \"Tobin sets the wrench down\"],\n \"peak\": \"Tobin sets the wrench down\",\n \"ends\": \"the deed signed on the bench\",\n \"says\": \"the thing they nearly split over was never the point\",\n \"fixed\": [\"both keep the garage\"], \"genre\": \"drama\" },\n \"shoot\": { \"ceilingSec\": 30, \"editModel\": \"<the picked model's edit sibling>\",\n \"extendModel\": \"<the picked model's extend sibling>\" },\n \"runs\": [\n { \"id\": \"r1\", \"scenes\": [\"sc01\", \"sc02\"], \"shots\": [\"12A\", \"12B\", \"12C\", \"13A\"],\n \"genSec\": 28, \"join\": \"ends as Cal's hand closes on the wrench; r2 opens on the wrench from close in\",\n \"cutTimes\": [5, 13, 19], \"cutTimesActual\": [5.2, 12.8, 19.1],\n \"refs\": [\"https://…sc01_12A_start.jpg\",\n \"https://…view_cal_identity_v1.jpg\", \"https://…view_cal_wardrobe_v1.jpg\",\n \"https://…view_tobin_identity_v1.jpg\", \"https://…view_tobin_wardrobe_v1.jpg\",\n \"https://…plate_wrench_v1.jpg\",\n \"https://…sc01_scene.jpg\", \"https://…garage_day_map.jpg\", \"https://…sc01_wide.jpg\"],\n \"refRoles\": [\"START FRAME\", \"IDENTITY @cal\", \"WARDROBE @cal\", \"IDENTITY @tobin\", \"WARDROBE @tobin\",\n \"PROP the wrench\", \"SCENE PICTURE\", \"SPACE map\", \"SPACE wide\"],\n \"promptVersion\": \"v3\", \"takes\": 2, \"editPasses\": 1, \"extends\": 1,\n \"pieces\": [\n { \"url\": \"https://…run-r1-take2-edit1.mp4\", \"from\": 0, \"to\": 28, \"promptVersion\": \"v3\",\n \"call\": \"edit\", \"note\": \"cut 3: mug is white ceramic\" },\n { \"url\": \"https://…run-r1-extend1.mp4\", \"from\": 28, \"to\": 34, \"promptVersion\": \"v4\",\n \"call\": \"extend\", \"note\": \"13A holds two more seconds on Tobin\" }\n ],\n \"select\": { \"url\": \"https://…\", \"jobId\": \"job_…\" } }\n ],\n \"model\": { \"id\": \"<the video model the user picked on picsart_model_choice — a version id, recorded once>\",\n \"draftResolution\": \"480p\",\n \"pictures\": { \"people\": \"<live 4K-capable scene-image model id selected at Stage 4>\",\n \"map\": \"<newest GPT Image id>\", \"vintage\": \"<newest GPT Image id>\",\n \"modern\": \"<newest Nano Banana id>\" } },\n \"banDictionary\": { \"dark\": \"low key\", \"jolting\": \"rapid motion\" },\n \"gates\": { \"1\": \"passed\", \"2\": \"passed\", \"3\": \"passed\", \"4\": \"passed\",\n \"5\": \"passed\", \"6\": null },\n \"voicePolicy\": \"native\",\n \"blocked_on\": null, \"attempts\": {},\n \"bible\": { \"stylePrefix\": \"<verbatim — pasted into every prompt of this world>\",\n \"palette\": { \"dominant\": \"amber lamplight on old plaster\",\n \"secondary\": \"burnt-orange rust and oiled timber\",\n \"accent\": \"lime-green enamel\",\n \"accentBelongsTo\": \"the brothers' car\" },\n \"setup\": { \"genre\": \"drama\", \"tone\": \"auto\", \"palette\": \"auto\",\n \"scheme\": \"analogous\", \"hue\": \"amber\", \"hueDegrees\": 60,\n \"ratio\": \"169\", \"camera\": \"35mm\", \"lens\": \"ana\" } },\n \"assets\": [\n { \"tag\": \"@cal\", \"type\": \"character\", \"version\": \"v1\", \"status\": \"locked\",\n \"descriptor\": \"<verbatim — the sheet's prompt and this record; never pasted into a video prompt>\",\n \"portrait\": \"https://…portrait_cal_v1.jpg\", \"profile\": \"https://…profile_cal_v1.jpg\",\n \"sheet\": \"https://…sheet_cal_v1.jpg\",\n \"grid\": \"T-cut triptych at the model's top resolution: left column two stacked face panels, right tall full-body panel; a states row below\",\n \"panels\": [\"1 front face (top-left)\", \"2 side face (bottom-left)\", \"3 full body front (right tall)\",\n \"4 @cal_wet (states row)\"],\n \"views\": [\n { \"id\": \"identity\", \"source\": \"portrait, head to neck\", \"box\": [512, 0, 1024, 1536], \"delivered\": [1024, 1536],\n \"url\": \"https://…view_cal_identity_v1.jpg\", \"bytes\": 291204 },\n { \"id\": \"profile\", \"source\": \"profile, head to neck\", \"box\": [512, 0, 1024, 1536], \"delivered\": [1024, 1536],\n \"url\": \"https://…view_cal_profile_v1.jpg\", \"bytes\": 240310 },\n { \"id\": \"wardrobe\", \"panel\": \"3 full body front\", \"position\": \"right tall\",\n \"box\": [1024, 0, 1024, 2048], \"delivered\": [1024, 2048],\n \"url\": \"https://…view_cal_wardrobe_v1.jpg\", \"bytes\": 188440 },\n { \"id\": \"wet\", \"panel\": \"4 @cal_wet\", \"position\": \"states row\",\n \"box\": [0, 2048, 1024, 1024], \"delivered\": [1024, 1024],\n \"url\": \"https://…view_cal_wet_v1.jpg\", \"bytes\": 164880 }],\n \"variants\": [\"@cal_wet\"], \"scenes\": [\"sc01\", \"sc02\"],\n \"blocking\": { \"asOf\": \"sc01\",\n \"left\": \"on foot, frame-right, walking away from the bench\",\n \"exit\": \"frame-right\" },\n \"voice\": \"low, unhurried, faint valley accent\" },\n { \"tag\": \"@maya\", \"type\": \"character\", \"source\": \"user\", \"version\": \"v1\", \"status\": \"locked\",\n \"original\": \"https://…maya_portrait.jpg\",\n \"sheet\": \"https://…sheet_maya_v1.jpg\",\n \"grid\": \"2046×1024; 1 row × 3 equal cols\",\n \"panels\": [\"profile (leftmost)\", \"back (centre)\", \"@maya_coat_off (rightmost)\"],\n \"views\": [\n { \"id\": \"coat_off\", \"panel\": \"@maya_coat_off\", \"position\": \"panel 3 of 3, rightmost\",\n \"box\": [1364, 0, 682, 1024], \"url\": \"https://…view_maya_coatoff_v1.jpg\", \"bytes\": 64200 }],\n \"derived\": [{ \"url\": \"https://…maya_portrait_q85.jpg\", \"op\": \"jpeg q85 — run budget\" }],\n \"descriptor\": \"<minimal — written from the photo; costume only where a scene departs from it>\",\n \"scenes\": [\"sc02\"] },\n { \"tag\": \"@garage\", \"type\": \"location\", \"version\": \"v1\", \"status\": \"locked\",\n \"descriptor\": \"<verbatim — architecture, materials, the anchor object, one light, the palette line>\",\n \"views\": [\n { \"id\": \"map\", \"url\": \"https://…garage_day_map.jpg\", \"madeFrom\": \"sc01.pictures.wide\",\n \"sides\": { \"A\": \"shelving wall\", \"B\": \"roller door\", \"C\": \"bench wall\", \"D\": \"tarp wall\" } },\n { \"id\": \"wide\", \"url\": \"https://…sc01_wide.jpg\" }],\n \"scenes\": [\"sc01\", \"sc02\"] }\n ],\n \"scenes\": [\n { \"id\": \"sc01\", \"slug\": \"EXT. GARAGE — DAY\", \"status\": \"selects-complete\",\n \"locationMap\": \"<verbatim spatial map, pasted into every prompt of the scene — with the room map's sides: Side A the shelving wall, …>\",\n \"pictures\": { \"scene\": \"https://…sc01_scene.jpg\", \"wide\": \"https://…sc01_wide.jpg\",\n \"wrecked\": [{ \"angle\": \"WS\", \"fromRun\": \"r1\", \"at\": 27.4, \"url\": \"https://…r1_f27.4.png\" }] },\n \"shots\": [\n { \"id\": \"12A\", \"status\": \"final\", \"duration\": 5, \"run\": \"r1\",\n \"refs\": [1, 2, 3, 4, 5, 6, 7, 8, 9],\n \"design\": { \"move\": \"slow push-in\", \"size\": \"MS\", \"fov\": \"40°\",\n \"aperture\": \"f/4\", \"lighting\": \"window\",\n \"tailHold\": 1.2, \"slowReason\": \"emotional pause\",\n \"acting\": { \"@cal\": \"anger·2, hides fear, gaze on Tobin\" } },\n \"picture\": \"scene\",\n \"trim\": { \"in\": 0.6, \"out\": 5.1 }, \"trimSource\": \"user\" }\n ] }\n ],\n \"review\": { \"round\": 1, \"lastFeedbackSummary\": \"…\" },\n \"credits\": { \"spentEstimate\": 320, \"nextActionQuote\": 45 }\n}\n```\n\nWhy the awkward fields: `descriptor` and `stylePrefix` are **verbatim** because\nconsistency dies the day someone shortens them; `bible.setup` holds the console's\n**preset ids** (what the user chose) while `bible.palette` holds the *resolved*\ncolours — the scheme and hue written as colours plus what carries them\n(`PRESETS-LOOKS.md`, *Colour*), absent when the scheme is Auto — plus\n`accentBelongsTo` — the one thing that owns the accent, which the\nconsole does not ask and you decide while designing the assets; `trim` is an\n**edit-time fact recorded on the shot** — a keep-window in source seconds, and\nfor a shot inside a run the source is its **piece** — the run's file, or the\nregenerated segment that replaced part of it (`runs[].pieces`, 2026-09-04) — so\nthe window is in that file's seconds (12B's is `{in: 5.4, out: 12.6}` on the run\nfile, not `{0.2, 7.4}`): every shot of a piece is a clip in the scene sharing one\nurl with a different window, which is how the scene editor and Final Cut\nalready work — so a\ntrim does not lose itself between the scene editor and the assembly, and\n**`trimSource` says where the window came from** — and since 2026-09-04 the\nonly value written is `user` (they moved it in the scene editor): the automatic\nrough cut that produced `aligned` (word timings) and `planned` (beat clock) is\noff, because a run arrives already cut by the model. Read either of those on an\nold `film.json` as an automatic window from before the switch and leave it alone\nexactly as a `user` one is left alone — nothing automatic touches a window, and\nrecomputing over somebody's own edit is how a helpful default becomes an argument\n(`virtualMove` sat beside it until 2026-08-31, holding a free crop-path move\nadded at the edit; crop-path is gone and a camera change is a retake now, so the\nkey is retired — read one on an old `film.json` as \"nothing to apply\");\n`shoot.ceilingSec` is\nthe model's live max duration, read from `picsart_model_params` once at the model\npick so the run pass has a number to close runs against, and `shoot.editModel` /\n`shoot.extendModel` are the siblings an edit or an extend call goes to\n(`film-scenes` step 5); `runs[]` is top-level because a\nrun spans scenes (2026-09-03: as much of the film as fits the ceiling is one\nmulti-shot generation, and there are no per-shot exceptions) and it is what\nactually got bought: which shots, the planned `cutTimes` and the\n`cutTimesActual` the delivered take was split at (the model cuts near the plan,\nnot on it, and every trim downstream hangs off the real numbers), `refs` — the\nordered images the call carried in `imageUrls`, with `refRoles` saying what\nthe tagging header told the model each one is (a face, an outfit, `START FRAME`,\n`FRAME REFERENCE for shot N`, `SPACE` — the `startFrame` field stays empty and\nthe role travels in words), which every shot block numbers against, so a shot's\n`refs` are indices into it — one `promptVersion`, `takes`\nand `editPasses` per run, and one `select`; each shot points back with `run`,\nkeeps its composed `still` when one was requested (an animatic panel if one\nwas watched, and in `refs` its labelled `START FRAME` or `FRAME REFERENCE`); `locationMap` lives on the scene\nbecause it is written once and pasted into every prompt unchanged; `design` stores\npreset names, not prose, so a change is one field and the recompiled prompt differs\nin exactly one block. A shot over 8 s carries its reason in `design.durationReason`, one line.\n**`blocking` is the one piece of character state that\ncrosses a scene boundary** — where they were left and which side of frame they\nwent out — because `locationMap` fixes geography only *within* a scene and\nnothing else carries a heading out of one: a character who exits frame-right and\nenters the next scene frame-right has quietly broken the geography, and it is\nthe continuity error viewers feel without being able to name. Note what it is\nNOT: wardrobe, damage and wetness are **not** recorded here, because a state\nchange is a separate asset with its own tag (rule 2) and a field holding\n\"now soaked\" would compete with `@cal_wet` for the truth. `stressTest` is not required (2026-09-05): the save tool locks on the\nreview-board approval, and no row carries a battery — there is none in this\nflow. Statuses: assets\n`draft → review → locked`; shots\n`planned → drafting → iterating → final`; a shot regenerated after picture lock gets\na new `promptVersion`, never an edit in place. **`story` is the ten lines the user said yes to, and `docs/story.md` holds\nevery scene as what we see and hear** (2026-09-09,\n`film-development/references/DRAMATURGY.md`); `runs[]` is seeded by the Stage 1\nvideo plan (`id`, `scenes`, `genSec`, `join`) and filled in Stage 6; the scene\ncontracts of 2026-09-05 are gone. The shotlist board\nowns a card's action, line, tags, values and order; `design`, `refs`, `run` and\n`trim` stay on the shot by id (`references/WIDGET-FEEDBACK.md`).\n\n## Ten rules that never bend\n\n1. **Generate nothing for the film until its scene's assets are locked.** The\n scene×assets matrix — read off `film.json` — is the gate, and a hole is a\n stop.\n2. **One asset — one approved sheet; one reference image — one view.** Each\n character, location, and prop has a single approved sheet (descriptor + one\n image). The descriptor is the sheet's prompt and the asset's record; since\n 2026-09-09 it is **never pasted into a video prompt** — the pictures carry\n identity and the prompt names them by number\n (`film-scenes/references/PROMPT-BLOCKS.md`, the one home of the prompt's\n shape). A state\n change (wet, bloodied, new costume) is a **panel on that sheet with its own\n tag** (`@cal_wet`; since 2026-09-04 a panel, not a second sheet) — a tag,\n never a note in the shot prompt. What reaches the model is not the sheet:\n since 2026-09-07 each panel the film uses is **cut out as its own image**,\n and a run's `imageUrls` carries those views, one thing per image. A\n multi-panel picture with the right panel named in words is what put the\n wrong Alina in sc04a.\n3. **Edits are surgical.** One preset, one line, one block at a time. A rewritten\n prompt loses whatever was working.\n4. **Show promptly; inspect before use.** A finished image may be shown\n immediately while its visual check is performed with `picsart_view_image`.\n Explain any defect under it. Approval, reference use, cropping and dependent\n generations wait for the check to pass. For `gcdn.picsart.com` images, build\n a preview URL by preserving the path and existing query parameters and\n setting `to=min`, `r=1280`, `type=jpeg`; use `r=2048` to read face panels.\n Inspect the returned pixels, not the URL. If that host transformation is\n unavailable or still too large, export a small JPEG with\n `picsart_media_export` and inspect it. Previews are for viewing only:\n reference inputs, crops and start frames keep the full-resolution original.\n Video checks use extracted frames. A tool error means the check has not\n happened; use the documented fallback or explain the block.\n5. **Everything is logged, lightly.** One prompt per video, the take's link and\n the verdict. Without that a good shot cannot be reproduced.\n6. **A dropped call is never a failed generation.** \"Server isn't responding\" is\n a *response* failure: the render usually completed, charged, and (with\n `saveToDrive`) landed in Drive — a blind retry pays twice for an image\n already owned, and writes finished assets off as \"blocked\". So every charged\n `picsart_generate` in this pipeline — sheets, stills, video — passes\n `async: true` (a handle always exists to poll),\n and recovery from any drop is **check first, never resubmit**:\n `picsart_job_status` on the handle, or list recent Drive files for a sync\n call that slipped through.\n7. **Nobody is in a video without their picture (2026-09-09).** Every video\n call — a 30-second run, a five-second bridge, an insert, a one-shot reshoot,\n whatever the clip is called — carries in `imageUrls` the identity view and\n the wardrobe view of every person who appears in it (the two pictures the\n lock step cuts from the sheet, `film-assets`), and the scene's\n pictures. A person carried by words alone is drawn from the model's habits\n (a wasteland in words puts a long coat on her), so words-only is a failed\n take before it is sent. It follows that the `startFrame` field is never\n filled, on any clip: an exact frame shuts every reference out (verified\n 2026-08-26), and a frame shows only the people in it. The frame a video\n must open on is picture 1 in `imageUrls`, labelled `START FRAME` in the\n tagging header, beside the others. On 2026-09-08 a five-second bridge went\n out with scene 1's last frame in the `startFrame` field — one woman's face\n — and no picture of the other woman, who was in the shot but not in the\n frame; she came back in a long coat with two flasks, and the bridge put a\n wardrobe jump on both of its joins. One habit rides with this rule: every\n object is named in one place with one position — a flask *on her strap*\n and *in her hand* in the same prompt is two flasks. Three cases the rule\n settles (2026-09-10): **a person the user photographed** — the photograph\n is the identity view and, when it shows the outfit the story dresses them\n in, the wardrobe view too; when it does not, ask for one more photograph\n that does, and never generate a lookalike to fill the slot. **A state** —\n when a dependent run takes frames from an earlier run because a person\n changed (dusty, torn, wet), the frame that shows it becomes that person's\n state view and **replaces** their wardrobe view in the dependent run; a\n clean coat beside a torn one makes the model average them. **A prop** —\n every object the film gave a plate to rides beside the people as `IMAGE n —\n PROP: <name>` and is named by that number where it appears; a canteen\n described in words came back as a flat flask on 2026-09-08.\n\n8. **Default review carries the whole film\n (2026-09-09).** Every `picsart_scene_editor` call opens on a `scene` that\n plays every accepted take plus the new one, in playback order — the montage\n built by `../film-edit/references/ASSEMBLY.md`. A film with two videos and a\n third just delivered opens as a scene of **three**, the new one in its story\n position — never a scene holding only what was just generated. `videoUrl` is\n for a film that genuinely has one video. **Why:** the\n only question worth asking about a new take is whether it *joins* — the seam\n into it and the seam out of it are the thing that fails, and a clip played on\n its own hides exactly that. A take that looks right alone and jumps in the\n film is the normal case, not the rare one, and it was being approved because\n nobody saw the join. The rule that permitted this said *one board per\n dispatched batch*, and a batch of one clip is a board of one clip. Watching\n the whole film costs nothing: the scene is windows over the source clips, and\n the editor plays it in the browser — it never renders and it never exports. **Focused join diagnosis/comparison is\n the exception** (`../film-edit/references/JOIN-REPAIR.md`): review both sides\n of the boundary, including a proposed insert, as a labelled preview separate\n from the accepted cut. Never approve a bridge from watching it alone.\n9. **The join between two videos is an angle change; a copied frame is the\n exception and must be named (2026-09-09).** This is the default for planning\n new runs, not an automatic repair for existing clips. User-requested join\n repairs follow `../film-edit/references/JOIN-REPAIR.md`, including motivated\n inserts and reference choices based on inspected boundary states. Two videos meet the handbook's\n way — the camera changes, the action carries, no pixels copied — which is what\n lets every video of the film dispatch in parallel. Taking video A's last frame\n into video B is permitted **only** when a physical state exists in A's pixels\n and in none of the approved pictures (a torn coat, a wrecked room, dust on a\n face); before dispatch, that state is named out loud in one sentence, and if\n the sentence names nothing, there is no exception and the angle change carries\n the join. The `startFrame` *field* stays empty either way (rule 7) — a frame\n travels as picture 1 of `imageUrls`, labelled `START FRAME`. **Why:** a copied\n frame makes B wait for A, and it carries only the composition and the people\n visible in it — everyone else regenerates from words and comes back changed\n (the 2026-09-08 bridge). The angle change hides the seam without paying that.\n\n10. **No place is in a video without its picture, and the picture has to cover\n what the frame will show (2026-09-10).** Rule 7 protects the people; this is\n the same law for the place, because it was the half that had no rule. Every\n video and every edit set in a place carries that place's **map** — the\n geometry — and carries the **wide** as well whenever the framing opens up\n beyond a tight frame. The budget ladder may drop the wide only when the map\n is present *and* no cut pulls back; **the map is never dropped**, exactly as\n the identity and wardrobe views are never dropped. An edit inherits the\n run's reference list verbatim, so a hole in the run becomes a hole in the\n edit — and an edit that reveals more of the setting than the clip showed\n must **add** the map and the wide, because the run's list only anchors what\n was already visible. **Why:** a tight frame constrains a doorway and nothing\n else; ask for a pull-back and the skyline, the street and the far wall are\n drawn from the model's habits, so the place changes while every face stays\n right. On 2026-09-10 clip 3 went out with only a tight frame of its setting,\n its edit inherited the same list, the edit pulled back, and the revealed\n skyline was somewhere else.\n\n## When a tool or endpoint is down\n\nOutages happen (a connector drops i2i while t2i works, a widget tool is\nmissing from the session). They are **status, not decisions**: report the\noutage in one line, keep the conveyor moving on everything free and working\n(reviews, descriptors, checklists, planning), park the blocked items in\n`openDecisions` with a retry note, and retry quietly next turn. Do **not**\nturn an outage into a user choice when the pipeline's own rules already answer\nit — \"redraw the lead's variant from scratch, or wait for i2i?\" is not a\nquestion, because taking consistency debt on a face to dodge a temporary\noutage is exactly what the rules forbid. Offer a genuine workaround decision\nonly when both paths are actually defensible.\n\nThe formalization that kills thrash loops (a documented forensic case: 14\nretries, six silent minutes, a fabricated diagnosis):\n\n- **Running out of credits gets ZERO retries and opens the purchase page.** An\n explicit insufficient-credits or insufficient-balance response from any\n Picsart tool is not a transient failure. Do not resubmit it, switch models, or\n substitute another charged operation. Preserve `film.json`, its job handles,\n and the current scene or asset state, and set `blocked_on` to\n `Picsart credits for <the paused operation>`.\n\n Open **https://picsart.com/pricing/** in the user's default browser so they can\n subscribe or buy more credits. In a shell-enabled desktop host, use its native\n URL opener (`open` on macOS, `xdg-open` on Linux, or `Start-Process` on Windows),\n not a web fetch: the user needs the interactive, signed-in page. If the host\n cannot launch a browser, give them a clickable\n [Buy more Picsart credits](https://picsart.com/pricing/) link instead. Say which\n operation is paused and continue only after the user says their credits are\n available; never poll their balance or retry automatically.\n- **Every failure has either a NAMED substitute or none.** Named substitutes\n are sanctioned in the skills (t2i when i2i is down is NOT one for a lead's\n variant; the markdown fallback when a board tool is absent IS one; the\n by-ear path when transcription is absent IS one). If no substitute is\n named, there isn't one — do not invent a workaround chain to keep moving.\n- **Other critical-component failures retry ONCE, then stop and hand it back.** The tools\n the pipeline cannot run without (`picsart_generate`, the job status poll,\n the export) get one identical retry; a second failure is the user's news,\n in one plain line: what you tried to do, what blocked it, what you need.\n Never reword-and-refire, never sleep-and-loop, never diagnose beyond what\n the error actually says.\n- **A 401 gets ZERO retries, and it is almost never the user's login.**\n `User token was not provided or is invalid` is emitted in exactly one place —\n the MCP server's own auth guard — and despite its wording it does not check a\n token at all. It checks whether the request carries the `user-id` header the\n Picsart gateway is supposed to inject. So the call was refused *before it was\n sent*: nothing reached Picsart, nothing was charged, no job exists to recover,\n and retrying an identical payload can only produce an identical 401.\n\n Report it as what it is — **identity did not reach the server on that call** —\n and say plainly that it is a gateway or deployment problem rather than\n something they can fix by logging in. Three things NOT to say: that Picsart\n rejected it (it never saw it); that their session expired (there is no user\n token in this path — upstream calls are authorized by a server-side service\n token); and that visible credits prove anything (`picsart_credits` does not\n use that token at all — it reads an internal credit service with only\n `x-act-as-user-id`, which is exactly why it can keep working while every\n generation fails).\n\n When it happens, the one useful probe is a tool that needs identity but is not\n on the guard's list, such as `picsart_job_status`: it fails with a *different*\n message, `Missing 'user-id' header…`, which confirms the same root cause in\n plainer words and tells the user which layer to look at.\n- **Track it in `film.json`**: `blocked_on` names the ONE missing thing\n (or `none`), and `attempts` counts per tool+failure so the once-only retry\n is enforced by bookkeeping, not memory.\n\n## Model policy\n\nThe pipeline is model-agnostic; the current fit is checked, not remembered.\n**The video model is chosen in Stage 1, right after the story and before the\nvideo plan is written** (2026-09-03; it used to sit at the bible lock): the shot\nlist needs its duration ladder, the run pass needs its ceiling, the reference\nbudget is its `imageUrls` max, and the edit sibling is its repair path — nothing\ndownstream of the console can be planned model-blind. Read\n`picsart_model_catalog` (`mode: \"video\"`, **`purpose: \"generate\"`**) and\n`picsart_model_params` live. Pass `purpose` every\ntime: `mode` and `inputType` say which media go in and out, never what a model is\nFOR, so without it the shortlist arrives salted with upscalers, reframes,\nvideo-extend and preset-effect models. `edit` is for changing a clip you already\nhave; `utility` is upscale/reframe/lip-sync/preset — ask for it by name when that\nis genuinely the question. What a film needs from its one video model: **native\nmulti-shot** — a declared cut count and cut times honoured inside one generation,\nat the finish resolution, for the run lengths the shot list needs (since\n2026-09-03 the unit of generation is the run — as much of the film as fits the\nceiling, scenes included; `film-scenes` has the finding and the rules) — plus\n**reference images in** (`imageUrls`, enough slots for every view, plate and\napproved still the run uses — views, never sheets — each named by index and role in the prompt —\nthe start frame travels as one of them, labelled `START FRAME`, since the\n`startFrame` field would lock the references out), native audio with\nlip-sync for dialogue, and **edit and extend siblings** (`purpose: \"edit\"`, v2v\n— the edit one takes the clip + a change + the same references, the extend one\ntakes the clip + what happens next) so a fix or a longer shot never re-rolls the\nrun (`film-scenes` step 5). The multi-shot line is a hard filter, not a preference: a model that\ncaps resolution on long takes, or will not hold a cut list, is a model that\nshoots the film shot by shot, which is the measured-worse path. Check the\nceiling, the `imageUrls` max and the resolution enum in `picsart_model_params`\nbefore the board opens. Put the shortlist through `picsart_model_choice` and let the user pick —\nthe same habit throughout. One model for the whole film unless a\nworld-switch demands otherwise.\n\n**The choice board is the only model UI this pipeline opens, and it is short.**\nNever `picsart_list_models`: that widget is the catalogue shelf — every match,\nunranked — and opening a card there puts the user in front of a full parameter\nform with its own generate button, a second production flow running beside this\none and spending credits outside the plan. What the board carries instead is a\ndecision already made: **your recommendation plus the one or two alternatives\nwhose trade-off a director could genuinely prefer** (three cards is the target,\nfour the ceiling), exactly one badged `recommended`, each `bestFor` written as\nthe trade — \"one continuous 30s take, but tops out at 720p\" — not as a spec\nsheet. Narrowing eight candidates to three is your job, not theirs; a long board\nis the catalogue handed back with extra steps.\n\nThe same rule binds **image** models for asset work: the film's target format\nis a hard constraint. Filter the catalog by `supportedAspectRatios` containing\nthe film's aspect ratio *before* comparing anything else — never settle for a\nmodel whose ratio enum lacks the finish format and let it \"cap\" the stills at\nthe nearest ratio it has.\n\n**Image models are families, not ids (2026-09-10).** The docs name the family\nthe handbook uses for each job; the id is read off `picsart_model_catalog`\nonce, at Stage 4's first call, and recorded in `film.json.model.pictures` so\none film never changes model midway while the next film picks up a newer\nversion the day it lands (`references/PIPELINE.md`, Stage 4, *Image models\nare families*):\n\n- **`people`** — the 4K wide's model, selected from the live catalog and\n schema; Nano Banana is the current repo default. Seedream Pro is an\n alternate after identity failures, with any smaller crop disclosed;\n- **`map`** — the room map, and a prop's plate: the GPT Image family\n (`gpt-image-*`; where a version ships in tiers, the fast one);\n- **`vintage`** — the era tones' portrait and sheet: the GPT Image family (the\n premium tier where there is one — a face is cut from it);\n- **`modern`** — the modern path and every profile: Nano Banana (Google's\n `gemini-*-flash-image`, never the `-lite`);\n- a point fix on a sheet goes to an image **edit** model (`purpose: \"edit\"`,\n the newest that takes a mask); signs, letters and labels go to the model that\n can spell, as a separate task, never generated inside a shot.\n\nA model id written anywhere in these docs is evidence with a date, not the\nrule. One video model for the whole film still stands, chosen by the user on\n`picsart_model_choice` and recorded with its edit and extend siblings.\n\n## Stage details, checklists, and mistakes\n\n`references/PIPELINE.md` holds all 11 stage cards: goal, input, output, exit\nchecklist, and the documented common mistakes. Load it when opening a stage or\njudging a gate. `references/PRESETS-LOOKS.md` holds the film-setup vocabulary\n(genres, tones, the colour scheme and hue, the frame, the camera and the lens\nfamily) — lighting, focal length, aperture, palettes, shot length, camera\nmovement and framing are deliberately not setup decisions, because none of them\ncan honestly be decided once per film; they are per-shot calls (the palette\ntable lives on there as the shot's colour). Grain is\nnot a control either (2026-09-07): it is a property of the stock and rides in\nthe Camera preset's fragment. A **tone** is an era recipe (2026-09-07): it sets the\ncamera and lens to a named period of cinema and adds one clause of its own,\nwhile colour remains independent,\nso when the story names a period or a cinema tradition, pass its id as\n`suggested.selections.tone` and let the console expand it.\n`references/WIDGET-FEEDBACK.md` documents every film board's feedback payload.\n\n## Cost ledger\n\n| Free | Charged, no rollback |\n|---|---|\n| all planning, breakdown, prompts, presets, `picsart_model_catalog`, `picsart_model_params`, `picsart_preflight`, `picsart_media_validate_scene`, authoring and re-authoring the cut, **splitting a run at its cuts** (probe + bootstrap + validate; the scene editor plays the built scene, so it never exports), every board and console (`picsart_film_setup`, `picsart_shotlist_board`, `picsart_asset_review`, `picsart_scene_editor` — which renders nothing, `picsart_model_choice`), every `picsart_media_contact_sheet` and `picsart_media_export` (real GPU renders, so it costs time and leaves a file — **not credits**, 2026-09-07), and `picsart_final_cut` **including its render** — that one happens in the user's browser, so editing and exporting there are both free | every `picsart_generate` (images and video) |\n\n## Traps\n\n- Starting generation \"just for one scene\" before its assets pass the gate. That is\n the trap the whole pipeline exists to prevent.\n- Dispatching video on the forecast. A forecast is a plan; the purchase needs a\n preflight number in front of the user and a yes after it — per dispatch, at\n any pace (`film-scenes`).\n- Shortening a descriptor \"for brevity\" inside a prompt. Consistency dies right there.\n- Building a state variant as its own asset with its own approval. Since\n 2026-09-04 `@cal_wet` is a panel on @cal's sheet, and since 2026-09-07 a\n view cut from it and numbered in the references header like any other image — a separate\n *sheet* only when the change lasts most of the film.\n- Handing the video model a multi-panel sheet, or a composite the user\n supplied, with the wanted panel named in words. Cut it into views at lock.\n No pointer works: sc04a and sc04b ran the same correct prompt and returned\n two different women, one frame blending two panels into a face not in the\n cast. A prompt that names a panel is a dispatch error (`film-assets`).\n- Letting the registry go stale — two weeks later it lies, and consistency rests on\n one person's memory.\n- Losing the project to an expired CDN link because nothing was saved to Drive\n — or, for a locked asset, recording the export's URL instead of the one\n `picsart_save_asset` handed back: the save re-hosts, the export URL expires\n (`film-assets`, step 4).\n- Altering a user's own photo on the way in, or asking them to approve it on a\n board. Since 2026-09-04 the original is saved byte-for-byte (probe-matched)\n and is IMAGE 1 in every run; changed copies are derived files with the\n reason recorded, and a sheet is generated *from* the photo only when a card\n needs an angle it lacks (`film-assets`).\n"
}SHA-256 of public snapshot: 95d988c4f80c8ef55237e012c70772f343a96faaca9650170d938a922ea0f057