<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jamie Cole</title>
    <description>The latest articles on DEV Community by Jamie Cole (@jamiecoleai).</description>
    <link>https://dev.to/jamiecoleai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3443861%2F4e11423b-f62e-4127-86bc-d615c4728112.png</url>
      <title>DEV Community: Jamie Cole</title>
      <link>https://dev.to/jamiecoleai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jamiecoleai"/>
    <language>en</language>
    <item>
      <title>How to Make a String Art AI Video From a Photo: Prompts, Shot List, and Common Mistakes</title>
      <dc:creator>Jamie Cole</dc:creator>
      <pubDate>Sat, 29 Aug 2026 07:00:20 +0000</pubDate>
      <link>https://dev.to/jamiecoleai/how-to-make-a-string-art-ai-video-from-a-photo-prompts-shot-list-and-common-mistakes-1oca</link>
      <guid>https://dev.to/jamiecoleai/how-to-make-a-string-art-ai-video-from-a-photo-prompts-shot-list-and-common-mistakes-1oca</guid>
      <description>&lt;p&gt;A convincing string art AI video does not begin with a dramatic prompt. It begins with a clear decision: &lt;strong&gt;the viewer must believe they are watching a real object being built&lt;/strong&gt;. The thread needs to appear anchored to nails. The portrait needs to remain recognisable. The camera has to show enough of the board for the process to make sense, then move close enough for the final reveal to feel tactile.&lt;/p&gt;

&lt;p&gt;That is why this trend is harder than it looks. A vague request such as “turn this photo into string art” often produces floating yarn, painted lines, an unrelated face, or a magical transformation with no physical logic. The most successful results treat the generation like a short craft film: a reference photo becomes a finished nail-and-thread artwork through a sequence of simple, visible actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The practical rule is simple: lock the object first, then animate one believable action at a time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This guide explains how to choose a suitable photo, write prompts that preserve the person in the image, plan a compact 10-second sequence, diagnose failures, and turn a pleasing experiment into a repeatable workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quick Answer
&lt;/h2&gt;

&lt;p&gt;To make a string art AI video from a photo, start with a sharp, high-contrast image that has a clear subject. Decide what the finished physical piece will look like before generating anything: the board shape, nail layout, thread palette, background, lighting, and frame. Use the uploaded photo as the sole identity reference, then describe string art as a physical process rather than an abstract visual style. Generate a short vertical sequence with three visual beats: the prepared board, the thread-building process, and the finished portrait reveal. Finally, remove any result in which the thread floats, the face morphs, the nails disappear, or the artwork behaves like paint or a digital overlay.&lt;/p&gt;

&lt;p&gt;The central idea is not “make it more detailed.” It is &lt;strong&gt;make its materials and cause-and-effect more specific&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Good String Art Video Is Actually Showing
&lt;/h2&gt;

&lt;p&gt;String art is not just a portrait drawn with thin lines. In a believable version, darker areas are built from many overlapping strands stretched between fixed points. The nail heads create a visible boundary and rhythm. The empty spaces are part of the image. A clean result looks organised because the thread crossings form density without becoming a tangled web.&lt;/p&gt;

&lt;p&gt;For an AI video, that physical logic gives you five elements to control.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;What the viewer should understand&lt;/th&gt;
&lt;th&gt;Useful prompt language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reference identity&lt;/td&gt;
&lt;td&gt;The subject is the person, pet, or object from the original photo&lt;/td&gt;
&lt;td&gt;“Use the uploaded photo as the only identity reference”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support surface&lt;/td&gt;
&lt;td&gt;The artwork exists on one stable, tangible object&lt;/td&gt;
&lt;td&gt;“Circular matte-black wooden board on a workbench”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anchor system&lt;/td&gt;
&lt;td&gt;The thread has somewhere to begin and end&lt;/td&gt;
&lt;td&gt;“Evenly spaced brass nails around the perimeter”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Material behaviour&lt;/td&gt;
&lt;td&gt;The line is taut fibre, not ink, paint, or neon light&lt;/td&gt;
&lt;td&gt;“Thin cotton thread under visible tension, wrapped around nail heads”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human cause&lt;/td&gt;
&lt;td&gt;The work changes because someone makes it change&lt;/td&gt;
&lt;td&gt;“Two hands guide and wrap a single continuous strand”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If one of these elements is missing, the illusion weakens. A portrait may still look attractive, but it stops reading as string art and starts reading as a generic animated filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With the Right Photo
&lt;/h2&gt;

&lt;p&gt;A strong source photo makes every later decision easier. The ideal reference is not necessarily the most beautiful photo in your camera roll. It is the photo whose most important information can survive simplification into thread density, negative space, and silhouette.&lt;/p&gt;

&lt;p&gt;Choose a portrait where the face is relatively front-facing or in a clean three-quarter angle. The eyes should be visible, the jawline should separate from the background, and the lighting should make the nose, cheeks, hairline, or ears legible. For a pet, the ears, muzzle, and eye contrast should be clear. For a product, choose an object with a strong outer shape and minimal visual clutter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source-photo trait&lt;/th&gt;
&lt;th&gt;Why it helps&lt;/th&gt;
&lt;th&gt;What usually goes wrong without it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One clear subject&lt;/td&gt;
&lt;td&gt;Gives the model an obvious visual priority&lt;/td&gt;
&lt;td&gt;Extra people merge into the thread pattern or disappear&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distinct light and dark areas&lt;/td&gt;
&lt;td&gt;Lets thread density describe form&lt;/td&gt;
&lt;td&gt;The result becomes a flat grey mesh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean background&lt;/td&gt;
&lt;td&gt;Protects the silhouette and facial outline&lt;/td&gt;
&lt;td&gt;Background texture competes with the portrait&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visible eyes and facial landmarks&lt;/td&gt;
&lt;td&gt;Improves identity retention&lt;/td&gt;
&lt;td&gt;The final face becomes anonymous or distorted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Moderate crop&lt;/td&gt;
&lt;td&gt;Leaves room for the board and full design&lt;/td&gt;
&lt;td&gt;A tight crop cuts off hair, shoulders, or ears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-quality original&lt;/td&gt;
&lt;td&gt;Preserves edge information during transformation&lt;/td&gt;
&lt;td&gt;Compression noise turns into random lines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Avoid heavily filtered selfies, tiny screenshots, group photos, mirror reflections, strong backlighting, or portraits in which the subject is hidden by hands, hats, hair, or deep shadow. These can work as stylised images, but they are poor candidates for a first attempt because you cannot tell whether the prompt failed or the reference was simply too ambiguous.&lt;/p&gt;

&lt;p&gt;Before uploading, make one light preparation pass. Crop to the final composition, reduce a chaotic background if necessary, and slightly increase separation between the subject and the surrounding space. Do not over-sharpen the image. Fine artificial edge detail may be interpreted as extra thread paths, especially around hair and clothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide the Finished Object Before You Animate It
&lt;/h2&gt;

&lt;p&gt;A common mistake is asking the video model to invent the physical artwork while it is also inventing the camera movement, hands, sound, and facial likeness. That is too many creative decisions at once.&lt;/p&gt;

&lt;p&gt;Instead, write a one-sentence object specification. This is your &lt;strong&gt;consistency lock&lt;/strong&gt;: the description that should remain unchanged across every generation, clip, retry, and edit.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A circular charcoal-stained wooden board with evenly spaced small brass nails, a monochrome cream-cotton string portrait, placed flat on a dark walnut workbench under warm side lighting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence tells the model what must not change. Once you have it, you can vary only the action: placing nails, wrapping thread, tightening a strand, brushing loose fibres away, or revealing the finished piece.&lt;/p&gt;

