Cyprus Comic Con 2026 Trailer

ABL Films sponsors Cyprus Comic Con every year with their entire media production and coverage. This year, we had the pleasure of creating a unique trailer for them: a Neo Geo-inspired pixel art trailer, produced entirely within the limitations of the classic 16-bit video game era. Produced natively in 384 x 216 resolution, with a limited palette of 66 colours, and absolutely no generative AI.

Between a constant live music stage, Cyprus's largest street food court, an artist alley with over 150 artists, wrestling, film festival and esports, cosplay competitions, an entire medieval tavern, CCC is a hugely varied event, and the challenge they have every year is how to convey the amount of stuff to experience. To solve this, we came up with the concept of an uninterrupted walk through the event spaces, starring Buggy Boop Boop, the CCC social media mascot.

We drew the base art by hand, with 67 unique sprites, walk cycles, backgrounds, eleven zones of the venue, all within the limitations of old-school gaming hardware, accompanied by custom era-appropriate music from an amazing local chiptune artist; and developed an extensive in-house software to put it all together, all within two weeks.

CHIEF CREATIVE AND DEVELOPER Marc Srour
ARTISTIC SUPPORT Gabi Necula
TECHNICAL SUPPORT Vladan Miladinovic
MUSIC Night Vendor
01

Behind The Scenes

We drew everything in Pro Motion NG, at the native size: the base sprites and their cycles, every head, every hair style, every garment cut, every prop from a violin to a wrestling belt, and every background across the eleven zones, down to the cart, the entrance arch, the ring and the tavern hearth.

Programmatically, one drawn cycle becomes 67 characters across four body builds, each with its own skin, hair, clothing, headgear and prop, and every one of them arrives re-posed, re-lit, outlined, dithered and palette-checked. The shading passes, the palette packing, the sky and the camera track were all done in code.

A head is a 12 × 12 pixel grid, one per hair style, drawn a pixel at a time. It is a third of the figure on screen, so it carries the crowd’s variety and its legibility at once, and the shader is forbidden to touch it.

A character is a row of eleven numbers, converted from our drawn images: skin ramp, hair style, hair ramp, top, bottom, garment cut, body build, outer layer, headgear, held prop, and which of the twenty-one animation programmes it runs. Walk, browse, sit, cheer, dance, vend, grill, violin, lute, sing, pose, cosplay, and the two halves of a wrestling slam. Adding somebody was as simple as clicking Randomise in our custom-built tool, placing them in the scene, and they arrive obeying every movement and animation rule the rest of the cast obeys.

This mix of artistry and tech is what made a crowd affordable to make at all. Drawing 67 characters at twelve frames each is 804 frames of animation, and a two-week schedule does not contain six weeks of drawing.

One row of data

On the left, the drawn part: a 12 × 12 head grid. On the right, the rig every background figure in the trailer runs, built live from the four ramps and the build you pick. Roll the dice the way we did. The count underneath is what that sheet would actually cost, against the fifteen colours a crowd sheet is allowed.

Skin
Hair
Top
Bottom
Build
View

 

 

02

Chartool

Chartool is where the art and the tech meet.

No available software on the market was satisfactory for our needs, so we built one that was. The art we drew in ProMotion NG becomes 67 characters and eleven zones inside it.

It started as a sprite viewer and finished as the place the whole trailer got assembled: nine tabs, 11,099 lines of Python, 158 sheets, and a 4,944-pixel world with 112 figures positioned and moving in it.

The Cast tab is the one worth understanding, and it is the piece of engineering here we are fondest of. It is the central tool we made to populate the crowd using modular parts, with handy randomisations possible for every parameter. All generated characters conform to the hard limits set on the rest of the trailer (15 colour cap per character, animation limitations), so consistency was maintained throughout.

The chartool scene tab.

The whole trailer as a live composite. Scrub the 50-second scroll on the timeline, click a figure in the frame to select it (alpha-precise, topmost wins), then drag it, retime its zone, push a parallax rate, or give it a drift in pixels per second so a cameo flies across the scene instead of standing in it. Save writes world.json and runs the scene validator on the way out.

Frame timing

