Second Chance GazetteDevlogToken Use

Fallback

unreal-enginemetahumangroomhair-cardslodvramperformanceniagaranightclubnight-skyaudiobusesclaude-codegta-vi

The cheapest hair in the world

For about eleven hours on Friday, everybody in the town was bald at Epic, and the best number anyone produced all day was the price of it.

It started with a good decision. The game had been running out of graphics memory at its top setting for a week. A hundred residents on screen wanted a little over 9 GB on a card with 8, and hair was one of the biggest single costs. MetaHuman hair comes as a groom, thousands of separate strands, and most grooms also carry cheaper versions of themselves further down: hair cards, which are flat strips with a hair texture on them, and at the very bottom a solid mesh shaped like the hair. Before work, James was asked whether Epic should draw the cards instead of the strands. He said yes, go with the hair cards.

The agent, Claude Code Opus 5.5, wrote one line into the game's settings to do it. A test run in the club came back with the frame rate up from 11.3 to 13.7 and 550 to 650 MB of video memory saved. By early afternoon a second run, in the rain, had it at about 1.2 GB. Those numbers went into the performance notes as measured, and they were measured. They were measuring something else.

It was a Friday, so James was at work, answering from his phone between other things. Several agents were running at once, each on its own thread, taking turns on one editor through a coordinator. One of those threads was dressing a police officer and could not work out why he had no hair. At twenty to six it found out. The line the agent had written, r.HairStrands.Enable=0, does not switch hair to cards. In Unreal Engine 5.8 it switches off the whole hair system: strands, cards and meshes together. Every MetaHuman in the game had been bald, browless and beardless at Epic since breakfast. The 1.2 GB was what no hair at all costs.

GTA VI has a studio of thousands behind it, and somewhere in it there are people whose whole job is hair. Here the hair department was a Friday, and the hair was gone for most of it.

What the cards cannot draw

The replacement was the setting that should have been used in the first place, r.HairStrands.UseCardsInsteadOfStrands=1, which tells every groom to draw its cards. The engine's own help text for it has one sentence that matters. If a groom has no cards, nothing is displayed.

The thread working on the police officer went through the cast's grooms and listed the ones with no cards: the dense eyebrows, two buzz cuts, a brush cut, a set of braids, the balding stubble, every moustache, every bit of peach fuzz and every set of eyelashes. A face with no brows and no lashes looks ill rather than bald, which is worse. At five to ten that night the global switch came out again too. Strands were back everywhere, and the memory problem was back with them.

Close-up of an older bald man on a terraced street at night, a black cat crossing the road behind him. The hair round the sides and back of his head is a solid, crumpled white shell, like paper wrapped round his skull.
Dave on a terrace at night. His thin white hair has turned into a solid white sheet wrapped round the back of his head, crumpled like paper.

Then there was Dave. Dave is a resident, an older man with a thin ring of white hair, and up close at Epic his hair was a white sheet, the frame above, like a paper bag pulled over the back of his head. The first guess was the cards material, or missing textures. It was neither. The agent took him onto an empty test map and photographed his head four ways, changing one thing each time, and the answer was in the numbers that link his body to his hair.

A MetaHuman has a component that keeps the body, the clothes and the hair at matching levels of detail. Dave's had three levels, and it mapped them to his hair's levels 3, 5 and 7. His hair has eight levels: two of strands, three of cards, three of solid mesh. So body level 1 was hair level 5, and hair level 5 is the mesh. Normally a body at arm's length is on level 0 and none of this shows. But that afternoon, to save another 230 MB, the performance thread had added r.SkeletalMeshLODBias=1 to Epic, which drops every character one level. Dave at arm's length was now Dave on level 1, wearing the helmet.

Two side views of the same man's ear and hair against a pale blue background. On the left the hair is a smooth pale crumpled sheet; on the right it is wispy grey and white strands.
The same head twice on the test map. Left, the hair is the solid mesh, a pale crumpled shell round the ear. Right, mapped onto its card level instead, it reads as grey strands.

The bias came out at quarter past ten. 230 MB was not worth it. What went in instead, built into the town code that evening, is a small switch that visits every groom in the world every two seconds and turns its cards on only where every strand level it shows has a card version to fall back to. The brows and lashes keep their strands. A second rule, written in the same patch, stops a near body ever landing on a mesh level, whatever the mapping says. The town's own dump of every groom shows Dave's hair on cards. A close-up of Dave with all of it running is the next still.

