Thimbleweed Park is available in ScummVM 🎉
I’m happy to announce the availability of Thimbleweed Park in ScummVM, it’s official now!

I’ve been working on integrating Thimbleweed Park into ScummVM.
I’d been approached a while back about integrating engge into ScummVM, but didn’t feel ready at the time.
I started working on this integration in December 2023 and regularly post my progress on youtube.
It all began with a POC (proof of concept):
See the full playlist.
Later, the ScummVM team warmly invited me to join their team, which I gladly accepted.
In the course of this work, I’ve been able to improve some of the shortcomings present in engge and engge2, and the game should now be faster in certain phases of the game (notably in the library), while the shader handling the lighting has also been improved + various small enhancements.
I hope this will please some of you, and don’t hesitate to leave me a comment if you wish.
More in the next episode.
]]>During this year I worked on 3 projects:
a Thomson MO5 Emulator
NES emulator
a rewrite of Another World (game by Eric Chahi)

The Thomson MO5 was my first computer. It had a Motorola 6809E processor clocked at 1 MHz, so I could play with it a bit, but it also gave me an early start in programming.
With a touch of nostalgia and thanks to the magnificent chips project, I’ve been able to create a MO5 emulator that can be used online here.
It allows me to play the various games I grew up playing.
I’d always wanted to create an NES emulator, but I’d never taken the time to do it. That desire came back recently when I got my hands on the chips.
I had a lot of fun recreating this console and, above all, adding the tools that revolve around it: a color palette viewer, sprite viewer, memory viewer and so on.


In the same vein, I’ve written a program that lets you play Another World directly from your browser here.
To play it, you’ll need to zip up the game Another World and drop it into your browser.
First of all, a word about Another World, the cult game created by Eric Chahi in 1991, an action/adventure game that made a big impression at the time thanks to its graphics and, above all, its animations and cinematic scenes.
As the game is really interesting to study, I wanted to include a debugger to help you better understand how it works.
]]>First I want to share my evil plan to accomplish my goal:
Well, this is something I wanted to try for a longtime, I like the principle to have a simple entity which can be extended with no limit without refactoring all my code.
I played with engge with Squirrel, but I wanted to try LUA for 2 reasons:
From my point of view, it’s not interesting when you define the game logic, to have the animations defined in code. So this is my decision to seperate it and to create JSON files. About JSON, I found it enough human readable and easy to manipulate.
Recompiling and launching the game is a long process. When you are designing your game, UI, enemy AI and so on you have start this process again and again, with scripting it’s so easy to modify the code and restart the engine without restarting the executable.
If I want to use hot reloading I need to minimize the C++ code. If I’m able to code almost all the game logic in LUA, then it will be easier to use hit reloading. So the question I need to ask myself anytime I add code in C++ is: can I do it in LUA instead ?
]]>So why not recreate the famous R-Type by Irem (Arcade-version)?
Wow, wow, slowly, I will try to create the first level, and I’ll do what I can, no promise done, you’re warned.

DISCLAIMER I’m not a game developer. Hum? OK, I do develop games but this is just a hobby. I’m not a professional, take my advice as is.
My first intuition was to search on Internet if someone has already made this level for me (Yes, I know I’m a bit lazy). Unfortunately, I found nothing, so why not learning how to do it myself?
To create the level, we need to capture the screenshots of the game.
I found on a forum someone who created a modified version of MAME which allows saving the tiles and palettes: mamed_ts.
An you’ll get 13 stripes (128 × 2048 pixels), click me to show 1 stripe.
Now I hope you like jigsaws ;)
Then use Tiled; if you don’t know Tiled, it’s an amazing tool to create your level based on tiles, go and try it.
The first step is to create bigger tiles from the little ones.

Then when all big tiles have been made, we can create a spritesheet with all of them.

The final step is to create the whole level just with this spritesheet, and you get this:

