Posted July 13, 2025 by Ex Blackhole
Suppose you’re making a visual novel with lots of images. In most VNs, each scene—say, groping in a hotel room—has a single image you assign to show. So you name your files something like:
hotel_groping.webp
And build scenes like this:
location: 'hotel'
activity: 'groping'
img: 'hotel_groping.webp'
You do this for every event—hotel_handjob, hotel_oral, home_tentacles, dream-world_sleep, and so on. You build an arsenal of lewd images in your assets folder.
But here’s the subtle problem:
If you want to test scenes before the final art is done, you need placeholders for everything. You create hundreds of placeholder files, which get replaced later.
Later, you expand: now each scene can show three images. You update all your scripts and rename all your files:
location: 'hotel'
activity: 'groping'
img: ['hotel_groping_01.webp', 'hotel_groping_02.webp', 'hotel_groping_03.webp']
Then you realize some images don’t fit—wrong hair color, odd angles, whatever—so you delete a few. But now you have to manually update every script that used them, or the game breaks.
Multiply by a thousand images, and you’re spending more time wiring assets than creating content.
AI art generation can’t save you if you’re still hand-wiring every scene.
Instead, you can name your generated image files like this:
hotel_groping_0.1301331480579081.webp
A parser script turns each file into an entry like this:
name: 'hotel_groping_0.1301331480579081.webp'
vec: [1, 0, 0.026]
Now imagine every image is a point in a 3D space:
After parsing, your asset table looks like:
{"name": "hotel_groping_0.1301...", "vec": [1, 0, 0.026]},
{"name": "hotel_groping_0.3127...", "vec": [1, 0, 0.289]},
{"name": "hotel_handjob_0.3659...", "vec": [1, 1, 0.214]},
// ...etc
Now your assets are mapped in a vector space—and computers are very good at finding what’s “closest” in a sea of numbers.
When the game needs an image, it doesn’t look for a filename—it describes the kind of image it wants:
location: 'hotel'
activity: 'handjob'
intensity: 0.4
The engine turns this into a vector:
[1, 1, 0.4]
It then finds the nearest neighbor in the asset table—like asking for the closest subway station to your destination. For example, the closest match might be [1, 1, 0.214], returning:
hotel_handjob_0.36592505330525615.webp
That’s the image you see in the game.
One word: scalability.
import { abilities, locations } from './data.js';
import { table } from './table.js';
const loc_labels = {}, act_labels = {};
locations.forEach((e, i) => loc_labels[e.label] = i);
abilities.forEach((e, i) => act_labels[e.label] = i);
const w = [16, 1, 1]; // weights: prioritize location most
const sqdist = (v1, v2) => v1.map((v, i) => ((v - v2[i]) * w[i]) ** 2).reduce((a, b) => a + b);
const nearest = vec =>
table.reduce((best, e) =>
sqdist(e.vec, vec) < sqdist(best.vec, vec) ? e : best,
table[0]
);
const qer2vec = (loc, act, intensity) => [loc_labels[loc] ?? -1, act_labels[act] ?? -1, intensity];
export const query = (loc, act, intensity=.5) => nearest(qer2vec(loc, act, intensity)).name;
In short:
Vectors turn asset management from a headache into a breeze. The game always shows the most appropriate image for any scene, without ever needing you to play librarian.