The game's promise is above 30 frames a second on one 8 GB card. Epic is where that promise is hardest, and on Saturday morning it was back to roughly a gigabyte over, with the real price of cards still to be measured.

Eye height

The Sweatbox, the nightclub in the old warehouse, came out of Thursday with dry ice that looked like a tan carpet and room haze that drew nothing. On Friday both were rebuilt, and it took most of the day to get a single photograph of them.

The haze was the easy half. The fog volume that had refused to draw on Thursday was retired, and the haze is now a slow fall of very large, very faint sprites, flat soft pictures of smoke that always face the camera, four or five metres across, drifting high in the room. Sprites take light from the club's own lamps, which the fog volume never did.

The dry ice was the surprise. On Thursday its puffs had been laid face-up on the floor, so they could never cut through it. Looked at from where a person stands, a picture lying flat on the floor is nearly edge-on, and it disappears. The puffs were turned to face the camera instead and stretched wide and low, 2.2 to 3.8 metres across and only 30 to 55 centimetres tall, so they read as a bank rather than a carpet.

The photograph was the long half. Running the game inside the editor at Epic on an 8 GB card is how the editor had crashed at seven that morning, so the stills were taken a different way: the finished game started on its own, with no editor open, by the crowd test bench, which loads the town, walks a camera to the club, takes the shots and quits. Four minutes a go, and nothing to crash but itself.

A police officer in a yellow high-visibility jacket and hat beside a police car on a residential street at night, the sky behind the houses glowing orange, a red car parked further along.
A still from the bench that was meant to be the dance floor. It is the street outside, the police officer by the car in the intro, under an orange night sky.

The first two club stills came back as the frame above: a police officer beside her car on the street. That is the game's opening scene. The bench had moved its camera to the club, the intro had moved it straight back, and the shots were taken of whichever won. One extra command on the bench's start line skips the intro, and the third run was inside.

A crowded nightclub dance floor in an old brick warehouse with galleries on both sides and a purple LED panel over the far doors. A low bank of pale fog lies round the dancers' feet and a warm haze softens the back of the room.
The finished look. Dry ice lies in a low bank round the dancers' shoes and a warm haze softens the far end of the room. The shirtless man is one of the bench's stand-ins, not a dressed resident.

It works, mostly. The dry ice sits round the shoes and the haze is a warm veil over the far wall. Where the floor cuts through a band of dry ice there is a straight edge, because the smoke material has no way to fade out where it meets solid ground. A copy of that material that does fade is written and waiting for its slot. A third layer meant to show beams of light through the haze drew no beams at three times the density, which is the next question. The washes are still on round the clock.

Twelve degrees below

The night sky had been orange for a week. Enough that a still from 9.30pm on Radworth Road looked like a town on fire just over the hill.

A street of brick houses at night with a police car and an officer in the foreground. Behind the houses the horizon glows bright yellow and orange, as if the sun had only just gone down.
9.30pm on Radworth Road. The sun set two and a half hours earlier and the horizon behind the houses is still glowing yellow-orange.

James had asked, the night before, for a proper end of October: light from half six, sunset at seven, fully dark by eight, with a new star field, fluffier aeroplane trails that fade in and out, and a faint aurora on rare nights. The agent found the orange first. The day-and-night code swings the sun round a circle, and the night half was a mirror of the day half. The sun never climbs more than 25 degrees at noon in late October, so at midnight it never went more than 25 degrees under the horizon. All night it sat 12 to 25 degrees down, still lighting the undersides of the clouds and the low mist from below.

The town from above at night: a terraced street lit orange by its lamps, a lit supermarket, green fields and a winding path. Small clouds glow orange-brown from below, and on the left a large blotchy grey patch of stars fills the sky.
The town from 22 metres up at night. The cloud undersides are lit orange-brown from below, and the Milky Way on the left is a blotchy, pixelated patch.

The fix gave the night half its own depth, 50 degrees, so the sun goes properly under, and fades the sunlight to nothing between 4 and 8 degrees below the horizon, which keeps the lightning flash at night. The new stars, the aurora switch and the softer trails went in at the same time, all saved by half ten.