The Frames tab's central idea, rebuilt here on npc-walk-a's twelve-frame walk. Each cell's width is that frame's hold and the gold marker is the playhead crossing them. Select a cell, change its hold, and watch the strip re-flow and the marker dwell.

Playback
Hold
Timings

 

03

66 global colours

We picked 66 colours by hand before drawing anything, and nothing in the two weeks that followed was allowed to add a 67th.

They are grouped into fifteen families that already run dark to light, so a surface gets shaded by stepping along its own family rather than by reaching for whatever looks about right. A character draws four of those ramps: skin, hair, top, bottom. True to the hardware, it then has to fit inside fifteen colours in total, outline and held prop included.

The master palette

All 66 down the left. Pick a character and the colours it spends light up while the rest go dark. Then squeeze that sheet below its own budget and watch the figure come apart, to see how colour economy really helped shape our artistic instincts.

Character

 

 

04

Background tiles

Backdrops take the same 15, one tile at a time.

Every parallax strip is cut into 16 × 16 tiles the way a Neo Geo tile map is, and no tile may spend more than fifteen colours, however many the strip carries across its full width. Nothing stores which tile uses which palette. The map below is derived from the pixels every time it is asked for, which is what keeps the check honest across a round trip through the editor.

Tile palettes

The z01-gate far strip under its 16 × 16 grid. Hover a tile, or focus the strip and use the arrow keys, to read that tile's palette. Its fullest tile is 14 colours.

 

 

05

Realism from four rendering passes

Every figure is drawn flat and gets its volume from four passes, in an analog of 3D rendering: relight, contact, dither, outline.

Relight. Every pixel counts how many pixels of its own material lie between it and the light, and that count picks a step down the material’s ramp. The count is a march: step one pixel toward the light, keep going while the material underneath stays the same, stop at seven.

The steps are uneven on purpose. Metal Slug, our gold standrad reference for pixel art quality, never shades evenly. It skips a tone at the turn into shadow and lets the dither pass cover for the one it skipped, and the plateau widths here come from referencing Metal Slug sprite sheets, which spend on average 13% of a figure’s area on lit tones, 35% on mids, and 52% on the two darks plus outline.

The march

Frame 7 of npc-walk-a exactly as the rig leaves it, 32 × 37 pixels, with every pixel's depth written inside it. Drag the magenta probe, or focus the grid and use the arrow keys, and the gold cells are the march that pixel walked. The head carries dots instead of counts because those twelve rows were drawn by hand and relight is forbidden to touch them. Turn the light and watch the near arm hold its own lit edge while the torso behind it re-shades.

 

Light direction

 

Contact. Where a near limb crosses the body, the covered surface drops two ramp steps along the edge facing away from the light. Dropping it to black instead put 18.5% of the figure in black when we measured it, which reads as a hole. Only the far side takes it: shade both and you ring the limb, making it look like a halo.

Dither. The pass that fakes the tone that relight skipped, checkering the two tones either side of the gap in antiphase. Dithering is foundational to pixel art, and here it runs directly off our lighting model.

Outline. A three-tone ring: near-black where the surface faces away from the light, the material’s own darkest tone where it faces into it, and one step up where it presents to the sky. The outward normal is accumulated from whichever four-neighbours are empty, dotted with the light direction, and ties go to shadow, which keeps the underside of a form reading as contact with the ground.

That third tone is why shoulders, hair mass and a forward thigh stop reading as flat-topped cutouts. Again, we counted the reference’s outline pixels by which direction they face, which means some ingenious artist at SNK worked it out by hand 30 years ago and we just implemented the feature in software.

The four passes

On the left, frame 8 of npc-walk-a exactly as it ships in the trailer. On the right, the same frame rebuilt in your browser from joint angles and flat material colour every time you touch a control. Turn a pass off and watch what it was carrying.

Passes
Light

depth steps 0,1,1,1,3,3,4,4

 

06

The walk cycles

The walk is 12 frames long, and we had to put it on 67 bodies. Fianlly a chnce to use school trigonometry.