In the next episode we will start to write some code.
]]>Here is a video of the new room that we will create (without the dialogs, maybe it will be in another tutorial).
The steps to create the new room:
To accomplish our task, you will need theses tools:
ThimbleweedPark.ggpack1 file.Watch out! Before starting, make a copy of your original ThimbleweedPark.ggpack1 file
Run Texture Packer or use can use a free alternative like Free Texture Packer
Drag and drop all your images, you should have something like this
![]()
Be sure to validate this:
ScummBarSheet.json

Then click on Publish sprite sheet, you should have now 2 files: ScummBarSheet.json and ScummBarSheet.png
🥳 Congratulations 🥳, the first part is done.
Before creating the room you need to setup your environment, first we need to create a specific folder structure like this:

Create a file and name it ScummBar.wimpy and paste this:
{
"background": "background",
"fullscreen": 2,
"height": 172,
"name": "ScummBar",
"objects": [],
"roomsize": "{640,240}",
"scaling": [
"0.6@89",
"0.6@118"
],
"sheet": "ScummBarSheet",
"walkboxes": []
}
Then run WimpyViewer and drag and drop your wimpy file into it, you should see an empty room like this:

Go in the menu Edit and select New object... or use the shortcut N

Rename it scummBarDoor, don’t change the following properties.
And here starts the fun part.
Go in the menu Edit and select Edit Animations... or use the shortcut A, click on New... and type state0, start again and create a state1.

In Animation states window, you have all the images declared in your spritesheet.
Drag and drop from Animation states window obj_322 into state1, like this.

Nothing has changed, but… if you click in the background and press key 2, now you should see the door opened, cool no?

You need to change the property of the object to Prop in order to move the door to the correct position.

And now once again if you click in the background, you can test the different states by pressing key 1 and key 2, it will set the state of the door to state0 (closed) and state1 (door) respectively.

Now we will define the hotspot for this door, but first what is a hotspot? A hotspot is the rectangular area where the mouse cursor will detect the object, here the door.

First, we need to unset the object as a Prop. Why? Because if your object is a Prop you won’t be able to interact with this object in the game.
Then we can edit the hotspot by clicking one of the handles and you need to define the use position, this position specify where the actor will move if you click on this object.
Finally, we have to define the direction of the actor when he will reach the object’s position, in our example we will see the back of the actor, so we need to define the use direction to Back.

What is a walkbox?
A walbox is a polygon where an actor (a character of the game) is able to walk.
Go to the menu View and select Show walkbox Info... and click on the New walkbox button in the Walkbox Info window. And now go to the Edit menu and select Edit walkboxes or shortcut W.

Now you can edit the handle of the walkbox, but 4 points are not enough to create our walkbox, you will need to add other points, to do this click on the right mouse button where you want to insert your point in the walkbox. To remove a point, click on the right mouse button on a point.
You should now be able to create a walkbox like this:

