<?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: Marcus huang</title>
    <description>The latest articles on DEV Community by Marcus huang (@marcushuang).</description>
    <link>https://dev.to/marcushuang</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%2F4203312%2F08efe62b-3044-40c6-b51d-96eb8e7d3dc3.png</url>
      <title>DEV Community: Marcus huang</title>
      <link>https://dev.to/marcushuang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marcushuang"/>
    <language>en</language>
    <item>
      <title>Treating image prompts like config: a structured brief workflow for Seedream 5.5</title>
      <dc:creator>Marcus huang</dc:creator>
      <pubDate>Mon, 26 Oct 2026 00:18:38 +0000</pubDate>
      <link>https://dev.to/marcushuang/treating-image-prompts-like-config-a-structured-brief-workflow-for-seedream-55-2fmh</link>
      <guid>https://dev.to/marcushuang/treating-image-prompts-like-config-a-structured-brief-workflow-for-seedream-55-2fmh</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm the owner of seedream55ai.com, an independent web workspace for the Seedream 5.5 image model. It is a third-party tool and is not affiliated with ByteDance or any model provider.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most bad AI images I see come from one-line prompts that mix subject, style and layout in a single run-on sentence. When something comes out wrong, you have no idea which part to change. The fix that worked for me is boring and very developer-friendly: treat the prompt like a config file.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Split the brief into fields
&lt;/h2&gt;

&lt;p&gt;Before writing any prose, I fill in a small object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"matte white ceramic coffee mug, no logo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"setting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"light oak table, soft morning window light from the left"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"camera"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"45-degree angle, 50mm look, shallow depth of field"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clean e-commerce product photo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aspect_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1:1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"avoid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"extra handles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warped rim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"busy background"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each field answers one question. If the light is wrong, you edit &lt;code&gt;setting&lt;/code&gt;. If the mug looks warped, you add to &lt;code&gt;avoid&lt;/code&gt; or tighten &lt;code&gt;subject&lt;/code&gt;. Changes become diffs instead of rewrites.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Render the object into a prompt
&lt;/h2&gt;

&lt;p&gt;A tiny function turns the object into the sentence the model actually receives:&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;renderPrompt&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;parts&lt;/span&gt; &lt;span class="o"&gt;=&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; of &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="nx"&gt;subject&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="s2"&gt;`on &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="nx"&gt;setting&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&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="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`with the exact text "&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="nx"&gt;text&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="kc"&gt;null&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="nx"&gt;avoid&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="s2"&gt;`avoid: &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="nx"&gt;avoid&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="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&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="kc"&gt;null&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;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&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="s2"&gt;. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;Keep the rendered prompt in version control next to the brief. When a result is good, you can reproduce it later; when it is bad, &lt;code&gt;git diff&lt;/code&gt; tells you exactly what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Change one field per iteration
&lt;/h2&gt;

&lt;p&gt;This is the same discipline as debugging: one variable at a time. My loop is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate 2 to 4 variations from the same brief.&lt;/li&gt;
&lt;li&gt;Pick the closest one and write down what is wrong in one line.&lt;/li&gt;
&lt;li&gt;Map that line to a single field and edit only that field.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three or four iterations usually get a usable product shot. Changing everything at once usually gets you a different, equally wrong image.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Text in images needs its own field
&lt;/h2&gt;

&lt;p&gt;Posters and labels fail most often on typography. Put the literal string in a dedicated &lt;code&gt;text&lt;/code&gt; field, quote it exactly in the rendered prompt, keep it short, and say where it goes ("centered headline at the top"). For bilingual posters, give the English and Chinese strings separately rather than asking the model to translate.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Use reference images for edits, not longer prompts
&lt;/h2&gt;