Four curve tables drive it: thigh swing, knee flexion, pelvis lift, and shoulder counter-rotation against the hips. Angles are degrees from straight down, positive swinging forward, which puts sine on x and cosine on y and makes frame 0 right-foot contact. Two-bone forward kinematics run twice a leg a frame. The limb is then drawn between the joints as a tapered capsule rather than as a line, so the ramp resolves across its width and a thigh gets a lit edge, a core, and a shadow edge.

The amplitudes are set against Metal Slug 2 rather than against real life. Over its twelve frames, the hostage walk’s widest silhouette spans 34 pixels on a 38-pixel figure and its narrowest 19, and the shape changes frame to frame. A correct, naturalistic, small-amplitude walk at this size is a shuffling rectangle, so every curve here runs about 1.5 times a natural swing.

A body build is four multipliers, because the whole thing is geometry. Change the limb lengths and the knee still lands where a knee lands, and the hip is re-derived afterwards so the lowest foot reaches the row it reached before.

Standing and seated poses invert the problem. A stride is an angle curve; a figure standing still has planted feet and a pelvis that moves, so those programmes solve the chain backwards from the contact point with the law of cosines, and a fold sign picks which of the two answers to take, since a two-bone chain reaching a point can always bend either way. Out of reach it straightens toward the target rather than raising, because a build whose limbs are too short for the ground row should stand up tall.

Both ends of that need clamping. At exactly l₁+l₂ the argument to the arc cosine lands on 1.0, floating point occasionally puts it a hair past, and what comes back is a domain error rather than a straight leg. So reach is clamped a thousandth inside full extension, and the other end is clamped out of the hollow the chain cannot fold tight enough to reach.

Both directions

The same two bones, at the walk programme's real proportions. In forward mode drag either joint and the foot lands wherever the angles put it. In inverse mode drag the foot and the angles fall out of the arithmetic above. Push the foot past the dashed circle to see the degenerate case. Arrow keys work, and shift moves further.

 

Solver
Fold

 

The walk rig

The rig the trailer bakes from, on the trailer's own 32 × 37 canvas, solved live in browser. Drag AMPLITUDE down to 0.4 to meet the shuffling rectangle a naturalistic swing produces at this size. The head is the one part of the figure stamped in from a drawn grid, and the shader is not allowed near it.

Build
View

 

07

One camera move

50 seconds, 3,000 frames, no cuts, no fades, no transitions, and the camera never stops.

It crosses 4,560 pixels at an average of 91.3 px/s. Every zone holds its own customisable dwell time, so the track is one breakpoint per zone boundary, linear inside a segment, with no easing anywhere.

The track

World position against time. The dashed line is the average speed; the aqua polyline is what the camera actually does, with a corner at every zone boundary. Drag anywhere to scrub. The flat run at the right-hand end is the finale where Buggy Boop Boop stops.

 

 

Four layers scroll at different rates: far, mid, near, ground. Far and mid are per-zone backdrops, each drawn a full viewport wider than the zone it belongs to, sampled anchored to its own zone, then composited column by column, so every screen column shows the backdrop belonging to whichever zone owns that column’s world position. An enclosed hall’s rear wall therefore fills every column up to its on-screen exit, and the outdoor zone beyond it appears only where it actually is.

Four layers

The z03-ring zone, composited the way the engine does it. Scrub the camera, then push the far and mid rates off to play with the parallax.

Layers

 

Camera speed by zone, in pixels per second, against the 91.3 px/s average.

08

Day into night, in twenty steps

The sun goes down across the whole 50 seconds, and it is controlled by four numbers in every zone. Each one declares its sky at the top of the frame and at rows 54, 108 and 150.

Between stops those anchors ease in and out of each other, with zero slope at both ends so a zone boundary arrives without a kink in it, and that single curve is the entire mechanism by which morning becomes afternoon becomes sunset becomes night with no cut anywhere in the trailer.

The easing happens in floating point and the ramp is quantised to twenty colours, and only the seams between neighbouring levels are ordered-dithered, which is the trick old consoles used and the reason a Neo Geo sky reads as a gradient when it is twenty flat bands with checkered joins. Below row 150 the horizon colour goes flat, because that is where the buildings start.