Now save your room, and that’s it for wimpy!
Create a script file named ScummBar.nut with its minimal content:
ScummBar <-
{
background = "ScummBar"
enter = function(door)
{
}
exit = function()
{
}
}
A .nut file is a squirrel script file, squirrel is a language similar to lua.
In this file, we declare a table called ScummBar (the name of the room), we defined a property background set to ScummBar and 2 functions enter and exit, when the room becomes the current room, the enter function is called.
Now we want to be able to interact with the door, to do this we need to declare inside the room, a table, this table should be named scummBarDoor, the exact same name as we declare the door in the wimpy file.
scummBarDoor =
{
name = "door"
flags = DOOR_BACK
defaultVerb = VERB_OPEN
autoclose = YES
}
The property name defined the text that the cursor will display when it will be over the door’s hotspot.
flags set to DOOR_BACK indicates the direction of the cursor when it will be over it.
The property defaultVerb is handy to use a quickly an action, when you press the right mouse button button on the door, it will open it when defaultVerb is set to VERB_OPEN.
autoclose is specific to a door object, when an actor open the door, the door will be automatically closed after.
Now we need to define what to do if we execute the VERB_OPEN on this door, and what to do if we execute VERB_CLOSE, you can also add what to do when the actor walks directly to the door, and he would be useful to exit from the current room to go to the other one (in this example, we will go to the main street).
verbOpen = function()
{
openDoors(this, MainStreet.mainStreetDinerDoor)
}
verbClose = function()
{
closeDoors(this, MainStreet.mainStreetDinerDoor)
}
verbWalkTo = function()
{
exitRoomFromDoor(this, MainStreet.mainStreetDinerDoor)
}
If you want your player be able to talk to someone, just add a table to the “object” you want to talk to:
pirate4 =
{
name = "pirate"
flags = TALKABLE
defaultVerb = VERB_TALKTO
verbTalkTo = function() {
sayLine("I don't think it's wise to wake a sleeping pirate.")
}
}
You have to set the property flags to TALKABLE to be able to talk to this pirate, set the defaultVerb to VERB_TALKTO and now you can decide what to do when the actor talks to this pirate.
In this example, we want the actor to say “I don’t think it’s wise to wake a sleeping pirate.”.
Nice 😎
OK now you have enough information to add the other interactions. Here is my final script:
ScummBar <-
{
background = "ScummBar"
enter = function(door)
{
loopObjectState(chandelier, 0)
loopObjectState(chain, 0)
loopObjectState(fire, 0)
loopObjectState(pirate1, 0)
loopObjectState(pirate2, 0)
loopObjectState(pirate3, 0)
loopObjectState(pirate4, 0)
loopObjectState(pirate5, 0)
loopObjectState(pirate6, 0)
loopObjectState(pirate7, 0)
loopObjectState(pirate8, 0)
loopObjectState(pirate9, 0)
loopObjectState(dog, 0)
}
exit = function()
{
}
chandelier =
{
}
fire =
{
}
pirate4 =
{
name = "pirate"
flags = TALKABLE
defaultVerb = VERB_TALKTO
verbTalkTo = function() {
sayLine("I don't think it's wise to wake a sleeping pirate.")
}
}
pirate5 =
{
name = "pirates"
flags = TALKABLE
defaultVerb = VERB_TALKTO
verbTalkTo = function() {
sayLine("I don't want to bother them.","They're busy listening to the guy on the left.")
}
}
pirate8 =
{
name = "pirate"
flags = TALKABLE
defaultVerb = VERB_TALKTO
verbTalkTo = function() {
sayLine("I think they're busy.")
}
}
pirate9 =
{
name = "pirate"
flags = TALKABLE
defaultVerb = VERB_TALKTO
verbTalkTo = function() {
sayLine("I don't think it's wise to wake a sleeping pirate.")
}
}
dog =
{
name = "dog"
flags = TALKABLE
defaultVerb = VERB_TALKTO
verbTalkTo = function() {
sayLine(dog,"Grrrr.")
}
}
scummBarDockDoor =
{
name = "door"
flags = DOOR_BACK
defaultVerb = VERB_OPEN
autoclose = YES
verbLookAt = function()
{
sayLine("It's a door.")
}
verbPickUp = function()
{
sayLine("I can't! It's been glued to the floor.")
}
verbUse = function(obj=null)
{
sayLine("No way!")
}
verbWalkTo = function()
{
exitRoomFromDoor(this, Dock.dockScummBarDoor)
}
verbOpen = function()
{
openDoors(this, Dock.dockScummBarDoor)
}
verbClose = function()
{
closeDoors(this, Dock.dockScummBarDoor)
}
verbPush = function()
{
verbOpen()
}
verbPull = function()
{
noReach()
sayLine("There's no handle on the door.")
}
}
}
The first time, you’ll need to extract all the files from ThimbleweedPark.ggpack1 in a directory.
In this tutorial I will use ggpack tools:
ggpack -extract "*" ThimbleweedPark.ggpack1
Now in this directory, don’t forget to copy our new room files:
ScummBarSheet.jsonScummBarSheet.pngScummBar.wimpyScummBar.nut (rename it to ScummBar.bnut)We are almost done, we need to declare our room in the DefineRooms.bnut, add this statement:
include("ScummBar.nut")
defineRoom(ScummBar)
Now we want to start the game directly in the ScummBar, edit the boot.bnut and locate this code:
if (do_opening) {
g.openingScene = 1
startglobalthread(newOpeningScene)
return
}
Modify it to:
if (do_opening) {
g.openingScene = 1
//startglobalthread(newOpeningScene)
startglobalthread(enterScummBar)
return
}
And add this code before
script enterScummBar() {
actorSlotSelectable(ray, ON)
actorSlotSelectable(reyes, ON)
actorSlotSelectable(ON)
cameraInRoom(ScummBar)
actorAt(ray, ScummBar.scummBarDoor)
selectActor(ray)
cameraFollow(ray)
inputVerbs(ON)
inputOn()
}
it’s time to pack all files
ggpack -create "*" ThimbleweedPark.ggpack1
Watch out! Before executing the next step, make a copy of your original ThimbleweedPark.ggpack1 file
Move or copy this file to your game directory.
Et voilà, I hope you enjoyed it as mush as I did, don’t hesitate to share your remarks in the comments and please let me know if you create your own room, it would be amazing. Don’t be shy if you have any questions, I will try to answer them, or if you want more tutorials let me know.
]]>OK, this year I decided to change my blog and I start with a new theme: minimal-mistakes, fantastic no ?
You didn’t believe I was able to create a blog like this on my own and you’re right!! Why do I ask, I’m alone on this blog 😃
Hmmm, I didn’t write too much posts last year, so why not change this ? So what’s the plan this year about engge:

