# Engine

WARNING: This page was written from source code of Atari ST versions only. Check the ReDMCSB Release 3 source code for other versions.

## Game time

The game time is an integer and starts at 0. The game time is incremented by 1 tick when any of the following player activity takes place:

- Turn
- Move (if movement is not blocked, for example by a wall or a creature)
- Grab an object on the floor
- Put an object on the floor
- Throw the leader hand object (in Chaos Strikes Back for Atari ST version 2.1 English only, this was missing in previous versions)
- Click on a door button
- Click on a wall (only if at least one sensor was triggered)
- Trigger an action of the object in the action hand (only if the action was actually triggered)
- Cast spell (only if the spell cast succeeds)
- Opening or closing the inventory
- Wake Up

If the player does not perform any of the activities above for 11 vertical blank interrupt cycles (which occur at 60Hz for North American systems / 50Hz for European systems) then the game time is incremented.

## Mouse

Each time the user moves the mouse or clicks on a button, it triggers an interruption.
The exception handler `F0075_MOUSE_Exception70Handler_IKBD_MIDI_MouseStatus` updates the cursor coordinates if the mouse moved, and if the buttons status changed since the last interruption (meaning a button was pressed or released), it calls `S0076_MOUSE_OnMouseButtonsStatusChange`.
If the status changed because a button was released: if the eye or mouth was being pressed then a variable is set to stop pressing it.
If the status changed because a button was pressed: `F0359_COMMAND_ProcessClick_CPSC` is called.
The purpose of this function is to detect if the click occurred while the mouse pointer was in one of the active areas on screen. When this is the case, the command associated to the active area is added in the command queue (otherwise the click is ignored).
The game implements a semaphore to avoid concurrent accesses to the command queue: if the command queue is locked then the mouse click is buffered in dedicated variables for delayed processing until `F0360_COMMAND_ProcessPendingClick` is called. However because of a bug this feature is never used.

## Light management

The game engine draws the dungeon view on screen using one of 6 color palettes with various brightness levels to simulate various levels of lighting.
On dungeon maps where the 'Difficulty' value is 0 (like the 'Hall of Champions' in Dungeon Master and the first map of the 'Prison' in Chaos Strikes Back) the dungeon view is always drawn with the brightest color palette.
On dungeon maps where the 'Difficulty' value is not 0 (all other maps in the original dungeons) the dungeon view is drawn with the darkest color palette by default and the party has to produce its own light to have a better view. Light is produced by several sources and the total amount of light produced determines the color palette that the engine will use to draw the dungeon view on screen.

Each way to produce light has an associated 'LightPower', which is a value between 0 and 15. This value is used as an index in table `G0039_ai_Graphic562_LightPowerToLightAmount` to get the corresponding LightAmount value:

    LightPower (index in table): 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
    LightAmount (data in table): 0   5  12  24  33  40  46  51  59  68  76  82  89  94  97 100

Here is the list of all light sources with their associated LightPower and Duration:

- Object 'Torch'. LightPower is stored in the Torch object itself, ranging from 0 to 15. Duration: The LightPower of each Torch object held in hand is decreased by 1 (if not already 0) every 512 clock ticks in function `F0338_INVENTORY_DecreaseTorchesLightPower_CPSE`.
- Object 'Illumulet'. LightPower is 2. Duration: As long as worn around a champion's neck.
  `F0300_CHAMPION_GetObjectRemovedFromSlot`
  `F0301_CHAMPION_AddObjectInSlot`
- Action 'Light' on objects 'Yew Staff' and 'Sceptre of Lyf'. LightPower is 2. Duration: 2500 clock ticks
  `F0407_MENUS_IsActionPerformed`
- Spell 'Torch'. LightPower is 3, 4, 5, 6, 7 or 8 based on spell power. Duration: 2640, 3152, 3664, 4176, 4688 or 5200 clock ticks based on spell power
  `F0412_MENUS_GetChampionSpellCastResult`
- Spell 'Light'. LightPower is 3, 5, 7, 9, 11 or 13 based on spell power. Duration: 10000, 12048, 14096, 16144, 18192 or 20240 clock ticks based on spell power
  `F0412_MENUS_GetChampionSpellCastResult`
- Spell 'Darkness'. LightPower is 2, 3, 4 ,5 ,6 or 7 based on spell power. Duration: 98 clock ticks
  `F0412_MENUS_GetChampionSpellCastResult`