&lt;p&gt;A concise object specification should answer the following questions in ordinary language:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Choose one clear answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Board&lt;/td&gt;
&lt;td&gt;Circular, square, or rectangular; pale wood, walnut, black, or painted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nail pattern&lt;/td&gt;
&lt;td&gt;Even perimeter, rectangular grid, silhouette outline, or geometric ring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread palette&lt;/td&gt;
&lt;td&gt;Monochrome, two colours, muted earth tones, or a controlled accent colour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portrait treatment&lt;/td&gt;
&lt;td&gt;Realistic, high-contrast, graphic, abstract, or geometric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workspace&lt;/td&gt;
&lt;td&gt;Craft table, studio bench, clean tabletop, or gallery wall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lighting&lt;/td&gt;
&lt;td&gt;Warm side light, soft daylight, dramatic spotlight, or gentle overhead light&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final framing&lt;/td&gt;
&lt;td&gt;Flat lay, angled close-up, slow push-in, or upright framed reveal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most first videos, monochrome thread on a dark or mid-tone board is the safest choice. It gives the portrait enough contrast and reduces colour flicker. If you want brighter content for social media, add one accent colour only after the basic version works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Prompts in Layers, Not as a Single Vague Sentence
&lt;/h2&gt;

&lt;p&gt;The best prompt is usually modular. It separates what must stay fixed from what is moving, how the camera behaves, and what must never happen. This makes it easier to diagnose a failed generation. If the face is right but the thread is wrong, you know which layer to revise.&lt;/p&gt;

&lt;p&gt;Use this five-part structure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt layer&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Questions to answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity lock&lt;/td&gt;
&lt;td&gt;Keeps the subject close to the uploaded photo&lt;/td&gt;
&lt;td&gt;Who or what must remain recognisable?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object lock&lt;/td&gt;
&lt;td&gt;Defines the physical artwork&lt;/td&gt;
&lt;td&gt;What board, nails, thread, palette, and lighting exist?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;td&gt;Gives the clip one visible event&lt;/td&gt;
&lt;td&gt;What are the hands doing in this moment?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camera and timing&lt;/td&gt;
&lt;td&gt;Prevents distracting movement&lt;/td&gt;
&lt;td&gt;Is the camera overhead, static, slowly pushing in, or orbiting?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constraints&lt;/td&gt;
&lt;td&gt;Blocks the most common visual failures&lt;/td&gt;
&lt;td&gt;What should not appear or change?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a reusable master prompt. Replace the bracketed details, but keep the underlying order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a 10-second vertical craft-film video using the uploaded photo as the only exact identity reference for the final portrait. The subject’s face, hairstyle, proportions, and expression remain recognisable throughout.

Scene: [board shape and wood finish] on a [workbench or surface], with [nail material] nails placed evenly around the outer edge. The artwork is made from [thread colour and material] thread, physically wrapped around real nail heads. [Lighting description].

Action: [one precise hand action]. Every new strand is pulled tight, anchored to a visible nail, and remains connected to the board. The artwork progresses through real thread placement, never through magical transformation.

Camera: [one camera instruction]. [Optional timing instruction]. Natural workshop ambience and soft thread tension sounds; no music.

Keep the original subject identity stable. No paint, printing, digital drawing, floating thread, disconnected thread, missing nails, changing board shape, face morphing, jump cuts, flicker, cartoon style, text, logos, or watermark.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is deliberately more specific than a stylish caption. You are asking the model to solve a continuity problem, a material problem, and an identity problem. Decorative adjectives should be the final addition, not the foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Ready-to-Use Prompt Directions
&lt;/h2&gt;

&lt;p&gt;The same structure can produce different visual personalities. The important thing is to change the art direction while preserving the physical rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Monochrome Portrait Workshop
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a 10-second vertical 9:16 video using the uploaded portrait as the only exact identity reference. A round charcoal-stained wood board rests on a dark walnut workbench. Small brass nails form a perfectly even ring around the edge. Thin ivory cotton thread creates a high-contrast, realistic portrait of the uploaded subject.

Two adult hands carefully wrap one continuous strand from nail to nail. The thread darkens the eyes, jawline, hair, and shadowed side of the face through dense overlapping lines. Each strand is visibly tight and anchored to real nails. Warm side lighting reveals wood grain, nail heads, and thread texture.

Start with a three-quarter overhead view, then make a slow, subtle push toward the finished portrait. Natural workshop sounds only. No music. No paint, printing, digital drawing, glowing lines, floating thread, missing nails, sudden morphing, face changes, jump cuts, flicker, text, logo, or watermark.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This direction is best for a portrait where likeness matters more than spectacle. It reads as patient craftsmanship, which makes the final reveal satisfying.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Warm Colour Accent Reveal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a short vertical string-art studio video from the uploaded photo, used as the only exact reference for the final subject. A square pale-oak board with evenly spaced black nails holds a realistic thread portrait. The design uses black thread for shadows and warm rust-red thread as a restrained accent in the background and clothing.

One pair of hands draws the red thread across the outer design first, then tightens black thread through the facial features. All thread wraps around visible nail heads and stays taut. The finished artwork must preserve the uploaded subject’s face and hairstyle.

Camera remains stable above the board for most of the clip, followed by a gentle angled close-up of the completed piece. Soft daylight, tactile wood and thread texture, quiet studio ambience. No paint strokes, no printed image, no floating yarn, no extra hands, no face morphing, no glitter effects, no text, and no watermark.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this when a monochrome result looks technically correct but feels visually quiet. The accent colour should support the portrait rather than compete with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pet Portrait With a Clean Final Reveal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a 10-second vertical craft video using the uploaded pet photo as the only exact reference. Build a circular nail-and-thread artwork on a natural birch board. Fine black cotton string forms the pet’s ears, eyes, muzzle, and outer silhouette, with a small muted gold thread accent around the border.

Show adult hands wrapping thread around evenly spaced brass nails. The thread must be continuous, taut, and physically attached to nail heads. Keep the pet’s markings, ear shape, and expression stable. Begin with the incomplete string pattern, then finish with a slow close-up reveal of the completed portrait resting on the workbench.

Soft window light, realistic craft materials, subtle thread tension and workshop ambience. No painted illustration, no animated sketch lines, no drifting string, no changing animal identity, no missing nails, no jump cuts, no text, and no watermark.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pet references benefit from a strong silhouette. If the animal has very fine fur or a busy setting, simplify the background before generation and prioritise the eyes and ear shape in the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a Shot List Instead of Asking for a Full Transformation All at Once
&lt;/h2&gt;

&lt;p&gt;A single prompt can produce a nice result, but a shot list is more reliable when you want a polished video. It prevents the model from trying to show every stage of the craft in one chaotic motion.&lt;/p&gt;

&lt;p&gt;For a 10-second vertical video, three shots are enough. Each shot has one job. Keep the board, nails, thread palette, lighting, subject identity, and workspace identical from shot to shot.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;What appears on screen&lt;/th&gt;
&lt;th&gt;Camera&lt;/th&gt;
&lt;th&gt;Action prompt focus&lt;/th&gt;
&lt;th&gt;Audio cue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.0–2.0 seconds&lt;/td&gt;
&lt;td&gt;Empty or lightly started board; source portrait idea is implied by the layout&lt;/td&gt;
&lt;td&gt;Still overhead or shallow three-quarter overhead&lt;/td&gt;
&lt;td&gt;A hand steadies the board or begins the first wrap&lt;/td&gt;
&lt;td&gt;Quiet tabletop contact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.0–7.0 seconds&lt;/td&gt;
&lt;td&gt;The string portrait visibly builds through key facial or silhouette areas&lt;/td&gt;
&lt;td&gt;Stable overhead with a very slow push&lt;/td&gt;
&lt;td&gt;One continuous strand is pulled, wrapped, and tightened on visible nails&lt;/td&gt;
&lt;td&gt;Thread tension, soft nail contact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.0–10.0 seconds&lt;/td&gt;
&lt;td&gt;Completed artwork fills more of the frame&lt;/td&gt;
&lt;td&gt;Slow angled close-up or controlled push-in&lt;/td&gt;
&lt;td&gt;Hands leave the frame; the final portrait is allowed to breathe&lt;/td&gt;
&lt;td&gt;Subtle room tone or a final thread tap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The point is not to show every real wrap. It is to make the transformation feel earned. A viewer accepts compression if each major change still has a visible physical cause.&lt;/p&gt;

