Vibecode Dazz Cam
track this build5 phases, 15 steps, beginner friendly0%A film look is a color pipeline: white balance shift, tone curve, channel bias, grain, vignette, bloom. Core Image gives you every one of those primitives on-device, and an agent can wire a live Metal-rendered viewfinder, a camera carousel, and full-resolution capture in one session - this entry's prompt reproduced the core loop as a working iPhone app on the first shot. What you cannot one-shot is a decade of taste: Dazz's dozens of stocks are tuned against thousands of reference photos, and the difference between a filter and a look people recognize is exactly that tuning.
You are building a lean indie version of Dazz Cam. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Dazz Cam · indie build A camera application with a live filtered viewfinder running at frame rate on the graphics processor, a carousel of film looks built from real transforms rather than overlays, full resolution capture applying the identical pipeline, and a develop mode that applies the same looks to photographs you already have. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 with SwiftUI and Metal | the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well | | Capture | AVFoundation with a video data output for preview and a photo output for capture | one session, two outputs, one filter pipeline applied to both | | Effects | A Metal shader pipeline parameterised by image dimensions | resolution-aware from the start, because that is the property that keeps preview and capture identical | | Colour | Work in a linear space and convert once at the end | film emulation is a colour transform and doing it in gamma space produces muddy results | | Storage | Save to the photo library with the original preserved when the user asks | an irreversible filter applied to the only copy is the complaint you will receive | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac with Xcode 16 or newer** · free if you own a Mac - Why: There is no supported way to build or sign an iPhone application without it. - Get it: Install Xcode from the Mac App Store or developer.apple.com. It is a large download. - Verify: xcodebuild -version prints 16 or higher - [ ] **A real iPhone** · free if you have one - Why: The simulator has no camera and no graphics processor characteristics worth measuring. Every claim in this build is a device claim. - Get it: Any recent iPhone with a developer profile installed. Free provisioning allows installing your own build for a limited period, which is enough to start. - Verify: A build runs on the device and shows a camera preview - [ ] **Comfort with shader programming, or willingness to learn it** · free - Why: The preview and capture pipeline is Metal, and doing this on the processor produces a slideshow rather than a viewfinder. - Get it: Work through a shader tutorial before Phase 1 if you have not written one. The first shader is the hard part; the rest is arithmetic. - Verify: You can write and run a shader that inverts colours on the preview - [ ] **Colour lookup tables you may redistribute** · free if you build your own, which is also the more interesting work - Why: Shipping a table extracted from another application or licensed for personal use only is a redistribution problem, and it is the most noticeable thing to copy. - Get it: Build your own from photographed colour references, or use tables explicitly licensed for redistribution. Record the source of each. - Verify: Every table has a recorded source and a licence permitting distribution - [ ] **Test scenes with hard cases** · free - Why: A look that flatters one scene is easy; the differences appear on a dark room, a bright window, and a face. Those three are where a filter reveals itself as an overlay. - Get it: Shoot a high contrast scene, a flat overcast one, a dark interior and a portrait. Keep them for comparing looks. - Verify: Four reference scenes are captured and used for every look ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. - dozens of hand-tuned film stocks refined over years of color work - the polished video modes - 8mm, camcorder wobble, stabilization - special-effect lenses like fisheye, prism, and starburst done well - ongoing filter drops that chase whatever look TikTok wants this month - one-tap install for friends who will never sideload anything If one of those is essential to you, that is the reason to keep paying for Dazz Cam, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Dazz Cam The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an iPhone camera that shoots through film emulations, with the capture matching what you framed to replace Dazz Cam. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The preview and the photograph must be the same image. The viewfinder runs on a small buffer at frame rate and the capture runs at full resolution, so any effect whose appearance depends on pixel dimensions will look different in the saved file than in the preview: grain becomes finer, vignette tighter, a light leak smaller. So every effect takes the image dimensions as an input and scales with them, and the check at the end of each phase is to compare a preview screenshot against the captured file at the same framing. Getting this wrong is the thing users notice immediately and cannot articulate, and it is far harder to retrofit than to build in. ### Stack (fixed, do not substitute) - Language: Swift 6 with SwiftUI and Metal · the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well - Capture: AVFoundation with a video data output for preview and a photo output for capture · one session, two outputs, one filter pipeline applied to both - Effects: A Metal shader pipeline parameterised by image dimensions · resolution-aware from the start, because that is the property that keeps preview and capture identical - Colour: Work in a linear space and convert once at the end · film emulation is a colour transform and doing it in gamma space produces muddy results - Storage: Save to the photo library with the original preserved when the user asks · an irreversible filter applied to the only copy is the complaint you will receive ### Data model (create this before Phase 1) - looks(id, name, ordinal, lut_key, grain_params_json, vignette_params_json, leak_params_json, date_stamp) - captures(id, look_id, taken_at, width, height, original_path, rendered_path, params_hash) - develops(id, source_asset_id, look_id, rendered_path, at) · applying a look to an existing photograph - settings(key, value) · selected look, whether to keep originals, date stamp format - The parameters hash on every capture is what lets a photograph be re-rendered later with the same look, which matters when a look is adjusted. ### Phase 1 · A viewfinder that runs at frame rate Build: An AVFoundation session with a video data output feeding a Metal view. Apply one simple colour transform in a shader. Measure the frame rate and the time spent per frame. Handle the session being interrupted by a call or another application. Trap: Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. Done when: The filtered preview holds a steady frame rate on a real device, measured rather than judged by eye. Per-frame processing time is well inside the frame budget. Memory does not grow while the preview runs for two minutes. An interruption from a phone call resumes the session cleanly. Do not build yet: No capture, looks or develop mode. ### Phase 2 · Capture that matches the preview Build: Capture at full resolution through the photo output and apply the same shader pipeline, passing the image dimensions so every effect scales. Compare a preview screenshot against the capture at the same framing and fix the differences. Trap: Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. Done when: A capture and a preview screenshot at the same framing match visually, checked at full size. Grain size relative to the frame is the same in both. Vignette falloff reaches the same relative distance in both. Capturing does not freeze the preview for longer than a moment. Do not build yet: No looks carousel or develop mode. ### Phase 3 · Looks that are colour transforms Build: Build looks from a colour lookup table plus parameterised grain, vignette and optional light leak. Work in a linear colour space and convert once at the end. Add a carousel for switching looks with the preview updating immediately. Trap: A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. Done when: Switching looks updates the preview within a frame or two. A look applied to a high contrast scene and a flat one produces visibly different results rather than the same tint. Blacks stay black rather than lifting, verified on a dark scene. Colour conversion happens once at the end of the chain. Do not build yet: No develop mode or date stamp yet. ### Phase 4 · The date stamp and the deliberately imperfect looks Build: A date stamp rendered in the right typeface and colour for the look, positioned relative to the frame. Add the artefacts that make a look convincing: chromatic aberration at the edges, a soft focus falloff, a scan line pattern where appropriate. Trap: A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. Done when: The date stamp appears at the same relative position and size in preview and capture. Its format is configurable and it can be disabled. Edge artefacts increase towards the frame edges rather than uniformly. A look with scan lines shows the same line count relative to the frame at both resolutions. Do not build yet: No develop mode yet. ### Phase 5 · Developing photographs you already have Build: Import from the photo library and apply a look through the identical pipeline. Save as a new asset rather than replacing the original. Record the look and parameters so it can be re-rendered. Trap: Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. Done when: A developed photograph matches a camera capture of the same scene with the same look. The original asset is unchanged, verified in the photo library. The look and parameters are recorded so the same photograph can be re-rendered after a look is adjusted. A very large photograph develops without exhausting memory. Do not build yet: Distribution comes last. ### Phase 6 · Permissions, distribution and honest looks Build: Request camera and photo library access with clear usage descriptions at the point of use. Sign and submit. Then confirm every lookup table and typeface you ship is one you created or are licensed to distribute. Trap: Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each. Done when: Permissions are requested at the point of use with descriptions explaining why. Every shipped lookup table and typeface has a recorded source and a licence permitting redistribution. The application submits and installs from a test build on a device that never had it. Denying photo library access still allows shooting and saving via the share sheet or a clear explanation. Do not build yet: Nothing after this. ### Out of scope (and why) - The dozens of hand-tuned film stocks behind the commercial application, which are years of colour work and are the actual product. - Video modes with stabilisation and camcorder motion, which are a substantially harder problem than stills and are much of what people buy this category for. - Special effect lenses such as fisheye, prism and starburst done convincingly, each of which is its own optical simulation. - The tuning that makes a look flattering across skin tones and lighting conditions, which needs far more test material than one person shoots. ### README must contain - That every effect is parameterised by image dimensions so preview and capture match - That colour work happens in a linear space with one conversion at the end - That looks are colour transforms rather than overlays, and how to tell the difference - That developing never modifies the original asset - The source and licence of every shipped lookup table and typeface ===== AGENTS.md ===== # Agent instructions · Dazz Cam indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift 6 with SwiftUI and Metal, AVFoundation with a video data output for preview and a photo output for capture, A Metal shader pipeline parameterised by image dimensions, Work in a linear space and convert once at the end, Save to the photo library with the original preserved when the user asks. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. ===== BUILD_PLAN.md ===== # Build plan · Dazz Cam A camera application with a live filtered viewfinder running at frame rate on the graphics processor, a carousel of film looks built from real transforms rather than overlays, full resolution capture applying the identical pipeline, and a develop mode that applies the same looks to photographs you already have. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A viewfinder that runs at frame rate The live preview shows a filtered image without dropping frames. ### Steps 1. Set up the session with a video data output Feeding a Metal view directly. No conversion through an image object per frame. Files: `Camera.swift` 2. Write one shader and measure A simple colour transform first. Instrument the per-frame time before adding anything. Files: `Shaders/Base.metal` 3. Handle interruptions A call, another camera application, backgrounding. All three end the session. Files: `Camera.swift` ### Done when - [ ] The filtered preview holds a steady frame rate on device - [ ] Per-frame time is inside the frame budget - [ ] Memory is stable over two minutes - [ ] A call interruption resumes cleanly ### Watch out - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. ## Phase 2 · Capture that matches the preview The saved photograph looks like what the viewfinder showed. ### Steps 1. Add the photo output and share the pipeline One shader chain applied to both paths. Two pipelines diverge immediately. Files: `Capture.swift` 2. Parameterise effects by dimensions Relative units throughout. This is the phase's whole point. Files: `Shaders/Effects.metal` 3. Compare preview and capture Same framing, side by side at full size. Fix every difference before moving on. ### Done when - [ ] Capture and preview match at the same framing - [ ] Grain is the same size relative to the frame in both - [ ] Vignette falloff matches relatively - [ ] Capturing does not freeze the preview ### Watch out - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. ## Phase 3 · Looks that are colour transforms Each film look is a real transform rather than a coloured overlay. ### Steps 1. Work in linear, convert once Undo the transfer function on input, apply, reapply at the end. Everything between is linear. Files: `Shaders/Colour.metal` 2. Apply looks as lookup tables A real colour transform. Overlays are the thing being avoided. Files: `Looks.swift` 3. Build the carousel Switching updates within a frame or two. A pause here makes browsing looks unpleasant. Files: `LookCarousel.swift` ### Done when - [ ] Switching looks updates within a frame or two - [ ] A look behaves differently on high contrast and flat scenes - [ ] Blacks stay black on a dark scene - [ ] Colour conversion happens once at the end ### Watch out - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. ## Phase 4 · The date stamp and the deliberately imperfect looks A camcorder or disposable look reads as that thing rather than as a filter. ### Steps 1. Render the stamp relative to the frame Position and size as fractions. The same rule as every other effect. Files: `Shaders/Stamp.metal` 2. Add edge-dependent artefacts Aberration and falloff increasing towards the edges, which is what real lenses do. Files: `Shaders/Lens.metal` 3. Check both resolutions Stamp and scan lines are the two people notice. Compare at full size. ### Done when - [ ] The date stamp matches relatively in preview and capture - [ ] The stamp format is configurable and can be disabled - [ ] Edge artefacts increase towards the edges - [ ] Scan line count is relatively the same at both resolutions ### Watch out - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. ## Phase 5 · Developing photographs you already have A look can be applied to an existing photograph with the same result. ### Steps 1. Import and apply the same pipeline Identical shader chain. A second path for develop mode would diverge from the camera. Files: `Develop.swift` 2. Always write a new asset Never in place. This is the rule that protects irreplaceable photographs. Files: `Library.swift` 3. Handle large images by tiling A full resolution photograph can exhaust memory on a phone. Tile with overlap if needed. Files: `Develop.swift` ### Done when - [ ] A developed photograph matches a camera capture of the same look - [ ] The original asset is unchanged - [ ] Look and parameters are recorded for re-rendering - [ ] A very large photograph develops without exhausting memory ### Watch out - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. ## Not in this build - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. ## After v1, if you want it - A half-frame or double exposure mode, which the pipeline already supports - Per-look strength, which is a single blend parameter and much requested - A contact sheet view of a shoot, which is how people actually pick a keeper ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frame rate the viewfinder holds. Measured on device rather than assumed. PREVIEW_TARGET_FPS=30 # Required. Effects are expressed relative to image dimensions. The alternative is the bug this build is built around. GRAIN_SCALE_MODE=relative # Required. Whether the unfiltered capture is saved alongside. True by default, because photographs are irreplaceable. KEEP_ORIGINALS=true # Required. Format for looks that carry a stamp. Configurable and disableable. DATE_STAMP_FORMAT=yy MM dd # Required. Colour lookup tables with their licence file alongside. LUT_DIR=Resources/LUTs
You are building a lean indie version of Dazz Cam. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Dazz Cam · indie build A camera application with a live filtered viewfinder running at frame rate on the graphics processor, a carousel of film looks built from real transforms rather than overlays, full resolution capture applying the identical pipeline, and a develop mode that applies the same looks to photographs you already have. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 with SwiftUI and Metal | the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well | | Capture | AVFoundation with a video data output for preview and a photo output for capture | one session, two outputs, one filter pipeline applied to both | | Effects | A Metal shader pipeline parameterised by image dimensions | resolution-aware from the start, because that is the property that keeps preview and capture identical | | Colour | Work in a linear space and convert once at the end | film emulation is a colour transform and doing it in gamma space produces muddy results | | Storage | Save to the photo library with the original preserved when the user asks | an irreversible filter applied to the only copy is the complaint you will receive | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac with Xcode 16 or newer** · free if you own a Mac - Why: There is no supported way to build or sign an iPhone application without it. - Get it: Install Xcode from the Mac App Store or developer.apple.com. It is a large download. - Verify: xcodebuild -version prints 16 or higher - [ ] **A real iPhone** · free if you have one - Why: The simulator has no camera and no graphics processor characteristics worth measuring. Every claim in this build is a device claim. - Get it: Any recent iPhone with a developer profile installed. Free provisioning allows installing your own build for a limited period, which is enough to start. - Verify: A build runs on the device and shows a camera preview - [ ] **Comfort with shader programming, or willingness to learn it** · free - Why: The preview and capture pipeline is Metal, and doing this on the processor produces a slideshow rather than a viewfinder. - Get it: Work through a shader tutorial before Phase 1 if you have not written one. The first shader is the hard part; the rest is arithmetic. - Verify: You can write and run a shader that inverts colours on the preview - [ ] **Colour lookup tables you may redistribute** · free if you build your own, which is also the more interesting work - Why: Shipping a table extracted from another application or licensed for personal use only is a redistribution problem, and it is the most noticeable thing to copy. - Get it: Build your own from photographed colour references, or use tables explicitly licensed for redistribution. Record the source of each. - Verify: Every table has a recorded source and a licence permitting distribution - [ ] **Test scenes with hard cases** · free - Why: A look that flatters one scene is easy; the differences appear on a dark room, a bright window, and a face. Those three are where a filter reveals itself as an overlay. - Get it: Shoot a high contrast scene, a flat overcast one, a dark interior and a portrait. Keep them for comparing looks. - Verify: Four reference scenes are captured and used for every look ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. - dozens of hand-tuned film stocks refined over years of color work - the polished video modes - 8mm, camcorder wobble, stabilization - special-effect lenses like fisheye, prism, and starburst done well - ongoing filter drops that chase whatever look TikTok wants this month - one-tap install for friends who will never sideload anything If one of those is essential to you, that is the reason to keep paying for Dazz Cam, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Dazz Cam The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an iPhone camera that shoots through film emulations, with the capture matching what you framed to replace Dazz Cam. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The preview and the photograph must be the same image. The viewfinder runs on a small buffer at frame rate and the capture runs at full resolution, so any effect whose appearance depends on pixel dimensions will look different in the saved file than in the preview: grain becomes finer, vignette tighter, a light leak smaller. So every effect takes the image dimensions as an input and scales with them, and the check at the end of each phase is to compare a preview screenshot against the captured file at the same framing. Getting this wrong is the thing users notice immediately and cannot articulate, and it is far harder to retrofit than to build in. ### Stack (fixed, do not substitute) - Language: Swift 6 with SwiftUI and Metal · the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well - Capture: AVFoundation with a video data output for preview and a photo output for capture · one session, two outputs, one filter pipeline applied to both - Effects: A Metal shader pipeline parameterised by image dimensions · resolution-aware from the start, because that is the property that keeps preview and capture identical - Colour: Work in a linear space and convert once at the end · film emulation is a colour transform and doing it in gamma space produces muddy results - Storage: Save to the photo library with the original preserved when the user asks · an irreversible filter applied to the only copy is the complaint you will receive ### Data model (create this before Phase 1) - looks(id, name, ordinal, lut_key, grain_params_json, vignette_params_json, leak_params_json, date_stamp) - captures(id, look_id, taken_at, width, height, original_path, rendered_path, params_hash) - develops(id, source_asset_id, look_id, rendered_path, at) · applying a look to an existing photograph - settings(key, value) · selected look, whether to keep originals, date stamp format - The parameters hash on every capture is what lets a photograph be re-rendered later with the same look, which matters when a look is adjusted. ### Phase 1 · A viewfinder that runs at frame rate Build: An AVFoundation session with a video data output feeding a Metal view. Apply one simple colour transform in a shader. Measure the frame rate and the time spent per frame. Handle the session being interrupted by a call or another application. Trap: Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. Done when: The filtered preview holds a steady frame rate on a real device, measured rather than judged by eye. Per-frame processing time is well inside the frame budget. Memory does not grow while the preview runs for two minutes. An interruption from a phone call resumes the session cleanly. Do not build yet: No capture, looks or develop mode. ### Phase 2 · Capture that matches the preview Build: Capture at full resolution through the photo output and apply the same shader pipeline, passing the image dimensions so every effect scales. Compare a preview screenshot against the capture at the same framing and fix the differences. Trap: Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. Done when: A capture and a preview screenshot at the same framing match visually, checked at full size. Grain size relative to the frame is the same in both. Vignette falloff reaches the same relative distance in both. Capturing does not freeze the preview for longer than a moment. Do not build yet: No looks carousel or develop mode. ### Phase 3 · Looks that are colour transforms Build: Build looks from a colour lookup table plus parameterised grain, vignette and optional light leak. Work in a linear colour space and convert once at the end. Add a carousel for switching looks with the preview updating immediately. Trap: A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. Done when: Switching looks updates the preview within a frame or two. A look applied to a high contrast scene and a flat one produces visibly different results rather than the same tint. Blacks stay black rather than lifting, verified on a dark scene. Colour conversion happens once at the end of the chain. Do not build yet: No develop mode or date stamp yet. ### Phase 4 · The date stamp and the deliberately imperfect looks Build: A date stamp rendered in the right typeface and colour for the look, positioned relative to the frame. Add the artefacts that make a look convincing: chromatic aberration at the edges, a soft focus falloff, a scan line pattern where appropriate. Trap: A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. Done when: The date stamp appears at the same relative position and size in preview and capture. Its format is configurable and it can be disabled. Edge artefacts increase towards the frame edges rather than uniformly. A look with scan lines shows the same line count relative to the frame at both resolutions. Do not build yet: No develop mode yet. ### Phase 5 · Developing photographs you already have Build: Import from the photo library and apply a look through the identical pipeline. Save as a new asset rather than replacing the original. Record the look and parameters so it can be re-rendered. Trap: Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. Done when: A developed photograph matches a camera capture of the same scene with the same look. The original asset is unchanged, verified in the photo library. The look and parameters are recorded so the same photograph can be re-rendered after a look is adjusted. A very large photograph develops without exhausting memory. Do not build yet: Distribution comes last. ### Phase 6 · Permissions, distribution and honest looks Build: Request camera and photo library access with clear usage descriptions at the point of use. Sign and submit. Then confirm every lookup table and typeface you ship is one you created or are licensed to distribute. Trap: Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each. Done when: Permissions are requested at the point of use with descriptions explaining why. Every shipped lookup table and typeface has a recorded source and a licence permitting redistribution. The application submits and installs from a test build on a device that never had it. Denying photo library access still allows shooting and saving via the share sheet or a clear explanation. Do not build yet: Nothing after this. ### Out of scope (and why) - The dozens of hand-tuned film stocks behind the commercial application, which are years of colour work and are the actual product. - Video modes with stabilisation and camcorder motion, which are a substantially harder problem than stills and are much of what people buy this category for. - Special effect lenses such as fisheye, prism and starburst done convincingly, each of which is its own optical simulation. - The tuning that makes a look flattering across skin tones and lighting conditions, which needs far more test material than one person shoots. ### README must contain - That every effect is parameterised by image dimensions so preview and capture match - That colour work happens in a linear space with one conversion at the end - That looks are colour transforms rather than overlays, and how to tell the difference - That developing never modifies the original asset - The source and licence of every shipped lookup table and typeface ===== AGENTS.md ===== # Agent instructions · Dazz Cam indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift 6 with SwiftUI and Metal, AVFoundation with a video data output for preview and a photo output for capture, A Metal shader pipeline parameterised by image dimensions, Work in a linear space and convert once at the end, Save to the photo library with the original preserved when the user asks. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. ===== BUILD_PLAN.md ===== # Build plan · Dazz Cam A camera application with a live filtered viewfinder running at frame rate on the graphics processor, a carousel of film looks built from real transforms rather than overlays, full resolution capture applying the identical pipeline, and a develop mode that applies the same looks to photographs you already have. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A viewfinder that runs at frame rate The live preview shows a filtered image without dropping frames. ### Steps 1. Set up the session with a video data output Feeding a Metal view directly. No conversion through an image object per frame. Files: `Camera.swift` 2. Write one shader and measure A simple colour transform first. Instrument the per-frame time before adding anything. Files: `Shaders/Base.metal` 3. Handle interruptions A call, another camera application, backgrounding. All three end the session. Files: `Camera.swift` ### Done when - [ ] The filtered preview holds a steady frame rate on device - [ ] Per-frame time is inside the frame budget - [ ] Memory is stable over two minutes - [ ] A call interruption resumes cleanly ### Watch out - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. ## Phase 2 · Capture that matches the preview The saved photograph looks like what the viewfinder showed. ### Steps 1. Add the photo output and share the pipeline One shader chain applied to both paths. Two pipelines diverge immediately. Files: `Capture.swift` 2. Parameterise effects by dimensions Relative units throughout. This is the phase's whole point. Files: `Shaders/Effects.metal` 3. Compare preview and capture Same framing, side by side at full size. Fix every difference before moving on. ### Done when - [ ] Capture and preview match at the same framing - [ ] Grain is the same size relative to the frame in both - [ ] Vignette falloff matches relatively - [ ] Capturing does not freeze the preview ### Watch out - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. ## Phase 3 · Looks that are colour transforms Each film look is a real transform rather than a coloured overlay. ### Steps 1. Work in linear, convert once Undo the transfer function on input, apply, reapply at the end. Everything between is linear. Files: `Shaders/Colour.metal` 2. Apply looks as lookup tables A real colour transform. Overlays are the thing being avoided. Files: `Looks.swift` 3. Build the carousel Switching updates within a frame or two. A pause here makes browsing looks unpleasant. Files: `LookCarousel.swift` ### Done when - [ ] Switching looks updates within a frame or two - [ ] A look behaves differently on high contrast and flat scenes - [ ] Blacks stay black on a dark scene - [ ] Colour conversion happens once at the end ### Watch out - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. ## Phase 4 · The date stamp and the deliberately imperfect looks A camcorder or disposable look reads as that thing rather than as a filter. ### Steps 1. Render the stamp relative to the frame Position and size as fractions. The same rule as every other effect. Files: `Shaders/Stamp.metal` 2. Add edge-dependent artefacts Aberration and falloff increasing towards the edges, which is what real lenses do. Files: `Shaders/Lens.metal` 3. Check both resolutions Stamp and scan lines are the two people notice. Compare at full size. ### Done when - [ ] The date stamp matches relatively in preview and capture - [ ] The stamp format is configurable and can be disabled - [ ] Edge artefacts increase towards the edges - [ ] Scan line count is relatively the same at both resolutions ### Watch out - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. ## Phase 5 · Developing photographs you already have A look can be applied to an existing photograph with the same result. ### Steps 1. Import and apply the same pipeline Identical shader chain. A second path for develop mode would diverge from the camera. Files: `Develop.swift` 2. Always write a new asset Never in place. This is the rule that protects irreplaceable photographs. Files: `Library.swift` 3. Handle large images by tiling A full resolution photograph can exhaust memory on a phone. Tile with overlap if needed. Files: `Develop.swift` ### Done when - [ ] A developed photograph matches a camera capture of the same look - [ ] The original asset is unchanged - [ ] Look and parameters are recorded for re-rendering - [ ] A very large photograph develops without exhausting memory ### Watch out - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. ## Not in this build - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. ## After v1, if you want it - A half-frame or double exposure mode, which the pipeline already supports - Per-look strength, which is a single blend parameter and much requested - A contact sheet view of a shoot, which is how people actually pick a keeper ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frame rate the viewfinder holds. Measured on device rather than assumed. PREVIEW_TARGET_FPS=30 # Required. Effects are expressed relative to image dimensions. The alternative is the bug this build is built around. GRAIN_SCALE_MODE=relative # Required. Whether the unfiltered capture is saved alongside. True by default, because photographs are irreplaceable. KEEP_ORIGINALS=true # Required. Format for looks that carry a stamp. Configurable and disableable. DATE_STAMP_FORMAT=yy MM dd # Required. Colour lookup tables with their licence file alongside. LUT_DIR=Resources/LUTs
You are building a production product version of Dazz Cam. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Dazz Cam · product brief ## Problem A film look is a color pipeline: white balance shift, tone curve, channel bias, grain, vignette, bloom. Core Image gives you every one of those primitives on-device, and an agent can wire a live Metal-rendered viewfinder, a camera carousel, and full-resolution capture in one session - this entry's prompt reproduced the core loop as a working iPhone app on the first shot. What you cannot one-shot is a decade of taste: Dazz's dozens of stocks are tuned against thousands of reference photos, and the difference between a filter and a look people recognize is exactly that tuning. ## Product outcome A camera whose saved photographs look exactly like the viewfinder promised, whose looks change colour the way film does rather than tinting the frame, and which never overwrites an original. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a Mac with Xcode and a physical iPhone - the simulator has no camera - a free Apple Developer account to sideload; 99 USD/year only if you want it on the App Store ## Explicit non-goals for v1 - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. - dozens of hand-tuned film stocks refined over years of color work - the polished video modes - 8mm, camcorder wobble, stabilization - special-effect lenses like fisheye, prism, and starburst done well - ongoing filter drops that chase whatever look TikTok wants this month - one-tap install for friends who will never sideload anything ## Success criteria - Preview and capture are confirmed to match at full size on all four reference scenes - The preview holds its target frame rate on the oldest device you support - Originals are confirmed unchanged after developing - Every lookup table and typeface licence permits redistribution - Denying photo library access leaves the camera usable ===== BRIEF.md ===== # Build brief · Dazz Cam The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an iPhone camera that shoots through film emulations, with the capture matching what you framed to replace Dazz Cam. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The preview and the photograph must be the same image. The viewfinder runs on a small buffer at frame rate and the capture runs at full resolution, so any effect whose appearance depends on pixel dimensions will look different in the saved file than in the preview: grain becomes finer, vignette tighter, a light leak smaller. So every effect takes the image dimensions as an input and scales with them, and the check at the end of each phase is to compare a preview screenshot against the captured file at the same framing. Getting this wrong is the thing users notice immediately and cannot articulate, and it is far harder to retrofit than to build in. ### Stack (fixed, do not substitute) - Language: Swift 6 with SwiftUI and Metal · the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well - Capture: AVFoundation with a video data output for preview and a photo output for capture · one session, two outputs, one filter pipeline applied to both - Effects: A Metal shader pipeline parameterised by image dimensions · resolution-aware from the start, because that is the property that keeps preview and capture identical - Colour: Work in a linear space and convert once at the end · film emulation is a colour transform and doing it in gamma space produces muddy results - Storage: Save to the photo library with the original preserved when the user asks · an irreversible filter applied to the only copy is the complaint you will receive ### Data model (create this before Phase 1) - looks(id, name, ordinal, lut_key, grain_params_json, vignette_params_json, leak_params_json, date_stamp) - captures(id, look_id, taken_at, width, height, original_path, rendered_path, params_hash) - develops(id, source_asset_id, look_id, rendered_path, at) · applying a look to an existing photograph - settings(key, value) · selected look, whether to keep originals, date stamp format - The parameters hash on every capture is what lets a photograph be re-rendered later with the same look, which matters when a look is adjusted. ### Phase 1 · A viewfinder that runs at frame rate Build: An AVFoundation session with a video data output feeding a Metal view. Apply one simple colour transform in a shader. Measure the frame rate and the time spent per frame. Handle the session being interrupted by a call or another application. Trap: Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. Done when: The filtered preview holds a steady frame rate on a real device, measured rather than judged by eye. Per-frame processing time is well inside the frame budget. Memory does not grow while the preview runs for two minutes. An interruption from a phone call resumes the session cleanly. Do not build yet: No capture, looks or develop mode. ### Phase 2 · Capture that matches the preview Build: Capture at full resolution through the photo output and apply the same shader pipeline, passing the image dimensions so every effect scales. Compare a preview screenshot against the capture at the same framing and fix the differences. Trap: Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. Done when: A capture and a preview screenshot at the same framing match visually, checked at full size. Grain size relative to the frame is the same in both. Vignette falloff reaches the same relative distance in both. Capturing does not freeze the preview for longer than a moment. Do not build yet: No looks carousel or develop mode. ### Phase 3 · Looks that are colour transforms Build: Build looks from a colour lookup table plus parameterised grain, vignette and optional light leak. Work in a linear colour space and convert once at the end. Add a carousel for switching looks with the preview updating immediately. Trap: A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. Done when: Switching looks updates the preview within a frame or two. A look applied to a high contrast scene and a flat one produces visibly different results rather than the same tint. Blacks stay black rather than lifting, verified on a dark scene. Colour conversion happens once at the end of the chain. Do not build yet: No develop mode or date stamp yet. ### Phase 4 · The date stamp and the deliberately imperfect looks Build: A date stamp rendered in the right typeface and colour for the look, positioned relative to the frame. Add the artefacts that make a look convincing: chromatic aberration at the edges, a soft focus falloff, a scan line pattern where appropriate. Trap: A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. Done when: The date stamp appears at the same relative position and size in preview and capture. Its format is configurable and it can be disabled. Edge artefacts increase towards the frame edges rather than uniformly. A look with scan lines shows the same line count relative to the frame at both resolutions. Do not build yet: No develop mode yet. ### Phase 5 · Developing photographs you already have Build: Import from the photo library and apply a look through the identical pipeline. Save as a new asset rather than replacing the original. Record the look and parameters so it can be re-rendered. Trap: Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. Done when: A developed photograph matches a camera capture of the same scene with the same look. The original asset is unchanged, verified in the photo library. The look and parameters are recorded so the same photograph can be re-rendered after a look is adjusted. A very large photograph develops without exhausting memory. Do not build yet: Distribution comes last. ### Phase 6 · Permissions, distribution and honest looks Build: Request camera and photo library access with clear usage descriptions at the point of use. Sign and submit. Then confirm every lookup table and typeface you ship is one you created or are licensed to distribute. Trap: Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each. Done when: Permissions are requested at the point of use with descriptions explaining why. Every shipped lookup table and typeface has a recorded source and a licence permitting redistribution. The application submits and installs from a test build on a device that never had it. Denying photo library access still allows shooting and saving via the share sheet or a clear explanation. Do not build yet: Nothing after this. ### Out of scope (and why) - The dozens of hand-tuned film stocks behind the commercial application, which are years of colour work and are the actual product. - Video modes with stabilisation and camcorder motion, which are a substantially harder problem than stills and are much of what people buy this category for. - Special effect lenses such as fisheye, prism and starburst done convincingly, each of which is its own optical simulation. - The tuning that makes a look flattering across skin tones and lighting conditions, which needs far more test material than one person shoots. ### README must contain - That every effect is parameterised by image dimensions so preview and capture match - That colour work happens in a linear space with one conversion at the end - That looks are colour transforms rather than overlays, and how to tell the difference - That developing never modifies the original asset - The source and licence of every shipped lookup table and typeface ===== ARCHITECTURE.md ===== # Architecture · Dazz Cam ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 with SwiftUI and Metal | the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well | | Capture | AVFoundation with a video data output for preview and a photo output for capture | one session, two outputs, one filter pipeline applied to both | | Effects | A Metal shader pipeline parameterised by image dimensions | resolution-aware from the start, because that is the property that keeps preview and capture identical | | Colour | Work in a linear space and convert once at the end | film emulation is a colour transform and doing it in gamma space produces muddy results | | Storage | Save to the photo library with the original preserved when the user asks | an irreversible filter applied to the only copy is the complaint you will receive | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Session | Capture, preview output and interruptions. | AVFoundation is the only route. Interruption handling is the part most likely to be forgotten. | | Pipeline | One shader chain for preview and capture. | Not swappable for two pipelines. A second path diverges from the first within a week. | | Looks | Colour transforms and effect parameters. | Tables are data, so a new look is a file. The relative-units rule applies to every effect in every look. | | Develop | Applying looks to existing photographs. | Any implementation using the same pipeline. Writing a new asset rather than in place is the rule that matters. | | Provenance | Where each look came from. | Never remove it. It is what makes the looks shippable. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PREVIEW_TARGET_FPS` · required · Frame rate the viewfinder holds. Measured on device rather than assumed. - `GRAIN_SCALE_MODE` · required · Effects are expressed relative to image dimensions. The alternative is the bug this build is built around. - `KEEP_ORIGINALS` · required · Whether the unfiltered capture is saved alongside. True by default, because photographs are irreplaceable. - `DATE_STAMP_FORMAT` · required · Format for looks that carry a stamp. Configurable and disableable. - `LUT_DIR` · required · Colour lookup tables with their licence file alongside. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Dazz Cam product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Swift 6 with SwiftUI and Metal, AVFoundation with a video data output for preview and a photo output for capture, A Metal shader pipeline parameterised by image dimensions, Work in a linear space and convert once at the end, Save to the photo library with the original preserved when the user asks. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. - Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each. ===== MILESTONES.md ===== # Delivery milestones · Dazz Cam Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A viewfinder that runs at frame rate The live preview shows a filtered image without dropping frames. ### Steps 1. Set up the session with a video data output Feeding a Metal view directly. No conversion through an image object per frame. Files: `Camera.swift` 2. Write one shader and measure A simple colour transform first. Instrument the per-frame time before adding anything. Files: `Shaders/Base.metal` 3. Handle interruptions A call, another camera application, backgrounding. All three end the session. Files: `Camera.swift` ### Done when - [ ] The filtered preview holds a steady frame rate on device - [ ] Per-frame time is inside the frame budget - [ ] Memory is stable over two minutes - [ ] A call interruption resumes cleanly ### Watch out - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. ## M2 · Capture that matches the preview The saved photograph looks like what the viewfinder showed. ### Steps 1. Add the photo output and share the pipeline One shader chain applied to both paths. Two pipelines diverge immediately. Files: `Capture.swift` 2. Parameterise effects by dimensions Relative units throughout. This is the phase's whole point. Files: `Shaders/Effects.metal` 3. Compare preview and capture Same framing, side by side at full size. Fix every difference before moving on. ### Done when - [ ] Capture and preview match at the same framing - [ ] Grain is the same size relative to the frame in both - [ ] Vignette falloff matches relatively - [ ] Capturing does not freeze the preview ### Watch out - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. ## M3 · Looks that are colour transforms Each film look is a real transform rather than a coloured overlay. ### Steps 1. Work in linear, convert once Undo the transfer function on input, apply, reapply at the end. Everything between is linear. Files: `Shaders/Colour.metal` 2. Apply looks as lookup tables A real colour transform. Overlays are the thing being avoided. Files: `Looks.swift` 3. Build the carousel Switching updates within a frame or two. A pause here makes browsing looks unpleasant. Files: `LookCarousel.swift` ### Done when - [ ] Switching looks updates within a frame or two - [ ] A look behaves differently on high contrast and flat scenes - [ ] Blacks stay black on a dark scene - [ ] Colour conversion happens once at the end ### Watch out - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. ## M4 · The date stamp and the deliberately imperfect looks A camcorder or disposable look reads as that thing rather than as a filter. ### Steps 1. Render the stamp relative to the frame Position and size as fractions. The same rule as every other effect. Files: `Shaders/Stamp.metal` 2. Add edge-dependent artefacts Aberration and falloff increasing towards the edges, which is what real lenses do. Files: `Shaders/Lens.metal` 3. Check both resolutions Stamp and scan lines are the two people notice. Compare at full size. ### Done when - [ ] The date stamp matches relatively in preview and capture - [ ] The stamp format is configurable and can be disabled - [ ] Edge artefacts increase towards the edges - [ ] Scan line count is relatively the same at both resolutions ### Watch out - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. ## M5 · Developing photographs you already have A look can be applied to an existing photograph with the same result. ### Steps 1. Import and apply the same pipeline Identical shader chain. A second path for develop mode would diverge from the camera. Files: `Develop.swift` 2. Always write a new asset Never in place. This is the rule that protects irreplaceable photographs. Files: `Library.swift` 3. Handle large images by tiling A full resolution photograph can exhaust memory on a phone. Tile with overlap if needed. Files: `Develop.swift` ### Done when - [ ] A developed photograph matches a camera capture of the same look - [ ] The original asset is unchanged - [ ] Look and parameters are recorded for re-rendering - [ ] A very large photograph develops without exhausting memory ### Watch out - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. ## M6 · Permissions, distribution and honest looks (production only) It installs from the store and the looks are yours to ship. ### Steps 1. Request permissions at point of use With usage descriptions that explain rather than restate the permission name. Files: `Info.plist` 2. Record provenance for every look Source and licence per lookup table and typeface. Checked rather than assumed. Files: `docs/looks.md` 3. Ship a test build to a device One that never had it. Installing on your development phone proves less than you think. ### Done when - [ ] Permissions are requested at point of use with explanatory descriptions - [ ] Every look and typeface has a recorded licence permitting redistribution - [ ] A test build installs on a fresh device - [ ] Denying library access leaves the camera usable ### Watch out - Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each. ===== OPERATIONS.md ===== # Operations · Dazz Cam ## Backup There is no server. What matters is that the user's originals are never modified, which makes the photo library their backup and makes the never-in-place rule an operational property rather than a preference. ## Restore Reinstalling loses settings and nothing else, since photographs live in the library. That is worth stating, because users assume otherwise. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Frame rate and memory during preview across device generations, and capture time. An older device dropping frames is the failure that gets reported as the application being broken. ## Incident checklist If saved photographs stop matching the preview, an effect has been written in pixel units, which is the recurring regression this whole build is arranged to prevent. The comparison check belongs in your release routine rather than in one phase. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Preview and capture are confirmed to match at full size on all four reference scenes - [ ] The preview holds its target frame rate on the oldest device you support - [ ] Originals are confirmed unchanged after developing - [ ] Every lookup table and typeface licence permits redistribution - [ ] Denying photo library access leaves the camera usable ## Launch constraint Do not market omitted Dazz Cam capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frame rate the viewfinder holds. Measured on device rather than assumed. PREVIEW_TARGET_FPS=30 # Required. Effects are expressed relative to image dimensions. The alternative is the bug this build is built around. GRAIN_SCALE_MODE=relative # Required. Whether the unfiltered capture is saved alongside. True by default, because photographs are irreplaceable. KEEP_ORIGINALS=true # Required. Format for looks that carry a stamp. Configurable and disableable. DATE_STAMP_FORMAT=yy MM dd # Required. Colour lookup tables with their licence file alongside. LUT_DIR=Resources/LUTs
# Dazz Cam · indie build A camera application with a live filtered viewfinder running at frame rate on the graphics processor, a carousel of film looks built from real transforms rather than overlays, full resolution capture applying the identical pipeline, and a develop mode that applies the same looks to photographs you already have. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 with SwiftUI and Metal | the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well | | Capture | AVFoundation with a video data output for preview and a photo output for capture | one session, two outputs, one filter pipeline applied to both | | Effects | A Metal shader pipeline parameterised by image dimensions | resolution-aware from the start, because that is the property that keeps preview and capture identical | | Colour | Work in a linear space and convert once at the end | film emulation is a colour transform and doing it in gamma space produces muddy results | | Storage | Save to the photo library with the original preserved when the user asks | an irreversible filter applied to the only copy is the complaint you will receive | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac with Xcode 16 or newer** · free if you own a Mac - Why: There is no supported way to build or sign an iPhone application without it. - Get it: Install Xcode from the Mac App Store or developer.apple.com. It is a large download. - Verify: xcodebuild -version prints 16 or higher - [ ] **A real iPhone** · free if you have one - Why: The simulator has no camera and no graphics processor characteristics worth measuring. Every claim in this build is a device claim. - Get it: Any recent iPhone with a developer profile installed. Free provisioning allows installing your own build for a limited period, which is enough to start. - Verify: A build runs on the device and shows a camera preview - [ ] **Comfort with shader programming, or willingness to learn it** · free - Why: The preview and capture pipeline is Metal, and doing this on the processor produces a slideshow rather than a viewfinder. - Get it: Work through a shader tutorial before Phase 1 if you have not written one. The first shader is the hard part; the rest is arithmetic. - Verify: You can write and run a shader that inverts colours on the preview - [ ] **Colour lookup tables you may redistribute** · free if you build your own, which is also the more interesting work - Why: Shipping a table extracted from another application or licensed for personal use only is a redistribution problem, and it is the most noticeable thing to copy. - Get it: Build your own from photographed colour references, or use tables explicitly licensed for redistribution. Record the source of each. - Verify: Every table has a recorded source and a licence permitting distribution - [ ] **Test scenes with hard cases** · free - Why: A look that flatters one scene is easy; the differences appear on a dark room, a bright window, and a face. Those three are where a filter reveals itself as an overlay. - Get it: Shoot a high contrast scene, a flat overcast one, a dark interior and a portrait. Keep them for comparing looks. - Verify: Four reference scenes are captured and used for every look ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. - dozens of hand-tuned film stocks refined over years of color work - the polished video modes - 8mm, camcorder wobble, stabilization - special-effect lenses like fisheye, prism, and starburst done well - ongoing filter drops that chase whatever look TikTok wants this month - one-tap install for friends who will never sideload anything If one of those is essential to you, that is the reason to keep paying for Dazz Cam, and the README should say so rather than pretend.
# Build brief · Dazz Cam The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an iPhone camera that shoots through film emulations, with the capture matching what you framed to replace Dazz Cam. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The preview and the photograph must be the same image. The viewfinder runs on a small buffer at frame rate and the capture runs at full resolution, so any effect whose appearance depends on pixel dimensions will look different in the saved file than in the preview: grain becomes finer, vignette tighter, a light leak smaller. So every effect takes the image dimensions as an input and scales with them, and the check at the end of each phase is to compare a preview screenshot against the captured file at the same framing. Getting this wrong is the thing users notice immediately and cannot articulate, and it is far harder to retrofit than to build in. ### Stack (fixed, do not substitute) - Language: Swift 6 with SwiftUI and Metal · the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well - Capture: AVFoundation with a video data output for preview and a photo output for capture · one session, two outputs, one filter pipeline applied to both - Effects: A Metal shader pipeline parameterised by image dimensions · resolution-aware from the start, because that is the property that keeps preview and capture identical - Colour: Work in a linear space and convert once at the end · film emulation is a colour transform and doing it in gamma space produces muddy results - Storage: Save to the photo library with the original preserved when the user asks · an irreversible filter applied to the only copy is the complaint you will receive ### Data model (create this before Phase 1) - looks(id, name, ordinal, lut_key, grain_params_json, vignette_params_json, leak_params_json, date_stamp) - captures(id, look_id, taken_at, width, height, original_path, rendered_path, params_hash) - develops(id, source_asset_id, look_id, rendered_path, at) · applying a look to an existing photograph - settings(key, value) · selected look, whether to keep originals, date stamp format - The parameters hash on every capture is what lets a photograph be re-rendered later with the same look, which matters when a look is adjusted. ### Phase 1 · A viewfinder that runs at frame rate Build: An AVFoundation session with a video data output feeding a Metal view. Apply one simple colour transform in a shader. Measure the frame rate and the time spent per frame. Handle the session being interrupted by a call or another application. Trap: Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. Done when: The filtered preview holds a steady frame rate on a real device, measured rather than judged by eye. Per-frame processing time is well inside the frame budget. Memory does not grow while the preview runs for two minutes. An interruption from a phone call resumes the session cleanly. Do not build yet: No capture, looks or develop mode. ### Phase 2 · Capture that matches the preview Build: Capture at full resolution through the photo output and apply the same shader pipeline, passing the image dimensions so every effect scales. Compare a preview screenshot against the capture at the same framing and fix the differences. Trap: Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. Done when: A capture and a preview screenshot at the same framing match visually, checked at full size. Grain size relative to the frame is the same in both. Vignette falloff reaches the same relative distance in both. Capturing does not freeze the preview for longer than a moment. Do not build yet: No looks carousel or develop mode. ### Phase 3 · Looks that are colour transforms Build: Build looks from a colour lookup table plus parameterised grain, vignette and optional light leak. Work in a linear colour space and convert once at the end. Add a carousel for switching looks with the preview updating immediately. Trap: A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. Done when: Switching looks updates the preview within a frame or two. A look applied to a high contrast scene and a flat one produces visibly different results rather than the same tint. Blacks stay black rather than lifting, verified on a dark scene. Colour conversion happens once at the end of the chain. Do not build yet: No develop mode or date stamp yet. ### Phase 4 · The date stamp and the deliberately imperfect looks Build: A date stamp rendered in the right typeface and colour for the look, positioned relative to the frame. Add the artefacts that make a look convincing: chromatic aberration at the edges, a soft focus falloff, a scan line pattern where appropriate. Trap: A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. Done when: The date stamp appears at the same relative position and size in preview and capture. Its format is configurable and it can be disabled. Edge artefacts increase towards the frame edges rather than uniformly. A look with scan lines shows the same line count relative to the frame at both resolutions. Do not build yet: No develop mode yet. ### Phase 5 · Developing photographs you already have Build: Import from the photo library and apply a look through the identical pipeline. Save as a new asset rather than replacing the original. Record the look and parameters so it can be re-rendered. Trap: Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. Done when: A developed photograph matches a camera capture of the same scene with the same look. The original asset is unchanged, verified in the photo library. The look and parameters are recorded so the same photograph can be re-rendered after a look is adjusted. A very large photograph develops without exhausting memory. Do not build yet: Distribution comes last. ### Phase 6 · Permissions, distribution and honest looks Build: Request camera and photo library access with clear usage descriptions at the point of use. Sign and submit. Then confirm every lookup table and typeface you ship is one you created or are licensed to distribute. Trap: Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each. Done when: Permissions are requested at the point of use with descriptions explaining why. Every shipped lookup table and typeface has a recorded source and a licence permitting redistribution. The application submits and installs from a test build on a device that never had it. Denying photo library access still allows shooting and saving via the share sheet or a clear explanation. Do not build yet: Nothing after this. ### Out of scope (and why) - The dozens of hand-tuned film stocks behind the commercial application, which are years of colour work and are the actual product. - Video modes with stabilisation and camcorder motion, which are a substantially harder problem than stills and are much of what people buy this category for. - Special effect lenses such as fisheye, prism and starburst done convincingly, each of which is its own optical simulation. - The tuning that makes a look flattering across skin tones and lighting conditions, which needs far more test material than one person shoots. ### README must contain - That every effect is parameterised by image dimensions so preview and capture match - That colour work happens in a linear space with one conversion at the end - That looks are colour transforms rather than overlays, and how to tell the difference - That developing never modifies the original asset - The source and licence of every shipped lookup table and typeface
# Agent instructions · Dazz Cam indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift 6 with SwiftUI and Metal, AVFoundation with a video data output for preview and a photo output for capture, A Metal shader pipeline parameterised by image dimensions, Work in a linear space and convert once at the end, Save to the photo library with the original preserved when the user asks. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find.
# Build plan · Dazz Cam A camera application with a live filtered viewfinder running at frame rate on the graphics processor, a carousel of film looks built from real transforms rather than overlays, full resolution capture applying the identical pipeline, and a develop mode that applies the same looks to photographs you already have. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A viewfinder that runs at frame rate The live preview shows a filtered image without dropping frames. ### Steps 1. Set up the session with a video data output Feeding a Metal view directly. No conversion through an image object per frame. Files: `Camera.swift` 2. Write one shader and measure A simple colour transform first. Instrument the per-frame time before adding anything. Files: `Shaders/Base.metal` 3. Handle interruptions A call, another camera application, backgrounding. All three end the session. Files: `Camera.swift` ### Done when - [ ] The filtered preview holds a steady frame rate on device - [ ] Per-frame time is inside the frame budget - [ ] Memory is stable over two minutes - [ ] A call interruption resumes cleanly ### Watch out - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. ## Phase 2 · Capture that matches the preview The saved photograph looks like what the viewfinder showed. ### Steps 1. Add the photo output and share the pipeline One shader chain applied to both paths. Two pipelines diverge immediately. Files: `Capture.swift` 2. Parameterise effects by dimensions Relative units throughout. This is the phase's whole point. Files: `Shaders/Effects.metal` 3. Compare preview and capture Same framing, side by side at full size. Fix every difference before moving on. ### Done when - [ ] Capture and preview match at the same framing - [ ] Grain is the same size relative to the frame in both - [ ] Vignette falloff matches relatively - [ ] Capturing does not freeze the preview ### Watch out - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. ## Phase 3 · Looks that are colour transforms Each film look is a real transform rather than a coloured overlay. ### Steps 1. Work in linear, convert once Undo the transfer function on input, apply, reapply at the end. Everything between is linear. Files: `Shaders/Colour.metal` 2. Apply looks as lookup tables A real colour transform. Overlays are the thing being avoided. Files: `Looks.swift` 3. Build the carousel Switching updates within a frame or two. A pause here makes browsing looks unpleasant. Files: `LookCarousel.swift` ### Done when - [ ] Switching looks updates within a frame or two - [ ] A look behaves differently on high contrast and flat scenes - [ ] Blacks stay black on a dark scene - [ ] Colour conversion happens once at the end ### Watch out - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. ## Phase 4 · The date stamp and the deliberately imperfect looks A camcorder or disposable look reads as that thing rather than as a filter. ### Steps 1. Render the stamp relative to the frame Position and size as fractions. The same rule as every other effect. Files: `Shaders/Stamp.metal` 2. Add edge-dependent artefacts Aberration and falloff increasing towards the edges, which is what real lenses do. Files: `Shaders/Lens.metal` 3. Check both resolutions Stamp and scan lines are the two people notice. Compare at full size. ### Done when - [ ] The date stamp matches relatively in preview and capture - [ ] The stamp format is configurable and can be disabled - [ ] Edge artefacts increase towards the edges - [ ] Scan line count is relatively the same at both resolutions ### Watch out - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. ## Phase 5 · Developing photographs you already have A look can be applied to an existing photograph with the same result. ### Steps 1. Import and apply the same pipeline Identical shader chain. A second path for develop mode would diverge from the camera. Files: `Develop.swift` 2. Always write a new asset Never in place. This is the rule that protects irreplaceable photographs. Files: `Library.swift` 3. Handle large images by tiling A full resolution photograph can exhaust memory on a phone. Tile with overlap if needed. Files: `Develop.swift` ### Done when - [ ] A developed photograph matches a camera capture of the same look - [ ] The original asset is unchanged - [ ] Look and parameters are recorded for re-rendering - [ ] A very large photograph develops without exhausting memory ### Watch out - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. ## Not in this build - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. ## After v1, if you want it - A half-frame or double exposure mode, which the pipeline already supports - Per-look strength, which is a single blend parameter and much requested - A contact sheet view of a shoot, which is how people actually pick a keeper
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frame rate the viewfinder holds. Measured on device rather than assumed. PREVIEW_TARGET_FPS=30 # Required. Effects are expressed relative to image dimensions. The alternative is the bug this build is built around. GRAIN_SCALE_MODE=relative # Required. Whether the unfiltered capture is saved alongside. True by default, because photographs are irreplaceable. KEEP_ORIGINALS=true # Required. Format for looks that carry a stamp. Configurable and disableable. DATE_STAMP_FORMAT=yy MM dd # Required. Colour lookup tables with their licence file alongside. LUT_DIR=Resources/LUTs
# Dazz Cam · product brief ## Problem A film look is a color pipeline: white balance shift, tone curve, channel bias, grain, vignette, bloom. Core Image gives you every one of those primitives on-device, and an agent can wire a live Metal-rendered viewfinder, a camera carousel, and full-resolution capture in one session - this entry's prompt reproduced the core loop as a working iPhone app on the first shot. What you cannot one-shot is a decade of taste: Dazz's dozens of stocks are tuned against thousands of reference photos, and the difference between a filter and a look people recognize is exactly that tuning. ## Product outcome A camera whose saved photographs look exactly like the viewfinder promised, whose looks change colour the way film does rather than tinting the frame, and which never overwrites an original. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a Mac with Xcode and a physical iPhone - the simulator has no camera - a free Apple Developer account to sideload; 99 USD/year only if you want it on the App Store ## Explicit non-goals for v1 - The hand-tuned film stocks of the commercial application. - Video modes with stabilisation and camcorder motion. - Convincing special effect lenses. - Tuning across skin tones and lighting conditions. - dozens of hand-tuned film stocks refined over years of color work - the polished video modes - 8mm, camcorder wobble, stabilization - special-effect lenses like fisheye, prism, and starburst done well - ongoing filter drops that chase whatever look TikTok wants this month - one-tap install for friends who will never sideload anything ## Success criteria - Preview and capture are confirmed to match at full size on all four reference scenes - The preview holds its target frame rate on the oldest device you support - Originals are confirmed unchanged after developing - Every lookup table and typeface licence permits redistribution - Denying photo library access leaves the camera usable
# Build brief · Dazz Cam The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an iPhone camera that shoots through film emulations, with the capture matching what you framed to replace Dazz Cam. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The preview and the photograph must be the same image. The viewfinder runs on a small buffer at frame rate and the capture runs at full resolution, so any effect whose appearance depends on pixel dimensions will look different in the saved file than in the preview: grain becomes finer, vignette tighter, a light leak smaller. So every effect takes the image dimensions as an input and scales with them, and the check at the end of each phase is to compare a preview screenshot against the captured file at the same framing. Getting this wrong is the thing users notice immediately and cannot articulate, and it is far harder to retrofit than to build in. ### Stack (fixed, do not substitute) - Language: Swift 6 with SwiftUI and Metal · the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well - Capture: AVFoundation with a video data output for preview and a photo output for capture · one session, two outputs, one filter pipeline applied to both - Effects: A Metal shader pipeline parameterised by image dimensions · resolution-aware from the start, because that is the property that keeps preview and capture identical - Colour: Work in a linear space and convert once at the end · film emulation is a colour transform and doing it in gamma space produces muddy results - Storage: Save to the photo library with the original preserved when the user asks · an irreversible filter applied to the only copy is the complaint you will receive ### Data model (create this before Phase 1) - looks(id, name, ordinal, lut_key, grain_params_json, vignette_params_json, leak_params_json, date_stamp) - captures(id, look_id, taken_at, width, height, original_path, rendered_path, params_hash) - develops(id, source_asset_id, look_id, rendered_path, at) · applying a look to an existing photograph - settings(key, value) · selected look, whether to keep originals, date stamp format - The parameters hash on every capture is what lets a photograph be re-rendered later with the same look, which matters when a look is adjusted. ### Phase 1 · A viewfinder that runs at frame rate Build: An AVFoundation session with a video data output feeding a Metal view. Apply one simple colour transform in a shader. Measure the frame rate and the time spent per frame. Handle the session being interrupted by a call or another application. Trap: Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. Done when: The filtered preview holds a steady frame rate on a real device, measured rather than judged by eye. Per-frame processing time is well inside the frame budget. Memory does not grow while the preview runs for two minutes. An interruption from a phone call resumes the session cleanly. Do not build yet: No capture, looks or develop mode. ### Phase 2 · Capture that matches the preview Build: Capture at full resolution through the photo output and apply the same shader pipeline, passing the image dimensions so every effect scales. Compare a preview screenshot against the capture at the same framing and fix the differences. Trap: Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. Done when: A capture and a preview screenshot at the same framing match visually, checked at full size. Grain size relative to the frame is the same in both. Vignette falloff reaches the same relative distance in both. Capturing does not freeze the preview for longer than a moment. Do not build yet: No looks carousel or develop mode. ### Phase 3 · Looks that are colour transforms Build: Build looks from a colour lookup table plus parameterised grain, vignette and optional light leak. Work in a linear colour space and convert once at the end. Add a carousel for switching looks with the preview updating immediately. Trap: A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. Done when: Switching looks updates the preview within a frame or two. A look applied to a high contrast scene and a flat one produces visibly different results rather than the same tint. Blacks stay black rather than lifting, verified on a dark scene. Colour conversion happens once at the end of the chain. Do not build yet: No develop mode or date stamp yet. ### Phase 4 · The date stamp and the deliberately imperfect looks Build: A date stamp rendered in the right typeface and colour for the look, positioned relative to the frame. Add the artefacts that make a look convincing: chromatic aberration at the edges, a soft focus falloff, a scan line pattern where appropriate. Trap: A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. Done when: The date stamp appears at the same relative position and size in preview and capture. Its format is configurable and it can be disabled. Edge artefacts increase towards the frame edges rather than uniformly. A look with scan lines shows the same line count relative to the frame at both resolutions. Do not build yet: No develop mode yet. ### Phase 5 · Developing photographs you already have Build: Import from the photo library and apply a look through the identical pipeline. Save as a new asset rather than replacing the original. Record the look and parameters so it can be re-rendered. Trap: Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. Done when: A developed photograph matches a camera capture of the same scene with the same look. The original asset is unchanged, verified in the photo library. The look and parameters are recorded so the same photograph can be re-rendered after a look is adjusted. A very large photograph develops without exhausting memory. Do not build yet: Distribution comes last. ### Phase 6 · Permissions, distribution and honest looks Build: Request camera and photo library access with clear usage descriptions at the point of use. Sign and submit. Then confirm every lookup table and typeface you ship is one you created or are licensed to distribute. Trap: Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each. Done when: Permissions are requested at the point of use with descriptions explaining why. Every shipped lookup table and typeface has a recorded source and a licence permitting redistribution. The application submits and installs from a test build on a device that never had it. Denying photo library access still allows shooting and saving via the share sheet or a clear explanation. Do not build yet: Nothing after this. ### Out of scope (and why) - The dozens of hand-tuned film stocks behind the commercial application, which are years of colour work and are the actual product. - Video modes with stabilisation and camcorder motion, which are a substantially harder problem than stills and are much of what people buy this category for. - Special effect lenses such as fisheye, prism and starburst done convincingly, each of which is its own optical simulation. - The tuning that makes a look flattering across skin tones and lighting conditions, which needs far more test material than one person shoots. ### README must contain - That every effect is parameterised by image dimensions so preview and capture match - That colour work happens in a linear space with one conversion at the end - That looks are colour transforms rather than overlays, and how to tell the difference - That developing never modifies the original asset - The source and licence of every shipped lookup table and typeface
# Architecture · Dazz Cam ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 with SwiftUI and Metal | the viewfinder must run on the graphics processor at frame rate, which nothing else on the platform does well | | Capture | AVFoundation with a video data output for preview and a photo output for capture | one session, two outputs, one filter pipeline applied to both | | Effects | A Metal shader pipeline parameterised by image dimensions | resolution-aware from the start, because that is the property that keeps preview and capture identical | | Colour | Work in a linear space and convert once at the end | film emulation is a colour transform and doing it in gamma space produces muddy results | | Storage | Save to the photo library with the original preserved when the user asks | an irreversible filter applied to the only copy is the complaint you will receive | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Session | Capture, preview output and interruptions. | AVFoundation is the only route. Interruption handling is the part most likely to be forgotten. | | Pipeline | One shader chain for preview and capture. | Not swappable for two pipelines. A second path diverges from the first within a week. | | Looks | Colour transforms and effect parameters. | Tables are data, so a new look is a file. The relative-units rule applies to every effect in every look. | | Develop | Applying looks to existing photographs. | Any implementation using the same pipeline. Writing a new asset rather than in place is the rule that matters. | | Provenance | Where each look came from. | Never remove it. It is what makes the looks shippable. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PREVIEW_TARGET_FPS` · required · Frame rate the viewfinder holds. Measured on device rather than assumed. - `GRAIN_SCALE_MODE` · required · Effects are expressed relative to image dimensions. The alternative is the bug this build is built around. - `KEEP_ORIGINALS` · required · Whether the unfiltered capture is saved alongside. True by default, because photographs are irreplaceable. - `DATE_STAMP_FORMAT` · required · Format for looks that carry a stamp. Configurable and disableable. - `LUT_DIR` · required · Colour lookup tables with their licence file alongside. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Dazz Cam product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Swift 6 with SwiftUI and Metal, AVFoundation with a video data output for preview and a photo output for capture, A Metal shader pipeline parameterised by image dimensions, Work in a linear space and convert once at the end, Save to the photo library with the original preserved when the user asks. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. - Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each.
# Delivery milestones · Dazz Cam Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A viewfinder that runs at frame rate The live preview shows a filtered image without dropping frames. ### Steps 1. Set up the session with a video data output Feeding a Metal view directly. No conversion through an image object per frame. Files: `Camera.swift` 2. Write one shader and measure A simple colour transform first. Instrument the per-frame time before adding anything. Files: `Shaders/Base.metal` 3. Handle interruptions A call, another camera application, backgrounding. All three end the session. Files: `Camera.swift` ### Done when - [ ] The filtered preview holds a steady frame rate on device - [ ] Per-frame time is inside the frame budget - [ ] Memory is stable over two minutes - [ ] A call interruption resumes cleanly ### Watch out - Filtering on the processor rather than the graphics processor drops the preview to a slideshow, and converting each frame through an image object allocates memory every frame which the collector then has to reclaim. Keep the pixel buffer on the graphics processor from capture to display and allocate nothing per frame. ## M2 · Capture that matches the preview The saved photograph looks like what the viewfinder showed. ### Steps 1. Add the photo output and share the pipeline One shader chain applied to both paths. Two pipelines diverge immediately. Files: `Capture.swift` 2. Parameterise effects by dimensions Relative units throughout. This is the phase's whole point. Files: `Shaders/Effects.metal` 3. Compare preview and capture Same framing, side by side at full size. Fix every difference before moving on. ### Done when - [ ] Capture and preview match at the same framing - [ ] Grain is the same size relative to the frame in both - [ ] Vignette falloff matches relatively - [ ] Capturing does not freeze the preview ### Watch out - Grain, vignette and light leaks written in pixel units look right on the small preview buffer and wrong at full resolution, and the difference is obvious as soon as someone looks at the file. Express every effect in units relative to the image dimensions and pass those dimensions into the shader. ## M3 · Looks that are colour transforms Each film look is a real transform rather than a coloured overlay. ### Steps 1. Work in linear, convert once Undo the transfer function on input, apply, reapply at the end. Everything between is linear. Files: `Shaders/Colour.metal` 2. Apply looks as lookup tables A real colour transform. Overlays are the thing being avoided. Files: `Looks.swift` 3. Build the carousel Switching updates within a frame or two. A pause here makes browsing looks unpleasant. Files: `LookCarousel.swift` ### Done when - [ ] Switching looks updates within a frame or two - [ ] A look behaves differently on high contrast and flat scenes - [ ] Blacks stay black on a dark scene - [ ] Colour conversion happens once at the end ### Watch out - A semi-transparent coloured overlay is what a filter looks like when it is not a colour transform: it lifts the blacks, flattens contrast and looks the same on every photograph. A lookup table applied in linear space changes colours the way film does, differently depending on what is in the frame. ## M4 · The date stamp and the deliberately imperfect looks A camcorder or disposable look reads as that thing rather than as a filter. ### Steps 1. Render the stamp relative to the frame Position and size as fractions. The same rule as every other effect. Files: `Shaders/Stamp.metal` 2. Add edge-dependent artefacts Aberration and falloff increasing towards the edges, which is what real lenses do. Files: `Shaders/Lens.metal` 3. Check both resolutions Stamp and scan lines are the two people notice. Compare at full size. ### Done when - [ ] The date stamp matches relatively in preview and capture - [ ] The stamp format is configurable and can be disabled - [ ] Edge artefacts increase towards the edges - [ ] Scan line count is relatively the same at both resolutions ### Watch out - A date stamp drawn at a fixed pixel size sits in a different place and a different proportion on the capture than on the preview, which is the same resolution problem as the effects and the one most likely to be forgotten because it feels like a text overlay rather than an effect. ## M5 · Developing photographs you already have A look can be applied to an existing photograph with the same result. ### Steps 1. Import and apply the same pipeline Identical shader chain. A second path for develop mode would diverge from the camera. Files: `Develop.swift` 2. Always write a new asset Never in place. This is the rule that protects irreplaceable photographs. Files: `Library.swift` 3. Handle large images by tiling A full resolution photograph can exhaust memory on a phone. Tile with overlap if needed. Files: `Develop.swift` ### Done when - [ ] A developed photograph matches a camera capture of the same look - [ ] The original asset is unchanged - [ ] Look and parameters are recorded for re-rendering - [ ] A very large photograph develops without exhausting memory ### Watch out - Overwriting the original when applying a filter destroys the only copy of a photograph, and it is the complaint that ends an application's reviews. Always write a new asset, and make keeping the original the default rather than a setting people have to find. ## M6 · Permissions, distribution and honest looks (production only) It installs from the store and the looks are yours to ship. ### Steps 1. Request permissions at point of use With usage descriptions that explain rather than restate the permission name. Files: `Info.plist` 2. Record provenance for every look Source and licence per lookup table and typeface. Checked rather than assumed. Files: `docs/looks.md` 3. Ship a test build to a device One that never had it. Installing on your development phone proves less than you think. ### Done when - [ ] Permissions are requested at point of use with explanatory descriptions - [ ] Every look and typeface has a recorded licence permitting redistribution - [ ] A test build installs on a fresh device - [ ] Denying library access leaves the camera usable ### Watch out - Colour lookup tables extracted from someone else's application or sold under a personal use licence are not yours to redistribute, and a look copied from a commercial product is the most likely thing to be noticed. Build your own from photographed reference or use tables licensed for redistribution, and record the provenance of each.
# Operations · Dazz Cam ## Backup There is no server. What matters is that the user's originals are never modified, which makes the photo library their backup and makes the never-in-place rule an operational property rather than a preference. ## Restore Reinstalling loses settings and nothing else, since photographs live in the library. That is worth stating, because users assume otherwise. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Frame rate and memory during preview across device generations, and capture time. An older device dropping frames is the failure that gets reported as the application being broken. ## Incident checklist If saved photographs stop matching the preview, an effect has been written in pixel units, which is the recurring regression this whole build is arranged to prevent. The comparison check belongs in your release routine rather than in one phase. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Preview and capture are confirmed to match at full size on all four reference scenes - [ ] The preview holds its target frame rate on the oldest device you support - [ ] Originals are confirmed unchanged after developing - [ ] Every lookup table and typeface licence permits redistribution - [ ] Denying photo library access leaves the camera usable ## Launch constraint Do not market omitted Dazz Cam capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frame rate the viewfinder holds. Measured on device rather than assumed. PREVIEW_TARGET_FPS=30 # Required. Effects are expressed relative to image dimensions. The alternative is the bug this build is built around. GRAIN_SCALE_MODE=relative # Required. Whether the unfiltered capture is saved alongside. True by default, because photographs are irreplaceable. KEEP_ORIGINALS=true # Required. Format for looks that carry a stamp. Configurable and disableable. DATE_STAMP_FORMAT=yy MM dd # Required. Colour lookup tables with their licence file alongside. LUT_DIR=Resources/LUTs
$ choose a build depth, inspect the files, then open the complete pack in your agent
Nobody pays Dazz for AVFoundation. They pay for a curated shelf of looks that are already dialed in - open the app, pick 135 SR, and the photo comes out feeling like 2002 without you touching a slider. That library, and the steady drip of new stocks tuned to current trends, is editorial work a one-shot build does not replicate.
xdozens of hand-tuned film stocks refined over years of color work
xthe polished video modes - 8mm, camcorder wobble, stabilization
xspecial-effect lenses like fisheye, prism, and starburst done well
xongoing filter drops that chase whatever look TikTok wants this month
xone-tap install for friends who will never sideload anything
Dazz Cam pricing
dazz pro$6.99/mo · monthly; a 19.99 USD one-time unlock is also offered · $83.88/yr
free tierThe free download includes a handful of cameras; most film stocks, effects, and video modes sit behind Dazz Pro or per-camera purchases.
verified 2026-08-11 · source ↗
Is Dazz Cam free?
The free download includes a handful of cameras; most film stocks, effects, and video modes sit behind Dazz Pro or per-camera purchases. Paid is Dazz Pro at $6.99/mo (checked 2026-08-11).
Vibecode Dazz Cam
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Dazz Cam replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Dazz Cam cost?
Dazz Cam costs about $6.99/month (Dazz Pro, checked 2026-08-11), which is $83.88 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Dazz Cam?
Honestly: dozens of hand-tuned film stocks refined over years of color work; the polished video modes - 8mm, camcorder wobble, stabilization; special-effect lenses like fisheye, prism, and starburst done well; ongoing filter drops that chase whatever look TikTok wants this month; one-tap install for friends who will never sideload anything. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Dazz Cam?
Yes: GPUImage3 (Metal-based real-time image and video filtering framework for iOS - the plumbing half of this app as a library.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.