20 came out of a measurement. At twenty levels with a seam half-width of 0.16, the rendered sky sits at 18.7% dithered blocks against Metal Slug 2’s sky's band of 16 to 20%, with a median dither pair gap of 7.4 against the reference’s 8. 20 is also the height-adjusted match for the reference’s 28 to 30 colours over a 240-row sky, this one running 150 rows to the horizon. Raise it to thirty and the steps land about two luminance apart while the density climbs to 29%.

The sky ramp

Scrub the trailer's 50 seconds. Drop LEVELS to 4 for the banding, then turn DITHER off to see the seams go hard.

 

Seams

 

09

Light in eight-luminance bands

Headlights, lamp pools, the esports wall, the stage rig. Every light in the piece is the same object: a smooth falloff, quantised into bands, dithered on one shared 4 × 4 ordered matrix.

The calibration constant is a luminance per band, 8.0, rather than a band count. A field is added to the frame as level times (R, G, B), so one step is worth the strength over the levels times the colour’s own luminance in the picture. Band on strength alone and the step is fixed in the mask instead, and the two agree only for white. A magenta rig light at the same strength banded at 3.8 luminance a step against a headlamp’s 7.9, one level further down, and that is the exact defect the constant exists to prevent. Coloured lights get fewer and coarser bands than a white one of the same strength.

Truncating a thresholded value dithers the whole range, so a light fades out by thinning rather than ending on a hard ring. The threshold comes off the ordered matrix phased by absolute canvas position rather than by each field’s own origin, which is what lets two overlapping lights share one checker phase instead of beating against each other where they cross.

Light bands

Hold the strength and switch the colour. The band count drops with the colour's luminance while the step luminance stays at 8.0.

 

Shape
Colour
Seams

 

A night street outside a purple cosplay hall, the white utility cart driving past with its headlights lit and two pools of light on the road ahead of it.
Frame 2240. The headlights on the cart the camera follows switch themselves on when the ambient light falls below 0.82, which happens 37.33 seconds in, somewhere behind the cosplay hall.
10

The sheets

Thirty of the finished sheets, playing at the timings they were authored with.

11
The venue entrance in morning light: a CYPRUS COMIC CON arch over a ticket hall, bunting strung across the forecourt, and the white utility cart driving in past two visitors.
z01-gate - 0.5s - the entrance, morning
An outdoor wrestling ring under floodlights, a wrestler mid-throw above the ropes, a packed crowd on the bleachers behind and hills on the horizon.
z03-ring - 13.0s - outdoor wrestling
A stone-walled tavern interior lit by a fire in a large arched hearth, with banners, a stained glass window, barrels and drinkers at the bar.
z05-tavern - 22.5s - Hall 4, reskinned as a medieval tavern
A neon-lit esports arena, a huge screen showing a falling-block game above rows of monitors, players at their stations and a Scorewarrior sign on the wall.
z07-esports - 30.8s - the arena, in passing
A darkened screening stage with a lit cinema screen behind red curtains, empty seating in front of it and exit signs glowing green on both walls.
z09-cinema - 40.8s - the screening stage
The night finale: a lit main stage with a band playing, speaker stacks either side, confetti and fireworks overhead and a crowd along the barrier.
z10-party - 47.5s - the finale, 26.8 pixels per second
12

The Codebase

35,447 lines.

The render engine that actually makes the frames is 1,985 lines across twelve files, under 6% of the total. The figure system is 9,055 lines, Chartool is 11,099, and the largest single file by a wide margin is worldgen.py at 6,764 lines, one function per zone composing stalls, tents, trusses, bunting and 3 × 5 pixel signage into four parallax strips. The engine stayed small because the hard parts were never in it. They were in making the art, and in enforcing the rules on it afterwards.

The map

One cell per file, width by line count on a square-root scale, because linear gives worldgen.py the whole bar and everything else a sub-pixel sliver. Hover or focus a cell to read it.

 

world.json

The scene document the trailer renders from, as it is written on disk. Eleven zones, their skies and dwell times, and every actor with its world position and animation programme. Scroll it.

Scroll

 

← Back to All News