&lt;p&gt;If your tool produces the entire 10-second sequence in one pass, describe the three beats in the order above and keep the camera instruction restrained. If it works better with shorter clips, generate each beat separately and assemble them in an editor. In that case, reuse the exact object specification at the beginning of every prompt. Copy it word for word.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Full 10-Second Shot Prompt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Make one continuous 10-second vertical 9:16 craft-film sequence using the uploaded photo as the only exact reference for the final thread portrait. Keep the same subject identity, circular dark wood board, evenly spaced brass nails, ivory cotton thread, walnut workbench, and warm side lighting for the entire video.

From 0 to 2 seconds, show an overhead view of the prepared board while an adult hand secures the first strand around a nail. From 2 to 7 seconds, show the hands wrapping and tightening continuous ivory thread across the board, creating the subject’s eyes, jawline, hair, and shadow shapes through dense connected lines. From 7 to 10 seconds, the hands move away and the camera slowly pushes in on the completed recognisable portrait.

Everything is physically made from taut thread anchored to visible nail heads. Natural workshop ambience only. No music. No painting, printing, digital sketching, floating threads, disconnected threads, missing nails, random knots, sudden face morphing, changing clothing, board changes, jump cuts, flicker, CGI glow, cartoon style, text, logo, or watermark.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Reliable Generation Workflow
&lt;/h2&gt;

&lt;p&gt;Your first output should be treated as a diagnostic, not a final. Generate short tests, identify the most important failure, and change only the instruction related to that failure. Rewriting the whole prompt after every attempt makes it difficult to learn what worked.&lt;/p&gt;

&lt;p&gt;Start by creating a clean still or a very short motion test. Confirm that the source image can become a believable string-art portrait on the intended board. Only then move to the full sequence. If the platform lets you choose a reference strength, image weight, motion strength, seed, or style intensity, use those settings conservatively. Strong identity guidance and modest motion are usually more useful than extreme animation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pass&lt;/th&gt;
&lt;th&gt;Objective&lt;/th&gt;
&lt;th&gt;What to keep fixed&lt;/th&gt;
&lt;th&gt;What to judge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Still-object test&lt;/td&gt;
&lt;td&gt;Establish board, nails, thread, and likeness&lt;/td&gt;
&lt;td&gt;Photo, board, palette, lighting&lt;/td&gt;
&lt;td&gt;Does it read as a real crafted object?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Hand-action test&lt;/td&gt;
&lt;td&gt;Verify physical wrapping&lt;/td&gt;
&lt;td&gt;All object details&lt;/td&gt;
&lt;td&gt;Are hands plausible and threads attached?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Short sequence test&lt;/td&gt;
&lt;td&gt;Test the build-to-reveal rhythm&lt;/td&gt;
&lt;td&gt;Identity and object lock&lt;/td&gt;
&lt;td&gt;Is the camera calm and continuity intact?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Final variation set&lt;/td&gt;
&lt;td&gt;Create several usable alternatives&lt;/td&gt;
&lt;td&gt;Best-performing prompt core&lt;/td&gt;
&lt;td&gt;Which ending has the strongest reveal?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Edit and export&lt;/td&gt;
&lt;td&gt;Improve pacing without changing the story&lt;/td&gt;
&lt;td&gt;Chosen clips&lt;/td&gt;
&lt;td&gt;Does every cut preserve the same artwork?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Generate more than one variation of the same well-built prompt. In short AI video, a small amount of randomness can affect hands, thread tension, facial detail, and the final crop. It is usually faster to choose the best result from a controlled set than to keep rewriting a fundamentally sound prompt.&lt;/p&gt;

&lt;p&gt;When one clip works, save its exact wording before experimenting. That working version becomes your baseline. Make variations by changing a single item at a time: the board finish, accent colour, final camera angle, or thread density. Do not change the subject identity, materials, and shot structure all at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing the Clips So the Illusion Holds
&lt;/h2&gt;

&lt;p&gt;A good generation can still feel artificial if the edit is too busy. String art videos benefit from patience. The viewer wants enough time to understand the material, notice the emerging face, and enjoy the final texture.&lt;/p&gt;

&lt;p&gt;Trim any opening where the board is unclear or the hands are malformed. Start on the first frame that establishes the physical setting. Keep the middle section long enough for at least two or three meaningful thread placements to be visible. Let the final reveal hold for a moment rather than cutting away immediately.&lt;/p&gt;

&lt;p&gt;If you combine separate clips, cut on a hand movement, a thread pull, or a camera push. These are natural disguises for small continuity differences. Avoid cutting from a half-finished face to a completely finished face with no shared visual bridge. A faster pace does not compensate for a broken causal story.&lt;/p&gt;

&lt;p&gt;Sound can help, but it should reinforce the illusion rather than fight it. Subtle thread tension, nail contact, and soft workbench ambience are generally more convincing than a dramatic soundtrack. If you add music later, keep it low enough that the visual craftsmanship remains the focus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes and How to Fix Them
&lt;/h2&gt;

&lt;p&gt;Most disappointing outputs fall into a small number of patterns. The fix is rarely “add more detail.” Usually, the fix is to clarify one physical rule.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Why it happens&lt;/th&gt;
&lt;th&gt;Prompt or workflow fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The portrait looks painted&lt;/td&gt;
&lt;td&gt;The model hears “string art” as a style, not a process&lt;/td&gt;
&lt;td&gt;Add “thin cotton thread physically wrapped around visible nail heads” and exclude paint, printing, and digital drawing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads float across the frame&lt;/td&gt;
&lt;td&gt;The model lacks an anchor system&lt;/td&gt;
&lt;td&gt;Specify evenly spaced nails, taut thread, continuous strands, and visible wrapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The face no longer resembles the photo&lt;/td&gt;
&lt;td&gt;Identity was under-specified or the source image was too complex&lt;/td&gt;
&lt;td&gt;State that the uploaded photo is the only exact identity reference; use a clearer crop; name the facial features that must remain stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nails are random or disappear&lt;/td&gt;
&lt;td&gt;The board geometry was left vague&lt;/td&gt;
&lt;td&gt;Lock board shape, nail material, even spacing, and perimeter placement before describing action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The artwork changes colour midway&lt;/td&gt;
&lt;td&gt;The palette was not locked&lt;/td&gt;
&lt;td&gt;Name the exact thread colours and repeat them in every clip prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The hands look malformed&lt;/td&gt;
&lt;td&gt;Too much action happens in one shot&lt;/td&gt;
&lt;td&gt;Ask for one simple task: wrap, pull tight, secure, or withdraw; avoid elaborate finger choreography&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The video jumps from blank board to final portrait&lt;/td&gt;
&lt;td&gt;The prompt describes a transformation, not a visible process&lt;/td&gt;
&lt;td&gt;Use a three-beat shot list and require hands to create each major change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The thread is too dense to read&lt;/td&gt;
&lt;td&gt;The prompt values complexity over portrait hierarchy&lt;/td&gt;
&lt;td&gt;Ask for clear negative space, defined eyes and jawline, controlled line density, and a readable silhouette&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hair becomes a tangled mess&lt;/td&gt;
&lt;td&gt;Fine detail is interpreted as many random paths&lt;/td&gt;
&lt;td&gt;Simplify the source image; reduce line density; ask for controlled hair contours rather than individual strands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The camera spins around the board&lt;/td&gt;
&lt;td&gt;“Cinematic” was interpreted as aggressive motion&lt;/td&gt;
&lt;td&gt;Replace generic cinematic language with “stable overhead view” or “slow subtle push-in”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The final board keeps changing&lt;/td&gt;
&lt;td&gt;Multiple visual decisions are being regenerated every second&lt;/td&gt;
&lt;td&gt;Put the full object specification at the top of every prompt and keep it identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text or a watermark appears&lt;/td&gt;
&lt;td&gt;The model defaults to social-video decoration&lt;/td&gt;
&lt;td&gt;State “no text, logo, caption, or watermark” in the constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Do Not Overload the Negative Prompt
&lt;/h2&gt;

&lt;p&gt;Negative instructions are useful, but a giant list can become a crutch. The model still needs a clear positive picture of what it should create. Begin with the physical scene and action. Then add only the negative terms that protect the most important failures.&lt;/p&gt;