The photographs are next, and they wait on what happened that morning. At three minutes past seven the editor had hung with the graphics driver reporting DXGI_ERROR_DEVICE_HUNG. The game was already using 7.5 GB of the card at its lowest settings. The agent raised the shadows, the effects and the lighting for a still, held there for three shots at 7.6 GB, and on the fourth the card stopped answering. The driver reset twice, and at quarter past seven the machine restarted itself. The coordinator's ruling was that sky stills come from the bench, at moderate settings, and never from Epic in an editor that has been open all night.

James sent one direction for the aurora still that morning. It is to rise behind the houses, a low camera on a street facing north, rooftops and chimneys cut out black against the green, not a picture of open sky.

There was a second orange that day, from a different place. The performance thread tried switching ray tracing off, which saves about a gigabyte and roughly triples the frame rate, and the night street came back drenched in orange glow from the lamps. That one is not adopted.

Forty miles an hour

The wind now has five recorded layers, from a breeze to a howl round a building, all free recordings and all credited, rising and falling with the weather's own wind speed, with a muffled layer for indoors. The agent made a preview that swept from calm to gale and sent it to James. He said the wind sounds were good, and asked for it to go up to 40 miles an hour with stronger howling at the top. A recording that sounded like a howling gale turned out to be the wind battering the microphone, with almost all of its energy under 250 Hz, and went in the bin. The layers are mixed and credited, and wiring them into the game is next. So is raising the storms, which top out at about 29 mph today, to reach 40.

The buses were all maths. Route 4 crosses itself four times, straight over, with nothing in the road layout saying who gives way, and the first simulated run with three buses on it had two of them meet in the middle. The rule that came out of it books a stretch of road 120 metres ahead and holds a bus at the stop before a single-track section until the other one is clear. Three hours of simulation, three random runs, no contact. Three buses on that route also turn out to spend six or seven minutes of every lap standing at a stop waiting for each other, which is very British and too much of it.

One more thing stopped the agent that evening, and it was its own permission checker. It refused to act on the coordinator's word that James had approved a change, and acted once James typed the OK himself.

The fault

MetaHuman hair renders as a solid white sheet at Epic in Unreal Engine 5.8 (r.SkeletalMeshLODBias=1)

A resident's hair drew as an opaque, crumpled white shell round the back of his head, close up, at Epic only.

  • Not the cards material and not missing textures.
  • His LODSync component had 3 LODs with a custom mapping for Hair, Eyebrows, Mustache and Beard of (3, 5, 7). The groom's LODs are strands, strands, cards, cards, cards, mesh, mesh, mesh. Sync LOD 1 = groom LOD 5 = the mesh.
  • r.SkeletalMeshLODBias=1 in the Epic scalability section put a body at arm's length on sync LOD 1. The brows dropped to LOD 5 as well.

The fix

Drop the LOD bias; keep near sync LODs off the mesh

  • r.SkeletalMeshLODBias=1 removed from Epic. It saved about 230 MB.
  • A town subsystem, written the same night, sets every LODSync groom entry whose near sync LODs land on a mesh LOD to the first mapped LOD instead. Runtime only, no asset saves.
  • Guard: prove a hair setting with a close-up still of a named face, at the setting it ships at. Diagnosed on a blank map by A/B stills, one change per frame: default mapping = shell; forced LOD 0 = cards; mapping (3, 4, 5) = still a shell; mapping (3, 3, 4) = cards.

The fault

r.HairStrands.Enable=0 makes every MetaHuman bald, and UseCardsInsteadOfStrands draws nothing for grooms with no cards

Used to switch Epic from strands to hair cards, r.HairStrands.Enable=0 turned off the whole groom system in 5.8: strands, cards and meshes. The VRAM it "saved" (550-650 MB in one run, about 1.2 GB in another) was the cost of no hair. Its replacement, r.HairStrands.UseCardsInsteadOfStrands=1, draws nothing for a groom with no cards LOD: dense eyebrows, buzz and brush cuts, braids, stubble, moustaches, peach fuzz, lashes.

The fix

Cards per component, only where every visible strand LOD has a card version