Before going deeper in all the explanations, I will describe how lights are defined in Thimbleweed Park.
Actually, all the lights are defined in the script files, directly in the room script.
More precisely they are defined in the enter function like this:
enter = function(enter_door)
{
setAmbientLight(0x77909d)
_lightObject1 = lightSetUp(0xfff892, 160, 278, 0.5, 180, 50, 0.15, 250, 0.75, 0, 97)
_lightObject2 = lightSetUp(0xfff892, 527, 278, 0.5, 180, 50, 0.15, 250, 0.75, 0, 97)
_lightObject3 = lightSetUp(0xfff892, 869, 278, 0.3, 180, 50, 0.15, 250, 0.75, 0, 97)
_lightObject4 = lightSetUp(0xfff892, 628, 185, 0.4, 180, 60, 0.25, 250, 0.65, 93, 100)
_lightObject5 = lightSetUp(0xfff892, 2133, 178, 0.4, 180, 90, 0.15, 250, 0.25, 0, 118)
_lightObject6 = lightSetUp(0xf78a00, objectPosX(aStreetTrashCan), 114, 0, 0, 270, 0.15, 80, 0.25, 76, 96)
_lightObject7 = lightSetUp(0xffed93, objectPosX(aStreetSaleSign), 114, 0.6, 0, 270, 0.15, 80, 0.25, 68, 97)
}
As you can see you can specify an ambient light color with setAmbientLight function.
This one is easy ;)
The following lines setup all the directional lights with an helper function lightSetUp defined in Helpers.nut.
Here the details of this function:
function lightSetUp(color, x, y, brightness, direction, angle, falloff, cutoffRadius, halfRadius, nearY = null, farY = null) {
local light = 0
light = createLight(color, x, y)
lightBrightness(light,brightness);
lightConeDirection(light,direction);
lightConeAngle(light,angle);
lightConeFalloff(light,falloff);
lightCutOffRadius(light,cutoffRadius);
lightHalfRadius(light,halfRadius);
if (nearY != null && farY != null) {
lightZRange(light, nearY, farY)
}
return light
}
I tried to find some information on internet how to create this lighting effect, I found of course this well-known blog: QuickiePal but there is no much explanation.
After several hours of searching, here is what I found interesting:
So I started with the project “cocos2d-x dynamic light tutorial”. The result is nice, it gives a 3D effect with an additional normal map to give for every sprite.
![]()
With this well-explained tutorial, I have all these properties resolved:
createLight(color, x, y)lightBrightness)lightCutOffRadius)lightHalfRadius)Then I tweaked the shader and C++ code to remove the 3D effect (normal map) and I added the other missing properties:
Here is the final result, not so bad, isn’t it ?