&lt;p&gt;For this format, the highest-value constraints are usually the following: no paint or printing; no floating or disconnected thread; no missing nails; no face morphing; no jump cuts or flicker; no text or watermark. Add more only when a specific model repeatedly produces a particular artifact.&lt;/p&gt;

&lt;p&gt;A practical way to think about constraints is this: &lt;strong&gt;they should protect continuity, material realism, identity, and cleanliness&lt;/strong&gt;. They should not become a long list of unrelated aesthetic objections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refine the Prompt Based on the Failure You See
&lt;/h2&gt;

&lt;p&gt;When a result is close but not usable, identify the failure category before rewriting. The table below gives targeted repair language.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you see this failure&lt;/th&gt;
&lt;th&gt;Add or replace with this wording&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weak facial likeness&lt;/td&gt;
&lt;td&gt;“Preserve the uploaded subject’s facial proportions, eye placement, hairstyle, and expression; the final portrait must be immediately recognisable.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread resembles marker lines&lt;/td&gt;
&lt;td&gt;“Matte cotton fibre with visible thickness and slight surface texture, tightly wrapped around metal nail heads.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Board feels digital&lt;/td&gt;
&lt;td&gt;“Real wood grain, natural small imperfections, tactile nail heads, soft shadows cast by raised thread.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process feels magical&lt;/td&gt;
&lt;td&gt;“Each new line is placed by a visible hand, pulled taut, and secured around a visible nail before the next line appears.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final reveal lacks impact&lt;/td&gt;
&lt;td&gt;“After the hands leave the frame, hold the completed portrait for a slow close-up, revealing the layered thread texture and recognisable face.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video feels too busy&lt;/td&gt;
&lt;td&gt;“One steady camera movement only: a slow two-step push-in. No orbit, spin, whip pan, or fast zoom.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Colour flickers&lt;/td&gt;
&lt;td&gt;“Use only ivory cotton thread and brass nails throughout; do not introduce new colours or materials.”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This approach is more efficient than adding fashionable words such as “hyper-detailed,” “epic,” or “ultra-cinematic.” Those terms may increase visual intensity, but they do not solve a missing nail, drifting thread, or changing face.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Final Quality Check Before You Post
&lt;/h2&gt;

&lt;p&gt;Watch the video once with the sound off. If a viewer can understand the board, nails, thread, subject, and final reveal without audio, the visual story is carrying its share of the work. Then watch it again with audio. The sound should make the process feel more tactile, not distract from it.&lt;/p&gt;

&lt;p&gt;Use this final checklist before exporting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pass condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subject identity&lt;/td&gt;
&lt;td&gt;The finished string portrait clearly resembles the uploaded photo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical logic&lt;/td&gt;
&lt;td&gt;Every visible strand has a plausible connection to a nail or existing thread path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Board continuity&lt;/td&gt;
&lt;td&gt;Board shape, wood finish, nail pattern, and palette stay stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hand behaviour&lt;/td&gt;
&lt;td&gt;Hands perform simple, believable actions and do not dominate the frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual hierarchy&lt;/td&gt;
&lt;td&gt;Eyes, silhouette, and key facial or subject features remain readable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camera control&lt;/td&gt;
&lt;td&gt;Movement supports the reveal instead of calling attention to itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transformation&lt;/td&gt;
&lt;td&gt;The artwork appears to be built, not instantly painted or magically replaced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean frame&lt;/td&gt;
&lt;td&gt;No stray text, logos, watermarks, glitches, or abrupt visual changes appear&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ending&lt;/td&gt;
&lt;td&gt;The completed artwork is held long enough to reward the viewer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Core Principle to Remember
&lt;/h2&gt;

&lt;p&gt;The best string art AI videos do not try to prove how much an AI model can invent. They make the viewer want to inspect the thread, the nail heads, and the slowly emerging face. That feeling comes from restraint.&lt;/p&gt;

&lt;p&gt;Choose a photo with a strong silhouette. Lock the board and materials. Describe one simple hand action per shot. Preserve the uploaded subject as the only identity reference. Then give the finished artwork a quiet second to exist at the end.&lt;/p&gt;

&lt;p&gt;When the craft process is clear, the result stops looking like a filter. It feels like a small object someone could reach out and touch.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Building Reliable AI Generation Workflows: The API Call Is the Easy Part</title>
      <dc:creator>Jamie Cole</dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:11:25 +0000</pubDate>
      <link>https://dev.to/jamiecoleai/building-reliable-ai-generation-workflows-the-api-call-is-the-easy-part-5hd5</link>
      <guid>https://dev.to/jamiecoleai/building-reliable-ai-generation-workflows-the-api-call-is-the-easy-part-5hd5</guid>
      <description>&lt;p&gt;Over the past month, I have been building an AI generation product with asynchronous video tasks.&lt;/p&gt;

&lt;p&gt;I originally expected model integration to be the difficult part.&lt;/p&gt;

&lt;p&gt;It was not.&lt;/p&gt;

&lt;p&gt;Sending a request to an AI provider and receiving a task ID is relatively straightforward. The harder engineering work begins when the provider is slow, the network fails at the wrong moment, the user refreshes the page, or money has already changed hands.&lt;/p&gt;

&lt;p&gt;This article covers the architecture lessons I learned while turning an AI API into a workflow that can recover from uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic workflow
&lt;/h2&gt;

&lt;p&gt;At a high level, an asynchronous generation request looks simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  → Generation API
  → Local task record
  → AI provider
  → Polling or webhook
  → Result reconciliation
  → Persistent result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The happy path is easy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the request.&lt;/li&gt;
&lt;li&gt;Calculate the cost.&lt;/li&gt;
&lt;li&gt;Deduct credits.&lt;/li&gt;
&lt;li&gt;Submit the provider task.&lt;/li&gt;
&lt;li&gt;Poll until it succeeds.&lt;/li&gt;
&lt;li&gt;Store the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Production systems rarely stay on the happy path.&lt;/p&gt;

&lt;p&gt;The interesting problems appear between those steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Treat submission as a state machine
&lt;/h2&gt;

&lt;p&gt;A generation task is not simply &lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, or &lt;code&gt;failed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is another important state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submission_unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine sending a &lt;code&gt;POST&lt;/code&gt; request to a provider. The provider accepts it and starts the generation, but the response is lost because a gateway times out.&lt;/p&gt;

&lt;p&gt;From the application's perspective, two realities are possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the provider never accepted the request;&lt;/li&gt;
&lt;li&gt;the provider accepted it, but the application did not receive confirmation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automatically retrying the request may create a second paid generation.&lt;/p&gt;

&lt;p&gt;Treating it as an ordinary failure may refund the user while an expensive provider task continues running.&lt;/p&gt;

&lt;p&gt;The system therefore needs to distinguish a confirmed rejection from an uncertain submission.&lt;/p&gt;

&lt;p&gt;A conceptual state model might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GenerationState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submitting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;succeeded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submission_unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact names are not important. The important part is preserving uncertainty instead of forcing every outcome into success or failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Give every submission a stable idempotency key
&lt;/h2&gt;

&lt;p&gt;Users double-click buttons. Browsers retry requests. Mobile connections disconnect. UI state gets restored after a refresh.&lt;/p&gt;

&lt;p&gt;The generation endpoint must assume that the same logical submission can arrive more than once.&lt;/p&gt;

&lt;p&gt;Each client submission should carry a stable idempotency key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GenerateRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the server, that key should resolve to one local task for one user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findTaskByIdempotencyKey&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;existingTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Database uniqueness is still necessary because two requests may pass the initial lookup concurrently.&lt;/p&gt;

&lt;p&gt;A safe flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check for an existing task.&lt;/li&gt;
&lt;li&gt;Attempt to create the task with a unique key.&lt;/li&gt;
&lt;li&gt;If the insert loses a race, load and return the winning task.&lt;/li&gt;
&lt;li&gt;Never deduct credits twice for the same logical submission.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Idempotency is not only protection against double-clicks. It is what makes recovery possible across the entire workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Do not confuse an observation timeout with a task timeout
&lt;/h2&gt;