&lt;p&gt;When you already have a photo and only want to change the background or lighting, an image-to-image edit with the original as a reference beats describing the product in more and more words. The brief then only describes the change: &lt;code&gt;"replace background with a plain warm grey studio sweep, keep the product unchanged"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I run this
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://seedream55ai.com/" rel="noopener noreferrer"&gt;Seedream55ai&lt;/a&gt; partly to make this loop less painful: text-to-image and reference edits in one place, aspect ratios like 1:1, 16:9 and 9:16, up to four outputs per request, and a credit quote shown before each generation. It is credit-based (packs from $2.99, plans from $7.99/month), so it is not a free unlimited service. The brief-as-config approach above works with any image model though, so steal the template either way.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Generating Hero Images, Icons and OG Cards for a Side Project with Nano Banana 2.1</title>
      <dc:creator>Marcus huang</dc:creator>
      <pubDate>Sat, 24 Oct 2026 01:21:39 +0000</pubDate>
      <link>https://dev.to/marcushuang/generating-hero-images-icons-and-og-cards-for-a-side-project-with-nano-banana-21-384h</link>
      <guid>https://dev.to/marcushuang/generating-hero-images-icons-and-og-cards-for-a-side-project-with-nano-banana-21-384h</guid>
      <description>&lt;p&gt;Every side project hits the same wall right before launch: the code works, but the landing page has no images. Here is the workflow I use to generate a consistent asset set with &lt;a href="https://nanobanana21pro.com/" rel="noopener noreferrer"&gt;nanobanana21pro.com&lt;/a&gt;, an independent third-party web app built on Google's Nano Banana image models.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: nanobanana21pro.com is not a Google product and not Google AI Studio. It is a separate service, not affiliated with or endorsed by Google. Writing prompts is free; generating images uses credits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Write an asset spec before any prompt
&lt;/h2&gt;

&lt;p&gt;Treat images like any other dependency. I keep a small spec file in the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;palette&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#0F172A"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#38BDF8"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#F8FAFC"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;soft&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3D&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;clay,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rounded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shapes,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;studio&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lighting"&lt;/span&gt;
&lt;span class="na"&gt;assets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hero&lt;/span&gt;
    &lt;span class="na"&gt;ratio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16:9"&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2K&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;feature-icons&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;ratio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1:1"&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1K&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;og-card&lt;/span&gt;
    &lt;span class="na"&gt;ratio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.91:1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(closest&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;available)"&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1K&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is to reuse the same style string in every prompt, so the set looks like it belongs together.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Generate the hero first
&lt;/h2&gt;