In the LightEffect::init, I added the missing properties and initialized the properties like this:
bool LightEffect::init()
{
if (initGLProgramState("pointlight.frag"))
{
setLightColor(cocos2d::Color3B::ORANGE);
setAmbientLightColor(cocos2d::Color3B(127,127,127));
setLightCutoffRadius(500.0f);
setLightHalfRadius(0.5f);
getGLProgramState()->setUniformFloat("u_coneCosineHalfConeAngle", cosf((M_PI/180.f) *(40.f / 2.f)));
getGLProgramState()->setUniformFloat("u_coneFalloff", 0.2f);
getGLProgramState()->setUniformVec2("u_coneDirection", Vec2(cosf((M_PI/180.f)*60),sinf((M_PI/180.f)*60)));
return true;
}
return false;
}
And the shader code:
#ifdef GL_ES
precision highp float;
#endif
varying vec2 v_texCoord;
uniform vec2 u_contentSize;
uniform vec3 u_ambientColor;
uniform vec2 u_spritePosInSheet;
uniform vec2 u_spriteSizeRelToSheet;
uniform vec2 u_spriteOffset;
uniform vec3 u_lightPos;
uniform vec3 u_lightColor;
uniform float u_brightness;
uniform float u_cutoffRadius;
uniform float u_halfRadius;
uniform float u_coneCosineHalfConeAngle;
uniform float u_coneFalloff;
uniform vec2 u_coneDirection;
void main(void)
{
vec4 texColor=texture2D(CC_Texture0, v_texCoord);
vec2 spriteTexCoord = (v_texCoord - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset; // [0..origSize]
vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, u_contentSize.y -pixelPos.y);
vec3 diffuse = vec3(0,0,0);
vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos.xy;
float coneValue = dot( normalize(-lightVec), u_coneDirection );
if ( coneValue >= u_coneCosineHalfConeAngle )
{
float intercept = u_cutoffRadius * u_halfRadius;
float dx_1 = 0.5 / intercept;
float dx_2 = 0.5 / (u_cutoffRadius - intercept);
float offset = 0.5 + intercept * dx_2;
float lightDist = length(lightVec);
float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
float falloffSelect = step(intercept, lightDist);
float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
float spotLight = u_brightness * falloffTerm;
vec3 ltdiffuse = vec3(u_brightness * falloffTerm) * u_lightColor;
float coneRange = 1.0-u_coneCosineHalfConeAngle;
float halfConeRange = coneRange * u_coneFalloff;
float conePos = 1.0-coneValue;
float coneFalloff = 1.0;
if ( conePos > halfConeRange )
{
coneFalloff = 1.0-((conePos-halfConeRange)/(coneRange-halfConeRange));
}
diffuse += ltdiffuse*coneFalloff;
}
vec4 finalCol = texColor;
if(finalCol.a == 0.0)
{
diffuse = vec3(0,0,0);
finalCol.rgb = texColor.rgb;
}
else
{
finalCol.rgb = finalCol.rgb * u_ambientColor;
}
gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
}
The next step will be to integrate this effect into engge.
In this animation, you can see a fire is crackling, and you can see the effect of this crackling on the face of the actors. Wow I can almost feel the warm of the fire.