&lt;p&gt;Polling is an observation mechanism. It is not the task itself.&lt;/p&gt;

&lt;p&gt;A browser may stop polling after thirty seconds, but the provider task can still be running correctly.&lt;/p&gt;

&lt;p&gt;This distinction matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Polling window ended ≠ Generation task failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the client treats every polling timeout as a terminal failure, it may submit another task when the user tries again.&lt;/p&gt;

&lt;p&gt;A better approach is to preserve the original task ID and resume observation later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resumeTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isTerminal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;localTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;observeExistingTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refreshing the page, reopening the application, or returning from another device should continue tracking the same task whenever possible.&lt;/p&gt;

&lt;p&gt;The UI can say that observation has paused without claiming that generation has failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Make reconciliation shared and idempotent
&lt;/h2&gt;

&lt;p&gt;A task may reach a terminal result through several paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;foreground polling;&lt;/li&gt;
&lt;li&gt;a provider webhook;&lt;/li&gt;
&lt;li&gt;scheduled recovery;&lt;/li&gt;
&lt;li&gt;an admin repair operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These paths should not contain separate versions of the completion logic.&lt;/p&gt;

&lt;p&gt;They should all call the same reconciliation function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reconcileTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;providerStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProviderStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;poll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webhook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recovery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Load the current task.&lt;/span&gt;
  &lt;span class="c1"&gt;// Ignore an already-finalized task.&lt;/span&gt;
  &lt;span class="c1"&gt;// Persist the result or failure once.&lt;/span&gt;
  &lt;span class="c1"&gt;// Refund credits at most once.&lt;/span&gt;
  &lt;span class="c1"&gt;// Return the canonical local state.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without shared reconciliation, race conditions become likely.&lt;/p&gt;

&lt;p&gt;A webhook may complete the task while foreground polling is still active. Both paths may attempt to save the result or issue a refund.&lt;/p&gt;

&lt;p&gt;Terminal transitions must therefore be idempotent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalizedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice, a simple application-level check may not be enough. Database constraints or transactional updates should enforce the invariant.&lt;/p&gt;

&lt;p&gt;The rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Many observers may discover the outcome, but only one transition should finalize it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5. Calculate billing on the server
&lt;/h2&gt;

&lt;p&gt;The frontend needs to display the expected credit cost, but it must not decide the final charge.&lt;/p&gt;

&lt;p&gt;Generation cost may depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model;&lt;/li&gt;
&lt;li&gt;duration;&lt;/li&gt;
&lt;li&gt;resolution;&lt;/li&gt;
&lt;li&gt;output count;&lt;/li&gt;
&lt;li&gt;generation mode;&lt;/li&gt;
&lt;li&gt;optional capabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server should parse and validate the real request parameters, then calculate the authoritative cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseGenerationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;creditCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateCreditCost&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outputCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend should consume the same shared pricing definitions when possible, but the server must still recompute the charge.&lt;/p&gt;

&lt;p&gt;Otherwise, stale clients or modified requests can create a gap between displayed pricing and actual billing.&lt;/p&gt;

&lt;p&gt;Refunds need the same discipline.&lt;/p&gt;

&lt;p&gt;A refund should be tied to a unique task and transaction reason so that repeated recovery attempts cannot refund the same task twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Persist enough information to recover the user experience
&lt;/h2&gt;

&lt;p&gt;Task recovery is not only a backend problem.&lt;/p&gt;

&lt;p&gt;After a refresh, the frontend needs enough information to reconstruct what the user was doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the original prompt;&lt;/li&gt;
&lt;li&gt;selected model;&lt;/li&gt;
&lt;li&gt;input assets;&lt;/li&gt;
&lt;li&gt;generation settings;&lt;/li&gt;
&lt;li&gt;local task ID;&lt;/li&gt;
&lt;li&gt;current status;&lt;/li&gt;
&lt;li&gt;result metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Persisting only the provider task ID is not enough.&lt;/p&gt;

&lt;p&gt;The provider knows how to generate the output, but it does not necessarily know how your product should present the user's intent.&lt;/p&gt;

&lt;p&gt;A local task record should remain the application's source of truth. Provider state is external evidence used to update that record.&lt;/p&gt;

&lt;p&gt;This separation also prevents internal provider identifiers and implementation details from leaking into the user interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in my thinking
&lt;/h2&gt;

&lt;p&gt;Before building this workflow, I thought the central engineering problem was model integration.&lt;/p&gt;

&lt;p&gt;Now I see three different layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provider transport&lt;/strong&gt; — submitting and querying external tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product workflow&lt;/strong&gt; — task identity, recovery, billing, and reconciliation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User experience&lt;/strong&gt; — explaining progress, uncertainty, failure, and recovery clearly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The provider API only solves the first layer.&lt;/p&gt;

&lt;p&gt;The product is built in the second and third.&lt;/p&gt;

&lt;p&gt;AI tools can make implementation faster, but they also make it easier to add features before the underlying state model is reliable.&lt;/p&gt;

&lt;p&gt;The most valuable work was not adding another model. It was defining what must remain true when several things fail at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;For asynchronous AI products, reliability comes from preserving identity and uncertainty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one logical submission maps to one task;&lt;/li&gt;
&lt;li&gt;an unknown outcome is not silently treated as failure;&lt;/li&gt;
&lt;li&gt;polling timeouts do not recreate work;&lt;/li&gt;
&lt;li&gt;every completion path shares one reconciliation contract;&lt;/li&gt;
&lt;li&gt;billing is authoritative on the server;&lt;/li&gt;
&lt;li&gt;refunds and terminal transitions are idempotent;&lt;/li&gt;
&lt;li&gt;the UI resumes existing work instead of submitting again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API call may be the visible feature.&lt;/p&gt;

&lt;p&gt;The recovery model is the real product.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This article is based on my own implementation experience and was reviewed against the system's actual behavior. AI was used to help organize the structure and edit the English draft.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Keep Characters Consistent in AI Videos: A Reference-First Workflow</title>
      <dc:creator>Jamie Cole</dc:creator>
      <pubDate>Mon, 10 Aug 2026 07:18:10 +0000</pubDate>
      <link>https://dev.to/jamiecoleai/how-to-keep-characters-consistent-in-ai-videos-a-reference-first-workflow-4il8</link>
      <guid>https://dev.to/jamiecoleai/how-to-keep-characters-consistent-in-ai-videos-a-reference-first-workflow-4il8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1nc322560rdross62687.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1nc322560rdross62687.webp" alt="Two consistent AI video characters dancing together in a rain-soaked street" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shot one looks right. Shot two is polished too, but the lead character now has different eyes, a wider jaw, and a jacket that changed color. Each clip works on its own. Put them together, and the story falls apart.&lt;/p&gt;

&lt;p&gt;Character consistency in AI video is not mainly a matter of writing a longer prompt. It is a workflow problem. If every shot begins with a fresh text description, the model has room to invent a fresh person. A reference-first workflow reduces that room before motion generation begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Short Answer
&lt;/h2&gt;

&lt;p&gt;To keep a character recognizable across AI video shots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define the identity details that must not change.&lt;/li&gt;
&lt;li&gt;Approve one clean master reference before generating video.&lt;/li&gt;
&lt;li&gt;Add only the extra angles or expressions a shot genuinely needs.&lt;/li&gt;
&lt;li&gt;Use image or reference inputs instead of rebuilding the character from text.&lt;/li&gt;
&lt;li&gt;Write prompts about action, camera, and environment rather than redesigning the person.&lt;/li&gt;
&lt;li&gt;Generate short, controlled shots and review identity before moving on.&lt;/li&gt;
&lt;li&gt;Save successful frames as tested anchors for later scenes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not perfect pixel-level duplication. The goal is a character your audience recognizes from shot to shot without stopping to wonder whether the actor changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Characters Drift Between AI Video Shots
&lt;/h2&gt;

&lt;p&gt;A text prompt describes a type of person. A reference image shows a specific person.&lt;/p&gt;

