All posts

Running Someone Else's Engine

The first ship was black. The GPU was idle. The cache downloaded 217 MB every time the program started. None of those were rendering bugs.

Engineers·10 min read

The first ship was black. The GPU was idle. The cache downloaded 217 MB every time the program started.

None of those were rendering bugs.

For engineers.

The first EVE ship EveLens ever managed to build with Trinity looked like this:

A perfect ship-shaped hole in the image.

The geometry was there. The silhouette was unmistakable. Running lights glowed exactly where they should. But every pixel belonging to the hull itself was #000000.

Black.

I increased the lights.

Black.

I changed the environment.

Black.

I verified the textures.

Black.

I checked the material parameters, rebuilt the ship, changed cameras, changed render targets, disabled pieces of the pipeline, and wrote increasingly specific test programs.

Still black.

It took nine investigation sessions to discover the problem.

The ship had been created at the wrong time.

Not incorrectly.

Not with invalid arguments.

Not through an unsupported API.

Just earlier in Trinity's startup sequence than EVE itself would ever create one.

That distinction ended up explaining most of this project.

01

The engine was open. The environment wasn't.

In July 2026, Carbon, the technology framework behind EVE Online, was released as open source. That included Trinity, its rendering engine, under the MIT license.

For someone building an EVE character tool, this was difficult to ignore.

EveLens already knew your characters and your ships.

Now the renderer used by EVE itself was sitting in a public repository.

The idea looked almost suspiciously straightforward:

Start Trinity
    ↓
Load EVE assets
    ↓
Build ship
    ↓
Apply SKINR materials
    ↓
Render
    ↓
Show image in EveLens

The individual pieces worked.

Trinity compiled.

Resources loaded.

Ships could be constructed.

Render targets produced pixels.

What I had underestimated was everything missing from that diagram.

Trinity does not normally wake up alone.

It wakes up inside EVE.

Before the first ship appears, the game and its surrounding infrastructure have already prepared a world for the renderer. Directories exist. Services exist. Globals have values. Resource paths have been configured. Devices have been selected. Caches have been prepared. Systems have initialized in an order accumulated over years of development.

None of that looks like part of the rendering API.

Until you remove it.

Then all of it becomes part of the rendering API.

That was the real project.

Not getting Trinity to render.

Getting Trinity to forget that EVE was supposed to be standing around it.

02

01. The black ship

The black ship was especially useful because it was not completely broken.

If nothing had rendered, the search space would have been enormous.

Instead, Trinity gave us a strangely precise failure:

geometry       ✓
camera         ✓
scene          ✓
render target  ✓
running lights ✓
hull lighting  ✗

That is the kind of bug that makes you believe the answer is close.

It wasn't.

Textures were downloaded correctly and matched what we expected.

The ship geometry was valid.

Material constants were populated.

The sun existed.

I pushed lighting values far beyond anything reasonable just to see whether the hull would react.

Nothing.

After enough experiments, the interesting question stopped being:

"Which rendering parameter is wrong?"

It became:

"What happened before the renderer got here?"

That turned out to be the right question.

Trinity normally starts as part of a much larger initialization sequence. A set of bindings needed by the ship had already been established by the time EVE's normal code path constructed it.

EveLens had changed that order.

The API calls still succeeded.

The ship still built.

No exception was thrown.

No warning appeared.

But some internal state resolved before the environment it depended on existed.

The resulting object remained broken for its entire lifetime.

Reordering initialization fixed the hull.

After nine sessions of shader experiments, texture checks, lighting probes, and render captures, the actual fix was effectively:

Do this later.

That was the first time the central lesson of the project became obvious.

The host application is part of the API

We tend to think of an engine's contract as the functions and classes it exposes.

That is only the explicit contract.

The implicit contract is much larger.

It includes things like:

this directory already exists

this global has already been populated

this subsystem starts before that subsystem

this resource path has already been registered

this device has already been selected

nobody constructs this object before this point

Inside the original application, these are not assumptions.

They are reality.

So there is little reason for the engine to defend against them.

When you remove the host application, assumptions become failure modes.

Usually silent ones.

03

02. The 52x performance fix hiding in a warning

Later, the renderer was working.

Ships loaded.

Materials looked right.

You could rotate them.

The feature was alive.

It was also awful to use.

Rotating a ship on a test build, I described it better than any profiler had:

"It's like it takes a picture and then shows me the picture."

Rotate.

Wait.

Image.

Rotate.

Wait.

Image.

A render pass was taking around 670 ms.

That was slow, obviously, but slow systems have a dangerous property: after working on them long enough, their numbers start to feel normal.

I had explanations.

Startup was complicated.

EVE assets were large.

Trinity was being hosted somewhere it was never designed to run.

There was integration overhead.

Probably.

Meanwhile, every time the renderer started, it printed this:

device HARDWARE unavailable - falling back to SOFTWARE

