chore(release): OpenScene v0.6.0 - #313
Merged
Merged
Conversation
* feat(export): read the finished file back before calling it done An export was reported as a success on the strength of the encoder exiting zero and a file existing. Nothing read the file. Every serious export defect found on a device this year got past exactly that: a cut truncated to 2.5s of 6.6s by a zero-sized title overlay, a layer dropped without a word, a clip that came out silent because its audio was never mapped. All of them exited zero and wrote a file. The plan already says what the file is supposed to be, so the file is measured and compared with it. The comparison is one shared rule, because the answer has to be the same on both surfaces; measuring is each platform's own business, because they have different tools for it. Desktop uses the ffprobe that ships beside the FFmpeg it already discovers; the phone uses MediaMetadataRetriever and AVAsset. A machine that cannot measure reports the export unchecked rather than failing it — being unable to inspect a file says nothing about the file. Rotation is applied before the shape is compared, or every upright phone export would be reported as the wrong shape. Sound is checked one way round only: a timeline with nothing to hear may still be written with a silent track, and there is nothing wrong with that. Measured, not assumed: against real files here, a good 1280x720 six-second export passes, a two-second truncation of it is caught, and a machine without ffprobe comes back unchecked. Closes #303 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(export): await the measurement before unwrapping it XCTUnwrap takes an autoclosure, and an await inside one does not compile. Caught by ios-export, which is the only thing on this project that compiles the Swift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…hat it did (#306) * feat(spend): a ceiling on what generation may cost, and a record of what it did Both surfaces already ask before a charge and price the job first. Neither had a memory or a limit. Once someone answers "always" on the phone, nothing bounds it again; on the desktop an agent asked for a ten-shot plan runs all ten. The bill is where either is discovered. So: a ledger of charges incurred, a month-to-date total, and a check that refuses the job that would cross the ceiling and says by how much. The rule is shared because a ceiling only one surface honours is not a ceiling — the phone guards its image and shot seams, the desktop guards all three of its job creators, and both record the charge as the request goes to the provider rather than when the job is queued. A job refused for a missing key cost nothing, and charging someone's ceiling for it would lock them out over nothing. A charge nobody can price cannot be kept under a ceiling, so with a limit set an unpriced model is refused unless the user takes it deliberately. That is the rule mediaGenerationPricing already follows for prices, applied to money. The agent can read the limit and not set it. An agent that can raise its own ceiling does not have one. Closes #305 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(spend): take the room out of the ceiling as it is checked Review caught the hole: checking the total and writing the charge were two moments, so two jobs asked for at once both read the same total, both passed, and both spent. The file made it worse — a read-modify-write drops whichever entry was written second. A limit that holds for one job at a time is not a limit. A job now reserves its room as the ceiling is checked, in one step, and settles it afterwards: kept when the request goes to a provider, handed back when it never does. Both states count against the ceiling, because an in-flight job is money on its way out. The desktop serializes every ledger operation through one queue; the phone reserves in a single synchronous read-check-write, which its one thread cannot interleave. A reservation nobody settled — an app killed mid-generation — is treated as a charge after six hours rather than held forever. The gap between reserving and dispatching is a key lookup; the gap between dispatching and settling is the whole generation, so a stale reservation was almost certainly already spent. That errs toward the user's wallet. Both races are pinned by tests that fail without the queue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The finished file is read back and compared with the cut. This is the other half: the reasons an export would not come out right, found before anything is rendered. Each of these was a separate late failure with its own wording. Two video clips over the same moment failed inside the Android renderer once the export was already running — and before that, quietly, as a layer missing from the file. A clip whose range ran past the end of its own file produced a frozen tail or a short segment and said nothing at all. A missing asset failed in staging on one surface and in URI resolution on the other. Stills were refused on the phone only, by a rule living in that surface's export bridge. They are one question — can this renderer make this cut — so it is asked in one place, and both surfaces refuse with the same sentence. What a renderer can do is reported by the renderer: Android says it does not composite layers, iOS says it does, and a build that was never asked says no, which is what every older one means. A clip past the end of its source is refused rather than trimmed. Silently shortening someone's clip is a different edit from the one they made. Closes #307 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Generating a sequence worked. Everything after the first take did not: the scenario prompt was reused verbatim for every shot with "shot 3 of 5" glued on, built inline inside one screen, so a shot could not carry its own description, a take that came back wrong could only be made again by paying for the whole plan, and saying what to change meant retyping the prompt from memory. The prompt is now composed by a shared rule, because three callers have to send the same thing — the phone, the desktop studio and the agent — and a shot asked for differently is a different shot. Each shot can carry a description on top of the scenario. Refining keeps the previous prompt whole and adds the change to it. That is deliberately arithmetic and not a model: asking a model to rewrite a prompt loses the parts nobody mentioned — the wardrobe, the lens, the location — which is exactly what continuity is made of. Notes accumulate in the order they were asked for, the same note twice is not asked twice, and a prompt grown past what this app will send is refused rather than truncated at the end, where the newest change lives. On the phone a redo runs that shot alone and stands the new take where the old one was, through a new shared rule that keeps the clip's place and length — refused rather than retimed when the take is too short to cover the shot, because silently shortening a cut is a different edit from the one anyone made. Closes #309 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Four features since v0.5.2, no breaking changes: the finished export is read back and compared with the cut (#304), generation has a monthly ceiling and a record of what it spent (#306), a cut that cannot be rendered here is refused before the render starts (#308), and a shot can carry its own description and be asked for again with a note (#310). The build number and version code go up as well as the version: both stores refuse an upload whose build number is not higher than the last one, even for a submission that was rejected. Store notes for both stores in both languages, with the same first line per language — a release described one way on one store and another elsewhere is two accounts of one build. The App Store files name no other platform, which is what Guideline 2.3.10 rejected a build for once. Closes #311 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
sjungwon03-ai
approved these changes
Aug 29, 2026
sjungwon03-ai
left a comment
Member
There was a problem hiding this comment.
The v0.6.0 release preparation aligns version metadata and release notes with the included features. Required release and CI checks passed on the reviewed head.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
devtomain, which is the release trigger.package.jsonsays 0.6.0 andv0.6.0is not tagged, so this publishes; a later push tomainwithout a version change would do nothing.What ships:
Also in this promotion: 0.6.0 in
package.jsonandmobile/app.json,ios.buildNumber9,android.versionCode8, and store notes for both stores in both languages.Merging tags
v0.6.0, packages the desktop app on all three runners, publishes the GitHub release, and hands the mobile builds to Google Play and App Store Connect.🤖 Generated with Claude Code