&lt;p&gt;That difference matters. If you prompt for “a woman with short dark hair in a navy jacket” five times, the model may satisfy the sentence with five different faces. Even a detailed description leaves many visual decisions open: jaw shape, eye spacing, hairline, proportions, fabric details, and how those features look from another angle.&lt;/p&gt;

&lt;p&gt;Motion makes the problem harder. During a head turn, fast gesture, close interaction, or dramatic camera move, the model must preserve identity while also inventing unseen views and maintaining temporal continuity. The more constraints competing for attention, the easier it is for facial features, clothing, or body proportions to drift.&lt;/p&gt;

&lt;p&gt;This is why “add more appearance details” often disappoints. The better first move is to give the generation a stronger visual anchor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbkph9ulbtxh8ylugz023.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbkph9ulbtxh8ylugz023.png" alt="The same AI-generated character maintained across different video scenes" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A consistent character shown across changing scenes.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Character Anchor Kit Before You Animate
&lt;/h2&gt;

&lt;p&gt;A useful anchor kit is small. You do not need a folder of nearly identical portraits. You need a few approved images in which the character is easy to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with one master reference
&lt;/h3&gt;

&lt;p&gt;Choose a clear front or three-quarter view with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharp facial details&lt;/li&gt;
&lt;li&gt;Even lighting&lt;/li&gt;
&lt;li&gt;An unobstructed face and hairline&lt;/li&gt;
&lt;li&gt;A simple background&lt;/li&gt;
&lt;li&gt;One visible character&lt;/li&gt;
&lt;li&gt;The exact hairstyle, outfit, and signature details you want to preserve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid using a dramatic action frame as the master reference. Motion blur, a hidden eye, a hand across the face, or colored stage lighting may look cinematic, but each one removes information the model needs later.&lt;/p&gt;

&lt;p&gt;If the character does not exist yet, create several candidates with the &lt;a href="https://imagetovideoai.tools/text-to-image" rel="noopener noreferrer"&gt;Text to Image AI Generator&lt;/a&gt;, select one design, and stop redesigning it. If you already have the right person but need a cleaner angle, background, or wardrobe treatment, use &lt;a href="https://imagetovideoai.tools/image-to-image" rel="noopener noreferrer"&gt;Image to Image AI&lt;/a&gt; to create controlled variations from the approved source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add views because the shot needs them
&lt;/h3&gt;

&lt;p&gt;A second or third reference is useful when the planned camera angle reveals information the master image does not contain. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a side view before a profile shot.&lt;/li&gt;
&lt;li&gt;Add a full-body view before a walking scene.&lt;/li&gt;
&lt;li&gt;Add a close portrait before an emotional close-up.&lt;/li&gt;
&lt;li&gt;Add a tested outfit reference when clothing continuity matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More references are not automatically better. Two images with different hairstyles, apparent ages, lighting, or facial proportions create a conflict instead of a stronger identity. Every image in the kit should look like evidence of the same character on the same production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsw8m7oq64kw36lcqo44r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsw8m7oq64kw36lcqo44r.png" alt="A four-panel character reference sheet with face, front, side, and back views" width="799" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A three-view reference layout that keeps the face, clothing, and silhouette visible on one canvas.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Record the non-negotiables
&lt;/h3&gt;

&lt;p&gt;Write a short identity lock beside the images. Keep it limited to details the audience would notice immediately if they changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Face and hairstyle&lt;/li&gt;
&lt;li&gt;Signature clothing or accessory&lt;/li&gt;
&lt;li&gt;Approximate age and build&lt;/li&gt;
&lt;li&gt;Art direction, such as photorealistic or stylized 3D&lt;/li&gt;
&lt;li&gt;Details that must stay fixed, such as a scar, glasses, or an ear cuff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a second giant prompt. It is a review checklist for deciding whether a generated shot belongs in the sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the Right Visual Control for Each Shot
&lt;/h2&gt;

&lt;p&gt;Reference images, a starting frame, and start/end frames solve related but different problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imagetovideoai.tools/" rel="noopener noreferrer"&gt;Image to Video AI&lt;/a&gt; separates these controls into focused browser workflows. You can begin with an approved still, provide visual references, or guide a shot with start and end frames, then choose the available model and video settings, generate the clip, and review the MP4 result. The useful question is not which mode has the most inputs. It is which input gives the current shot the control it actually needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ddnt1991em2lpsdhpjl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ddnt1991em2lpsdhpjl.png" alt="A visual anchor workflow showing one character carried into multiple scenes" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One neutral character anchor reused across different environments and camera conditions.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Reference to Video for identity across new scenes
&lt;/h3&gt;

&lt;p&gt;Choose &lt;a href="https://imagetovideoai.tools/reference-to-video" rel="noopener noreferrer"&gt;Reference to Video&lt;/a&gt; when the character should remain recognizable but the new shot can begin in a different composition, location, or pose. The reference supplies identity and visual context; the prompt describes what happens next.&lt;/p&gt;

&lt;p&gt;This is a strong fit for a recurring character moving between scenes: a presenter in a studio, then at a product launch, then walking through a city. The number of reference images available depends on the selected model, so use the model limit as a ceiling, not a target.&lt;/p&gt;


  Your browser does not support embedded video playback.


&lt;p&gt;&lt;em&gt;Example of a recurring character moving through multiple European locations.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Image to Video when the opening composition is already correct
&lt;/h3&gt;

&lt;p&gt;Choose &lt;a href="https://imagetovideoai.tools/" rel="noopener noreferrer"&gt;Image to Video AI&lt;/a&gt; when you have approved the exact still that should become the first frame. This is often the simplest route to a stable single shot because the face, pose, wardrobe, lighting, and composition are already established before motion begins.&lt;/p&gt;

&lt;p&gt;It works especially well for portrait motion, product demonstrations, subtle expressions, and scenes where the first frame matters more than matching a separate reference sheet.&lt;/p&gt;


  Your browser does not support embedded video playback.


&lt;p&gt;&lt;em&gt;A restrained portrait-motion example generated from a reference image.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Frames to Video when the shot needs a controlled destination
&lt;/h3&gt;

&lt;p&gt;Choose &lt;a href="https://imagetovideoai.tools/frames-to-video" rel="noopener noreferrer"&gt;Frames to Video&lt;/a&gt; when both the beginning and ending composition matter. A start frame and an end frame can guide a reveal, transformation, camera move, or planned transition.&lt;/p&gt;

&lt;p&gt;This gives you endpoint control, but the two frames must agree. If the face, outfit, or proportions differ between them, the model has to reconcile the conflict during the shot, often by morphing. Build both frames from the same approved character kit before asking the video model to connect them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgd6f8dfvcsxrx3a4ziw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgd6f8dfvcsxrx3a4ziw2.png" alt="A first-frame and last-frame video generation workflow" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;First and last frames define the two visual endpoints the model must connect.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Reference-First Workflow for Consistent AI Video Characters
&lt;/h2&gt;

&lt;p&gt;The following process is deliberately sequential. Do not generate ten finished scenes and inspect identity at the end. Validate the character at each stage, when mistakes are still cheap to replace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define one shot, not the whole film
&lt;/h3&gt;

&lt;p&gt;Write down the job of the next clip in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A medium tracking shot of the character walking through a rainy street, glancing toward the camera, and stopping beneath a streetlight.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence defines the subject, action, camera relationship, environment, and endpoint. If it contains three locations, several costume changes, and four camera moves, split it into separate shots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Match the reference to the camera angle
&lt;/h3&gt;

&lt;p&gt;For the rainy street example, a three-quarter or full-body reference is more useful than an extreme facial close-up. If the camera will orbit behind the character, add a rear or side reference rather than asking the model to invent every unseen detail.&lt;/p&gt;

&lt;p&gt;Check the reference at the size viewers will actually see. A face that looks clear when zoomed in may become ambiguous in a full vertical frame.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Separate identity from motion in the prompt
&lt;/h3&gt;

&lt;p&gt;Let the image establish who the character is. Use the prompt to direct what changes.&lt;/p&gt;