&lt;p&gt;The hero sets the visual language. Example prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Soft 3D clay illustration of a small rocket made of stacked code brackets,
launching from a laptop keyboard, navy background (#0F172A),
sky blue accents (#38BDF8), studio lighting, lots of empty space on the left
for a headline. No text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for empty space where your headline will sit.&lt;/li&gt;
&lt;li&gt;Say "No text" when the HTML will carry the copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Use the hero as a reference for everything else
&lt;/h2&gt;

&lt;p&gt;This is where reference editing pays off. Nano Banana 2.1 accepts up to 14 reference images, so I attach the approved hero and give it a role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the reference image only for style, color palette and lighting.
Create a single soft 3D clay icon of a padlock on a plain #F8FAFC background,
centered, 1:1. No text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat for each feature icon (padlock, lightning bolt, chart). Because the hero is the style reference, the icons stay on-brand.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. OG card with real text
&lt;/h2&gt;

&lt;p&gt;For social preview cards I do want text in the image, and this is an area where the 2.1 model is strong at legible lettering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the reference image for style and palette.
Wide social preview card, the words SHIPKIT in bold white letters on the left,
the tagline "Deploy in one command" below it in smaller sky blue letters,
the clay rocket on the right.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep text short and quote it exactly. Check every letter before you ship it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Budget the run
&lt;/h2&gt;

&lt;p&gt;The generator shows a credit quote before you submit. On Nano Banana 2.1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Credits per image&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1K&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2K&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4K&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My set above (one 2K hero, three 1K icons, one 1K OG card, plus a few retries) fits in a small credit pack. One-time packs start at $2.99, and monthly plans are $18.99/mo and $44.99/mo. Batch size multiplies the cost, so explore at 1K.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>design</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Weighted-Random Birth Simulator on an Open-Source Engine</title>
      <dc:creator>Marcus huang</dc:creator>
      <pubDate>Fri, 23 Oct 2026 19:19:00 +0000</pubDate>
      <link>https://dev.to/marcushuang/building-a-weighted-random-birth-simulator-on-an-open-source-engine-535f</link>
      <guid>https://dev.to/marcushuang/building-a-weighted-random-birth-simulator-on-an-open-source-engine-535f</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I made 1in8billion.live. It's an independent, unofficial fan-made game inspired by "1 in 8 Billion" and isn't affiliated with 1in8b.life. I built it on top of the MIT-licensed open-source born-somewhere engine, which I didn't write.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a write-up of the design ideas behind &lt;strong&gt;1 in 8 Billion – Free Life Simulator&lt;/strong&gt;, my browser game where you're born in a random country and live one life from birth to death. It covers the general patterns: weighted sampling, data-driven events, local saves and i18n. The code snippets are simplified illustrations of each pattern, not the production source.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Weighted random selection
&lt;/h3&gt;

&lt;p&gt;The core mechanic is a draw over 172 countries. A uniform &lt;code&gt;Math.random()&lt;/code&gt; pick would be wrong, because Tuvalu shouldn't come up as often as India. The game weights the draw by &lt;strong&gt;annual births&lt;/strong&gt;, and the homepage shows the resulting shares: India around 17.6%, China around 6.7%, Nigeria around 5.7%, and so on.&lt;/p&gt;

&lt;p&gt;The standard way to do this is cumulative-weight sampling:&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;weightedPick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// items: [{ id: "IN", weight: 23000000 }, ...]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;r&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;total&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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weight&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;r&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;item&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;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&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;1&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 172 entries a linear scan is plenty fast. If you were sampling millions of times, you'd precompute a prefix-sum array and binary search it, or use the alias method for O(1) draws.&lt;/p&gt;

&lt;p&gt;I also added a &lt;strong&gt;population mode&lt;/strong&gt;, which swaps birth weights for population weights. That's a nice example of keeping the sampling function generic and passing in a different weight vector.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data-driven life events
&lt;/h3&gt;

&lt;p&gt;Each country carries real indicators: &lt;strong&gt;life expectancy, infant mortality, under-5 mortality and GDP per capita (PPP)&lt;/strong&gt;. A simple way to turn these into gameplay is a yearly hazard. Each simulated year, compute a probability of death (and of other events) from the country's stats and the character's age, then roll for it. Infant and under-5 mortality cover the early years, and life expectancy calibrates the curve for the rest of the life.&lt;/p&gt;

&lt;p&gt;Choices sit on top of this. At turning points the player picks an action, or &lt;strong&gt;auto mode&lt;/strong&gt; picks one, and the result nudges stats like health, happiness, knowledge and social standing. The game shows these as 0–100 meters along with net assets in PPP dollars.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Batch simulation
&lt;/h3&gt;

&lt;p&gt;Once a single life is a pure-ish function of &lt;code&gt;(country, seed, choices)&lt;/code&gt;, running many is easy. The game's &lt;strong&gt;Lab&lt;/strong&gt; runs lives in bulk and summarizes the results. If you build something similar, keep the simulation step free of DOM access so you can move it to a Web Worker without changing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. No-backend saves with localStorage
&lt;/h3&gt;

&lt;p&gt;There's no signup. Progress and past lives are saved &lt;strong&gt;in the browser on the device&lt;/strong&gt;, and the site tells players plainly that clearing site storage removes them and that there's no cloud sync. The usual pattern:&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;KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lives:v1&lt;/span&gt;&lt;span class="dl"&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;load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&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="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[]&lt;/span&gt;&lt;span class="dl"&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;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lives&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Version your keys, cap the history, and wrap &lt;code&gt;setItem&lt;/code&gt; in try/catch, because storage quotas and private-browsing modes can throw.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. i18n across six locales
&lt;/h3&gt;

&lt;p&gt;The UI ships in &lt;strong&gt;EN, DE, ES, FR, PT-BR and ZH-CN&lt;/strong&gt;. Some general lessons for a narrative game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep event text as keyed templates with placeholders, not concatenated strings.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Intl.NumberFormat&lt;/code&gt; for currency and &lt;code&gt;Intl.PluralRules&lt;/code&gt; for "1 year / 2 years".&lt;/li&gt;
&lt;li&gt;CJK languages often need different line-height and truncation rules on share cards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Shareable output
&lt;/h3&gt;

&lt;p&gt;At the end of a life you can download a &lt;strong&gt;life card&lt;/strong&gt; image, plus a generated &lt;strong&gt;family tree&lt;/strong&gt;. Drawing to a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; and exporting with &lt;code&gt;toBlob()&lt;/code&gt; is the usual browser-only way to make an image like this without a server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;p&gt;Most of the "feel" comes from two choices: weighting the draw by real births, and letting real indicators drive the yearly odds. The rest is UX: pause, two reading speeds, an auto mode, and an &lt;a href="https://1in8billion.live/?edu=1" rel="noopener noreferrer"&gt;education mode&lt;/a&gt; for classrooms.&lt;/p&gt;

&lt;p&gt;You can try what I built at &lt;a href="https://1in8billion.live" rel="noopener noreferrer"&gt;1in8billion.live&lt;/a&gt;. It's free to play, with an optional US$2.99 one-time Keepsake card. I'd like to hear how you'd approach the hazard model.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>gamedev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