I had read that message many times.

The renderer did not crash.

The fallback succeeded.

The image was correct.

So at some point my brain had quietly classified it as informational.

"This environment must not support the hardware path."

Except there was a fairly large clue sitting under the desk.

The machine had a gaming GPU.

The same machine could run EVE itself at around 100 frames per second.

EveLens was rendering the same kind of assets using Microsoft's software rasterizer.

The GPU was practically a spectator.

The reason turned out to be a Trinity code path associated with tooling. In its original context, the behavior made sense. Tooling can run on build machines and other environments where a GPU cannot be assumed.

EveLens had wandered into that path on a machine where a GPU was very much available.

Once it stopped doing that, one of our first-boot measurements changed from:

294 seconds

to:

5.6 seconds

That is a little over 52 times faster.

The renderer had not been "a bit unoptimized."

It had been using the wrong class of hardware.

And the system had told me.

Every single time it started.

Successful fallbacks are dangerous

Crashes are loud.

Fallbacks are polite.

That makes fallbacks surprisingly good at hiding catastrophic performance problems.

If hardware initialization had failed and killed the process, I probably would have fixed the problem immediately.

Instead, another subsystem graciously stepped in and produced the correct output.

Slowly.

Correctness camouflaged the failure.

That changed the Studio itself.

The active rendering device is now visible in the status area. If Trinity is running in a degraded mode, I want to see it while developing and testing.

Not buried thirty lines deep in a log.

A fallback that makes something 20 times slower is not merely an implementation detail.

It is product state.

04

03. The 217 MB cache that successfully cached nothing

The next mystery looked like a networking problem.

The renderer needs EVE resources.

Geometry, textures, material data and the other pieces required to construct a ship are retrieved as needed.

The resource cache existed specifically so this should become cheaper after the first run.

And technically, it worked.

A request came in.

The resource downloaded.

Its checksum matched.

Trinity consumed it.

The ship rendered.

Everything looked correct.

Close EveLens.

Open it again.

Download the same resources.

Again.

More than 217 MB could move through the cache during a session and leave essentially nothing useful behind for the next one.

So I instrumented the cache.

Requests looked right.

Downloads looked right.

Writes appeared to happen.

No obvious errors.

The missing component was not in the cache code at all.

It was the filesystem around it.

Trinity expected a directory layout that, in its normal life, had already been created before the engine started.

The engine did not create that structure.

Why would it?

Inside EVE, it is always there.

The launcher and surrounding application infrastructure had fulfilled that contract long before the renderer cared about it.

Outside EVE, nobody had.

So the cache could correctly perform its part of a workflow whose precondition had never been satisfied.

Create the expected environment and suddenly the cache became a cache.

This same pattern appeared often enough that I eventually stopped asking:

"What does Trinity need?"

and started asking:

"What does Trinity assume somebody else already did?"

That question was much more productive.

Open source gives you the code, not the context

This was probably the biggest misconception I had going into the project.

Having the source feels like having the answers.

And it is enormously valuable.

But mature software contains history.

A function can make perfect sense locally while relying on a convention established fifteen years earlier somewhere completely different.

There are assumptions encoded not just in code, but in the architecture around the code.

The original application is effectively a second documentation layer.

Sometimes the only one.

When you host someone else's engine, you are not only integrating a library.

You are reconstructing an ecosystem.

05

04. Eventually I stopped debugging EveLens

Around this point the project changed shape.

Opening the full application, reproducing a problem, changing something, reopening it, and looking at the result was too slow.

Worse, there were too many variables.

Was the ship brighter because the lighting fix worked?

Or because another initialization step changed?

Did hardware rendering start because of the device change?

Or because something else in the same run happened to put Trinity into a different state?

A visual renderer is particularly good at giving you results that look persuasive.

So I started writing probes.

Lots of them.

A probe does one thing.

Start the minimum amount of Trinity necessary.

Construct one controlled condition.

Ask one question.

Record the answer.

Exit.

The probe directory eventually passed two hundred scripts.

That sounds excessive until you encounter a renderer producing results you cannot explain.

Then it starts feeling conservative.

Positive tests lie

The biggest improvement was not probes themselves.

It was negative controls.

Suppose I believed initialization order caused the black hull.

After changing the order, the hull rendered correctly.

Great.

But that only established:

change happened
+
image is now correct

It did not establish:

change caused image to become correct

So I deliberately reproduced the previous condition.

If my explanation was correct, the failure should return.

It did.

That failure was more convincing than the successful render.

The same thing happened with the hardware device fix.

New path: hardware renderer.

Explicit corrected path: hardware renderer.

Old path used as the control: same software fallback as before.

Perfect.

Now we had a mechanism, not just an improvement.

One probe taught this lesson particularly painfully.

I wrote a six-direction lighting sweep.

The output was beautifully consistent.