&lt;p&gt;A weak prompt keeps redesigning the subject:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Beautiful young woman with dark hair, cinematic face, perfect features, stylish clothes, walking in a city, dramatic movement, dynamic camera, highly detailed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The adjectives are broad, and several of them invite a new interpretation of the person.&lt;/p&gt;

&lt;p&gt;A stronger motion prompt is easier to execute:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The referenced character walks slowly through a rain-soaked street, glances toward the camera once, and stops beneath a warm streetlight. Medium tracking shot, gentle handheld movement, natural body motion, shallow depth of field. Preserve the same face, short black hair, silver ear cuff, and navy jacket. No outfit change and no additional people.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The prompt has one action sequence, one camera idea, one environment, and a short identity lock.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Keep the first motion test simple
&lt;/h3&gt;

&lt;p&gt;Test a restrained version before adding a fast turn, spinning camera, crowd, flying fabric, or physical interaction. A slow walk, small head movement, or seated gesture tells you whether the reference and prompt work together.&lt;/p&gt;

&lt;p&gt;When the simple version holds the character, add one layer of complexity at a time. That makes failures diagnosable. If identity breaks only after adding a fast orbit, the camera move is the variable to change—not the character design.&lt;/p&gt;


  Your browser does not support embedded video playback.


&lt;p&gt;&lt;em&gt;A more demanding spinning-camera example for reviewing face, outfit, and silhouette stability during motion.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Generate a short shot and inspect specific frames
&lt;/h3&gt;

&lt;p&gt;Do not judge consistency from a looping thumbnail. Pause at the beginning, middle, and end. Then inspect the moment with the largest head turn or body movement.&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do the eyes, nose, jaw, and hairline still belong to the same person?&lt;/li&gt;
&lt;li&gt;Did the outfit color, neckline, sleeves, or accessories change?&lt;/li&gt;
&lt;li&gt;Did body proportions shift during movement?&lt;/li&gt;
&lt;li&gt;Did fingers, hair, or another character merge into the face?&lt;/li&gt;
&lt;li&gt;Does the final frame remain usable as an edit point?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Approve identity first. A beautiful camera move is not worth keeping if it breaks the character between shots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Save successful outputs as production assets
&lt;/h3&gt;

&lt;p&gt;When a shot works, save the prompt, model, aspect ratio, reference set, and a clean frame from the result. That frame is now tested visual evidence of the character in a new angle or environment.&lt;/p&gt;

&lt;p&gt;For the next shot, begin from the closest tested asset instead of returning to the oldest portrait every time. The reference library should grow from approved results, not from random variations.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Fix Common Character Consistency Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The face changes as soon as the character turns
&lt;/h3&gt;

&lt;p&gt;The model may not have enough information about the side of the face. Add a matching three-quarter or profile reference, reduce the speed of the turn, or change the camera so it does not demand a full unseen angle in one motion.&lt;/p&gt;

&lt;h3&gt;
  
  
  The outfit changes color or shape
&lt;/h3&gt;

&lt;p&gt;Keep the outfit visible in the reference and name only its defining details in the identity lock. Remove style words that conflict with it. “Futuristic fashion,” for example, may encourage the model to redesign a simple jacket even if the reference shows exactly what you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two characters blend together
&lt;/h3&gt;

&lt;p&gt;Establish each character separately before generating interaction shots. Make their silhouettes, clothing colors, and signature features visually distinct. Begin with simple staging, such as standing side by side, before attempting an embrace, fight, or fast dance where limbs and faces overlap.&lt;/p&gt;

&lt;h3&gt;
  
  
  The first frame is right but the face degrades over time
&lt;/h3&gt;

&lt;p&gt;Reduce simultaneous demands. Shorten the action, simplify the background, slow the camera, or divide the sequence into two clips. A single generation that must preserve identity, perform complex choreography, reveal a new environment, and execute a dramatic camera move is carrying too many jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every generation is a different kind of wrong
&lt;/h3&gt;

&lt;p&gt;Return to the anchor kit. If the references disagree, prompts cannot reliably repair them. Pick one master identity, rebuild only the necessary angles from it, and run a neutral motion test before returning to the production scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Prompt Formula
&lt;/h2&gt;

&lt;p&gt;Use this order when writing a character video prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Referenced character + one action + environment + camera framing/movement + lighting/mood + short identity lock + exclusions&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The referenced character sits at a cafe table and closes a sketchbook, then looks through the window. Medium close-up with a slow push-in, soft morning light, quiet reflective mood. Preserve the same face, bob haircut, round glasses, and green cardigan. No speaking, no extra people, no wardrobe change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The formula is a diagnostic tool, not a reason to make every prompt long. If the result drifts, remove optional details until the model can preserve the character and perform the central action. Then add detail back carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Consistency Checklist
&lt;/h2&gt;

&lt;p&gt;Before generating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One approved master reference&lt;/li&gt;
&lt;li&gt;Additional views only when required by the shot&lt;/li&gt;
&lt;li&gt;Matching identity, outfit, age, and visual style across references&lt;/li&gt;
&lt;li&gt;One clear action and one primary camera idea&lt;/li&gt;
&lt;li&gt;A short list of identity details that must not change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before accepting a clip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same recognizable face at the start, middle, and end&lt;/li&gt;
&lt;li&gt;Stable hair, clothing, accessories, and body proportions&lt;/li&gt;
&lt;li&gt;No unwanted character merging or new people&lt;/li&gt;
&lt;li&gt;Motion that does not hide a severe identity break&lt;/li&gt;
&lt;li&gt;A final frame that can cut cleanly into the next shot&lt;/li&gt;
&lt;li&gt;Saved prompt, settings, references, and approved output&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Consistency Comes From the Pipeline
&lt;/h2&gt;

&lt;p&gt;The recurring mistake is treating every clip as a separate prompt-writing contest. A sequence becomes more reliable when each shot inherits approved visual information from the one before it.&lt;/p&gt;

&lt;p&gt;Start with one character, one clean reference, and one simple action. Use &lt;a href="https://imagetovideoai.tools/reference-to-video" rel="noopener noreferrer"&gt;Reference to Video&lt;/a&gt; when identity must travel into a new scene, use &lt;a href="https://imagetovideoai.tools/" rel="noopener noreferrer"&gt;Image to Video AI&lt;/a&gt; when the opening still is already correct, and use &lt;a href="https://imagetovideoai.tools/frames-to-video" rel="noopener noreferrer"&gt;Frames to Video&lt;/a&gt; when the destination frame matters too.&lt;/p&gt;

&lt;p&gt;Lock the character first. Direct the motion second. Review before you scale. That order will do more for continuity than another paragraph of adjectives.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Validating Paths in a Browser-Based Word Puzzle Game</title>
      <dc:creator>Jamie Cole</dc:creator>
      <pubDate>Sat, 13 Jun 2026 05:42:22 +0000</pubDate>
      <link>https://dev.to/jamiecoleai/validating-paths-in-a-browser-based-word-puzzle-game-2nim</link>
      <guid>https://dev.to/jamiecoleai/validating-paths-in-a-browser-based-word-puzzle-game-2nim</guid>
      <description>&lt;p&gt;Word puzzle games often look simple: players connect letters, form words, and finish the board.&lt;/p&gt;

&lt;p&gt;The implementation is less simple.&lt;/p&gt;

&lt;p&gt;A browser-based word puzzle needs to answer a few important questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the selected path legal?&lt;/li&gt;
&lt;li&gt;Are the selected tiles adjacent?&lt;/li&gt;
&lt;li&gt;Is any tile reused?&lt;/li&gt;
&lt;li&gt;Does the selected path form an accepted word?&lt;/li&gt;
&lt;li&gt;Can the final board be completed fairly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post focuses on the validation layer behind a small word path puzzle game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Representing a tile
&lt;/h2&gt;

&lt;p&gt;A tile needs more than just a letter. It also needs a position.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;R&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;row&lt;/code&gt; and &lt;code&gt;col&lt;/code&gt; values are important because every movement rule depends on position.&lt;/p&gt;

&lt;p&gt;Without stable coordinates, the game cannot reliably check whether two tiles are connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking adjacency
&lt;/h2&gt;