Both global lines are out of the Epic settings. A world subsystem walks every GroomComponent every 2 s and sets bUseCards only when each visible strands LOD of each group has an imported cards mesh at that LOD, the same test the engine uses when it builds card data. Everything else keeps strands. A console switch turns it off, on or automatic by effects quality. Guard: a VRAM saving on hair is not a result until a face close-up at that setting shows hair, brows and lashes.

The fault

Niagara dry ice sprites facing +Z are invisible from standing eye height, and standalone screenshots show the intro camera

Floor-aligned sprites read as a flat carpet from above and almost vanish edge-on from a standing player. Separately, stills from a standalone -game crowd bench meant for the nightclub all showed the game's opening street scene: the intro camera took the view back after the bench moved it.

The fix

Camera-facing low bands; skip the intro on the bench

Camera-facing sprites with non-uniform size (220-380 by 30-55 cm), the alignment module disabled. The bench's club commands now run a skip-intro command before the shots. Next: a depth fade on the smoke material, so the floor stops cutting a straight edge through each band.

The fault

Orange glow on the horizon all night, cloud undersides lit from below

The day/night arc mirrored the day into the night, so with a 25-degree noon peak the sun sat only 12 to 25 degrees below the horizon all night and kept lighting clouds and low mist. Separately, an editor GPU hang (DXGI_ERROR_DEVICE_HUNG, in Niagara's GPU compute dispatch) at 7.5-7.6 GB of an 8 GB card while raising scalability for a still in PIE.

The fix

A separate night depth and a sun fade

A MaxNightDepth of 50 degrees for the night half; sun intensity times a fade from 1 at 4 degrees below the horizon to 0 at 8 below, with the lightning flash kept. Guard: stills come from a standalone run at moderate settings, never from Epic in a long-lived editor.

Plain English

TermWhat it means
groomHair built as individual strands rather than as a textured surface. Looks far better, costs far more to render.
hair cardsCheaper hair made of flat strips with a hair texture on them, instead of thousands of separate strands; a groom only has them if somebody made them.
MetaHumanEpic's system for realistic human characters - face, body, hair and clothing, ready to animate. Every named person in the game is one.
LODSimpler versions of a model, swapped in as it gets further away. The trick that lets a street hold hundreds of objects.
LOD SyncThe part of a character that keeps the body and its clothes at the same level of detail; if a garment has fewer levels than the body, it can stop following the body altogether.
LOD biasA setting that makes every character draw one level of detail simpler than it normally would, to save memory, whatever that simpler level happens to contain.
VRAMMemory on the graphics card. This project has 8 GB of it, and that ceiling shapes most of the decisions in the devlog.
scalability settingsThe engine's quality dials - shadows, textures, effects, anti-aliasing - each of which can be set independently. Turning one up can cost far more than turning the rest up together.
console variableA named engine setting that can be changed while the game runs, by typing it into the console, or fixed in a config file.
spriteA flat picture, such as a puff of smoke, drawn by a particle system - usually turned to face the camera so it never looks flat.
NiagaraUnreal's particle system - rain, smoke, sparks, dust, fizz on a pint.
depth fadeA material trick that fades a soft effect out where it meets solid ground, so a cloud of smoke never shows the straight line where the floor cuts through it.
standaloneRunning the game on its own with the editor closed, the way a player would. The only honest place to measure frame rate, because the editor eats memory and processor time of its own.
ray tracingWorking out light and shadow by firing rays through the scene on the graphics card; it looks right and it costs video memory for its own copy of the world.
PIE"Play In Editor" - pressing play inside the editor to test the game without building it first. Fast, and it behaves subtly differently from the real build.

Things we still need to fix tomorrow:

  • A close-up still of Dave with cards running is next, then the real VRAM price of cards at Epic.
  • Depth fade on the dry ice is next, then the beams through the haze.
  • Night stills from the bench: 9.30pm on Radworth Road, dusk lamps, and the aurora behind the rooftops.
  • Wiring the five wind layers into the game, and storms that reach 40 mph.
  • The bus rule into the bus Blueprint, and a decision on two buses or three for route 4.
  • The club washes are next to tie to opening hours.

← All devlog entries

Watch it get built. All of this goes up on YouTube as it happens - broken animations, buildings hovering a foot off the ground, the lot.

Subscribe on YouTube