The engine stores the total sum of LightAmount produced by all light sources except 'Torch' objects in the variable MagicalLightAmount of the `G0407_s_Party` structure. LightAmount produced by magic is fully cumulative: the effects of several spells, 'Light' actions and 'Illumulet' objects are always added to variable MagicalLightAmount.
There is no variable storing the total LightAmount including the LightAmount produced by 'Torch' objects. Function `F0337_INVENTORY_SetDungeonViewPalette` computes this total.

The LightAmount produced by 'Torch' objects is computed in the following way:
The LightPower of all 'Torch' objects held in champion hands are stored in an array. The four highest values are in the first four entries, sorted from highest to smallest. The four smallest values are placed in the last four entries and are not sorted. Only the first five entries in the array are used and their corresponding LightAmount values are multiplied by modifiers before being added:
LightAmount of the Torch with the highest LightPower is multiplied by 1
LightAmount of the Torch with the 2nd highest LightPower is multiplied by 1/2
LightAmount of the Torch with the 3rd highest LightPower is multiplied by 1/4
LightAmount of the Torch with the 4th highest LightPower is multiplied by 1/8
LightAmount of the 5th Torch (whose LightPower is among the four smallest but not necessarily the highest of them) is multiplied by 1/16

The modified LightAmount of all 'Torch' objects are added together along with variable MagicalLightAmount and the resulting value is used with table `G0040_ai_Graphic562_PaletteIndexToLightAmount` to get the palette index to use to draw the dungeon view:
99 75 50 25 1 0

If the total LightAmount is 0 or negative, palette #5 (the darkest) will be selected.
If the total LightAmount is between 1 and 24, palette #4 will be selected.
...
If the total LightAmount is 99 or more, then palette #0 (the brightest) will be selected.

The only exception is for dungeon maps where the difficulty is 0 in which case the function does not care about LightAmount and always selects Palette #0 (the brightest).

Note:

- The function `F0446_STARTEND_FuseSequence` sets MagicalLightAmount to 200 to enforce the maximum brightness during the end of the game.
- The spells and the 'Light' action have limited durations. This is accomplished with events of type 70. These events are created by function `F0404_MENUS_CreateEvent70_Light`. They are processed by function `F0257_TIMELINE_ProcessEvent70_Light` which creates a fading effect by applying changes of LightPower one step at a time (every 4 clock ticks) until the LightPower reaches 0.
- When an event of type `C70_EVENT_LIGHT` is created, the map number in the event is set to the current map but this value is not used. The last two bytes of the event are not set and may contain any value present in the stack at the moment the event was created.

## Balance issues

A Rock shot with a Sling never moves farther than three squares, which is much less than when thrown by hand.

The Punch action can be abused because it is too fast. Most creatures can be killed by clicking repeatedly on this action.

Creatures that cannot see an invisible party never fight back when hit as long as the party is invisible.

## Limits

The game has many limits that are not be enforced or checked at runtime: the game simply assumes that these "rules" are followed. Going over these limits will often cause issues and crashes:

- 64 maps per dungeon. Map indices are stored in a 6 bits field in scents, which limits the practical number of maps to 64. Otherwise, the limit would be 255 because everywhere else map indices are stored in 8 bit fields.
- Total map data is limited to 65535 bytes (one byte for each square in each map plus one byte for each creature graphic, wall ornament graphic, floor ornament graphic and door ornament graphics.
- Maximum 60 groups on the party map. The game stores an additional data structure named ACTIVE_GROUP for each group on the same map as the party. There can be up to 60 of these structures. The functions to add/remove ACTIVE_GROUP to the groups do take into account the maximum number of ACTIVE_GROUP so that if there are more than 60 groups on the party map, some of them do not have an associated ACTIVE_GROUP. However, all functions needing to access the ACTIVE_GROUP structure for groups on the same map as the party assume that there is one. This would cause issues like reading or writing the ACTIVE_GROUP of another group or outside the bounds of the array of ACTIVE_GROUP (memory corruption).
- Only a single text thing on a square. Text things other than the first are ignored. Because of a bug, it is not possible to have different texts on each side of a wall square.
- Only a single teleporter thing on a teleporter square. Teleporter things other than the first are ignored.
- Only a single group thing on square.