+x  0 lit pixels
-x  0 lit pixels
+y  0 lit pixels
-y  0 lit pixels
+z  0 lit pixels
-z  0 lit pixels

Stable.

Repeatable.

Very convincing evidence of a lighting problem.

Except the ship had never been added to the scene.

The renderer was correctly reporting that zero pixels of a nonexistent ship were illuminated.

The measurement was flawless.

The experiment was nonsense.

Every relevant probe now verifies that the object being measured actually exists before trusting the result.

Then the renderer started flipping a coin

The worst investigation involved nondeterminism.

Same executable.

Same arguments.

Same design.

Same machine.

Same inputs.

Run one:

mean luminance ≈ 66

Run two:

mean luminance ≈ 40

Run again:

66.

Again:

40.

Not gradual variation.

Two different visual states.

Debugging deterministic software lets you move backward from cause and effect.

Nondeterminism takes that comfort away.

So there was no clever breakthrough.

There were hypotheses.

Then probes.

Then dead hypotheses.

One by one.

Six probes eventually killed five explanations and uncovered an unrelated real bug along the way before the actual mechanism became clear.

It was slow, boring, disciplined work.

And that is precisely why it worked.

The project left me with a rule I now trust much more than I used to:

A fix you cannot falsify is a coincidence you have not caught yet.

If your experiment cannot prove you wrong, it is not yet a useful experiment.

06

"Their gold looks richer than ours"

Not every useful measurement came from instrumentation.

One comparison produced a much less technical bug report, from my own notes:

"Their gold looks richer than ours."

We had the same ship open in EVE's studio and EveLens.

Our render looked good.

If you saw it by itself, you might have called it finished.

Then you put the two next to each other.

EVE's gold looked like gold.

Ours looked like something pretending to be gold.

Slightly flatter.

Slightly duller.

Just wrong enough that once you saw it, you could not unsee it.

There was no crash.

No warning.

No incorrect texture checksum.

No obviously broken constant.

But for a SKINR viewer, this was not cosmetic polish.

It was correctness.

The entire promise of the feature is:

This is what your design looks like on the ship.

"Almost" is not good enough when appearance is the product.

So "the gold feels wrong" became another engineering investigation.

Material behavior was isolated.

Lighting changed.

Reference images were compared.

Constants were inspected.

Different ships and environments were tested.

One successful coating was not allowed to prove that all coatings were correct.

Eventually the verification work covered 444 nanocoating materials.

That process reinforced something the 52x bug had already suggested.

Feel is data

"It's like it takes a picture."

That turned into a 52x startup improvement and a renderer using the correct hardware.

"The gold looks richer."

That exposed a material fidelity problem.

Engineers naturally prefer bug reports containing reproduction steps and numbers.

But users experience systems before they measure them.

"It feels slow."

"The animation feels strange."

"The lighting looks flat."

"The ship feels plastic."

Those are observations.

They are simply encoded in human perception.

The engineering work is translating them into something measurable.

For visual software in particular, the person saying "this feels wrong" may be handing you the first line of a stack trace.

They just do not speak the rest of its language.

07

So what actually shipped?

After all of this, the renderer became the engine behind SKINR Studio in EveLens 1.5.0.

It can render EVE ships locally with their SKINR designs.

Your character's collection can be explored in 3D.

Paragon Hub listings can be staged on the ship before you own them.

Lighting environments can be changed live.

Photo Mode removes the interface.

Photo Op can put up to ten ships together for a formation shot.

And the renderer itself remains an optional component.

EveLens is still a character tool first. If you never install the rendering component, the rest of the application continues to work.

Which is a remarkably ordinary outcome for something that began with a perfectly black spaceship.

08

The code was the easy part

Not literally.

Trinity is a substantial renderer and understanding it required plenty of time in unfamiliar code.

But source access was not the part that surprised me.

The surprise was everything outside the source I thought I needed.

A renderer that has spent years inside a game develops a habitat.

The launcher creates things for it.

The game initializes things for it.

Other systems register things for it.

Filesystem conventions exist around it.

Startup order protects it from states nobody bothered handling because those states cannot happen in the original application.

Logs contain fallbacks that are perfectly reasonable in one environment and catastrophic in another.

Eventually those assumptions become invisible, even in the codebase itself.

Until somebody takes the engine somewhere else.

Then you discover them one at a time.

Sometimes as a black ship.

Sometimes as 294 seconds of startup.

Sometimes as 217 MB that refuses to stay cached.

Sometimes because you rotate a ship and catch yourself thinking:

"It's like it takes a picture."

That is what I thought this project would be about:

How do I render an EVE ship?

The much more interesting question turned out to be:

What does EVE do before it ever asks Trinity to render one?

Answering that question was most of the work.

And probably the most interesting engineering EveLens has required so far.

EVE Online and all related logos, names, images, assets, and trademarks are the property of CCP hf. EveLens is a third-party application and is not affiliated with or endorsed by CCP Games.