How is it done ?
Well this effect is done with only these few lines:
script williesFireLighting() {
do {
lightBrightness(_lightObject6, random(0.4,3.0))
breakhere(2)
}
}
Magic isn’t it ?
This is a loop where every 2 frames, the brightness of the fire light is changed with a random value between 0.4 and 3.0, simple but clever.
]]>
This format can be represented by a json struture, here is an example, a lot of data have been removed to make it clear:
{
"actors": {
"bankmanager": {
"_costume": "BankMgrAnimation",
"_dir": 2,
"_lockFacing": 0,
"_pos": "{541,61}",
"_roomKey": "Bank",
"defaultVerb": 3,
"detective": 0,
"dialog": null,
// ...
},
},
"callbacks": {
"callbacks": [
{
"function": "analyticsCallback",
"guid": 8000217,
"time": 29920
}
],
"nextGuid": 8000218
},
"currentRoom": "TrailerAlley",
"dialog": {
"#ChetAgentStreetDialog14reyes": 2,
// ...
},
"easy_mode": 0,
"gameGUID": "",
"gameScene": {
"actorsSelectable": 1,
"actorsTempUnselectable": 0,
"forceTalkieText": 0,
"selectableActors": [
{
"_actorKey": "ray",
"selectable": 1
},
// ...
]
},
"gameTime": 29738,
"globals": {
"abducted_agent": {
"_actorKey": "ray"
},
"abducted_agent_seen": 1,
"act1": 0,
// ...
},
"inputState": 101,
"inventory": {
"slots": [
{
"jiggle": [
1,
0,
0,
0,
0,
0,
0,
0
],
"objects": [
"raysBadge",
"raysNotebook",
"countyMap1",
"cellPhone",
"fingerprintKit",
"speckOfDustRay",
"rayHotelKeycard",
"pillowTronTool"
],
"scroll": 0
},
// ...
]
},
"objects": {
"aStreetArcadeDoorWF": {
"flags": 1073742912,
"name": "@29000"
},
},
"rooms": {
"AStreet": {
"background": "AStreet",
"speck_of_dust": 1,
"speck_of_dust_collected": 1
},
},
"savebuild": 0,
"savetime": 1587988116,
"selectedActor": "ransome",
"version": 2
}
For each actor the properties are saved, there are 2 types of properties:
Callbacks are functions that can be called at a certain time of the game. These callbacks are added from a script file, for example:
addCallback(60*5, analyticsCallback)
With this example:
"callbacks": {
"callbacks": [
{
"function": "closeElevatorDelay",
"guid": 8000036,
"param": 5,
"time": 15186
}
],
"nextGuid": 8000218
},
function: name of the function to call when time is elapsedparam: optional parameter given to the function when it is calledguid: unique identifier of the callbacktime: time in ms to wait before calling the callbacknextGuid: specify the identifier to use for the next callbackAs you know, we have to save the state of the dialogs in order to know what choices have been displayed or chosen.
This state is described like this:
"dialog": {
"#ChetAgentStreetDialog14reyes": 2,
// ...
},
Each line corresponds to a condition of a dialog, the format can be described like this:
"[mode][dialog_name][line_number][actor]": [value]
mode: mode is one of the following value: ?: once #: showonce &: onceever $: showonceever ^: temponce
The goal of this blog is to explain what I discovered when building engge and how it works. Be kind, this is a work in progress and as you can read english is not my primary language :)
Oops I forgot to explain what is engge. engge is an adventure game engine and supports playing Thimbleweed Park.
The engine is written in C++ and is cross platform. You must have a legally purchased copy of Thimbleweed Park installed on your computer in order to run that game on this engine.
]]>First thing first, engge use Thimbleweed park ggpack files, theses files are archives and contains all the resources: images, scripts, text files, music, sounds etc.
The first thing to understand is Thimbleweed Park use squirrel scripts. Actually it uses a modified version of squirrel, why ? Because this language lacks some useful functionnalities like:
OK let’s go back to how Thimblewed Park starts:
When these scripts are executed, all the game is ready to start. The main function to start the game is called start:
function start(do_opening)
As you imagine if you call start with true as argument, then you will have the opening sentence played.
For engge, it’s a little bit different, the program checks first if the test.nut is present next to the executable and executes it, if not, it does the same thing as Thimbleweed Park, it will execute Defines.nut and Boot.nut scripts. This solution allows me to test my engines and start the game where I want.
Here is an example a script which allow to skip the opening and start the game with Ray in the main street:
include("Defines.nut")
include("Boot.nut")
inputOn()
inputVerbs(ON)
selectActor(ray)
enterRoomFromDoor(highwayMainStreetDoor)