&lt;p&gt;The first rule is movement.&lt;/p&gt;

&lt;p&gt;If diagonal movement is allowed, a tile can move to any of the eight neighboring tiles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAdjacent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function rejects the same tile and accepts horizontal, vertical, and diagonal neighbors.&lt;/p&gt;

&lt;p&gt;If the puzzle should only allow horizontal and vertical movement, the rule can be changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isOrthogonallyAdjacent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This small difference changes the entire feel of the puzzle.&lt;/p&gt;

&lt;p&gt;Diagonal movement gives players more freedom. Orthogonal movement creates stricter paths and often makes puzzles easier to analyze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing reused tiles
&lt;/h2&gt;

&lt;p&gt;A common rule in word path puzzles is that a tile cannot be reused inside the same path.&lt;/p&gt;

&lt;p&gt;The simplest way to check this is to store each tile coordinate in a set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hasDuplicateTiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents loops where the player returns to a tile that has already been used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating the full path
&lt;/h2&gt;

&lt;p&gt;Once adjacency and duplicate checks exist, a full path validator is simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hasDuplicateTiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isAdjacent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only validates movement.&lt;/p&gt;

&lt;p&gt;It does not decide whether the path forms a real word. That should be handled separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting a path into a word
&lt;/h2&gt;

&lt;p&gt;A path can be converted into a word by joining the tile letters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pathToWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it can be checked against an accepted word list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAcceptedWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;acceptedWords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pathToWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;acceptedWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For puzzle games, I usually prefer a curated answer list instead of a large dictionary.&lt;/p&gt;

&lt;p&gt;A large dictionary may create many accidental valid words. A curated list gives the designer more control over the intended solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking board coverage
&lt;/h2&gt;

&lt;p&gt;Some word path puzzles require the final solution to cover every required tile.&lt;/p&gt;

&lt;p&gt;That means the game needs to know how many unique tiles are covered by all solved paths.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCoveredTiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;covered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;covered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;covered&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the board can be checked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isBoardComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requiredTileCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getCoveredTiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;requiredTileCount&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful because a puzzle can contain valid words but still be incomplete.&lt;/p&gt;

&lt;p&gt;A fair board should not leave impossible or confusing leftover tiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoiding overlapping solution paths
&lt;/h2&gt;

&lt;p&gt;If each tile should belong to only one final answer, paths must not overlap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pathsOverlap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This check becomes important when generating puzzles automatically.&lt;/p&gt;

&lt;p&gt;A generator can accidentally create boards where multiple solution paths fight for the same tile.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical generation approach
&lt;/h2&gt;

&lt;p&gt;For a first version, I would not start with fully random letters.&lt;/p&gt;

&lt;p&gt;Random boards are easy to create but hard to make fair.&lt;/p&gt;

&lt;p&gt;A more controlled approach is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;choose a board size&lt;/li&gt;
&lt;li&gt;choose a small curated word list&lt;/li&gt;
&lt;li&gt;place each word as a legal path&lt;/li&gt;
&lt;li&gt;reject overlapping paths&lt;/li&gt;
&lt;li&gt;check board coverage&lt;/li&gt;
&lt;li&gt;review difficulty before publishing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not a perfect generator, but it is a useful starting point.&lt;/p&gt;

&lt;p&gt;For browser games, a small reliable generator is better than a large unreliable one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Daily puzzles are easier to control
&lt;/h2&gt;

&lt;p&gt;An infinite random mode sounds exciting, but quality control becomes harder.&lt;/p&gt;

&lt;p&gt;Daily puzzles are easier because the game only needs one good board per day.&lt;/p&gt;

&lt;p&gt;That allows extra checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;path legality&lt;/li&gt;
&lt;li&gt;word validity&lt;/li&gt;
&lt;li&gt;board coverage&lt;/li&gt;
&lt;li&gt;duplicate tile prevention&lt;/li&gt;
&lt;li&gt;difficulty review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small V0, daily mode is often the better choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The hard part of a word path puzzle is not rendering the grid.&lt;/p&gt;

&lt;p&gt;The hard part is validation.&lt;/p&gt;

&lt;p&gt;Before adding accounts, leaderboards, animations, or sharing features, the core rules should be solid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;legal movement&lt;/li&gt;
&lt;li&gt;no reused tiles&lt;/li&gt;
&lt;li&gt;accepted words only&lt;/li&gt;
&lt;li&gt;no invalid overlap&lt;/li&gt;
&lt;li&gt;complete board coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those rules work, the game becomes much easier to improve.&lt;/p&gt;

&lt;p&gt;I used this approach while experimenting with a small independent browser puzzle project. The main lesson was simple: build the validator first, then build the game around it.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>webdev</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>How I Built a Performant Logic Puzzle Game with Next.js and a Web Worker</title>
      <dc:creator>Jamie Cole</dc:creator>
      <pubDate>Tue, 19 Aug 2025 02:05:11 +0000</pubDate>
      <link>https://dev.to/jamiecoleai/how-i-built-a-performant-logic-puzzle-game-with-nextjs-and-a-web-worker-4381</link>
      <guid>https://dev.to/jamiecoleai/how-i-built-a-performant-logic-puzzle-game-with-nextjs-and-a-web-worker-4381</guid>
      <description>&lt;p&gt;Hey DEV community!&lt;/p&gt;

&lt;p&gt;I've always been a huge fan of logic puzzles. Recently, I got hooked on the &lt;a href="https://queensgame.io/" rel="noopener noreferrer"&gt;Queens puzzle game&lt;/a&gt; but felt the classic rules were a bit too simple. This sparked an idea: what if I could create a more modern, more challenging version?&lt;/p&gt;

&lt;p&gt;Today, I'm excited to share the result of that idea: Enhanced Queens Game, a free-to-play web game I built from scratch.&lt;/p&gt;

&lt;p&gt;🎮 You can play it here:  &lt;a href="https://queensgame.io/" rel="noopener noreferrer"&gt;queens game &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is a quick breakdown of the journey, the tech stack, and one interesting technical challenge I solved.&lt;/p&gt;

&lt;p&gt;The Core Idea: Adding a "Third Dimension"&lt;br&gt;
The biggest change from the classic puzzle is the addition of colored regions. On top of having only one queen per row and column, you must also have only one per colored block. This simple twist turns a 2D puzzle into a much deeper 3D spatial reasoning challenge.&lt;/p&gt;

&lt;p&gt;![Insert a GIF of the gameplay, showing the colored regions and a queen turning red]&lt;/p&gt;

&lt;p&gt;The Tech Stack&lt;br&gt;
I chose a modern stack to build this, focusing on performance and developer experience:&lt;/p&gt;

&lt;p&gt;Framework: Next.js (for great SEO and fast performance)&lt;/p&gt;

&lt;p&gt;Language: TypeScript (a must for managing complex game logic)&lt;/p&gt;

&lt;p&gt;Styling: Tailwind CSS (for building the clean UI quickly)&lt;/p&gt;

&lt;p&gt;The Coolest Technical Challenge: Real-time Validation without Lag&lt;br&gt;
A key feature is that a queen turns red instantly if it breaks a rule. On larger 7x7 boards, checking all conflicts (row, column, region, and adjacency) on every single move could potentially lag the main UI thread.&lt;/p&gt;

&lt;p&gt;The solution? A Web Worker.&lt;/p&gt;

&lt;p&gt;I offloaded the most intensive validation logic to a separate background thread using a Web Worker (solveQueensWorker.ts in the repo). The main thread sends the current board state to the worker, the worker runs the heavy computations, and then sends the results (any conflicts) back.&lt;/p&gt;

&lt;p&gt;This ensures the UI remains buttery smooth and responsive, even while complex calculations are happening in the background. It was a perfect use case for a worker and a fun problem to solve.&lt;/p&gt;

&lt;p&gt;Feedback Welcome!&lt;br&gt;
This has been an incredibly rewarding personal project. I'd be honored if you'd take a look. I'm open to any and all feedback—whether it's on the game's difficulty, the UI/UX, or the code itself.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
