Updates to match JoeyLib.

This commit is contained in:
Scott Duensing 2026-10-19 15:47:28 -05:00
parent 2d10c1eb2b
commit 850181d8a9
17 changed files with 131 additions and 131 deletions

View file

@ -51,7 +51,7 @@ and speed on the 8 MHz machines.
|------|--------------| |------|--------------|
| `tests/hostPort.c`, `tests/agiHost.c` | The whole interpreter on the build machine (JoeyLib's BLANK port): virtual 60 Hz time, a scripted keyboard, every frame resolved through the palette and hashed (FNV-1a) or saved as PPM. Fully reproducible. | | `tests/hostPort.c`, `tests/agiHost.c` | The whole interpreter on the build machine (JoeyLib's BLANK port): virtual 60 Hz time, a scripted keyboard, every frame resolved through the palette and hashed (FNV-1a) or saved as PPM. Fully reproducible. |
| `tests/agiGame.py`, `tests/probes.py` | Builds AGI games from scratch (LOGIC assembler from the spec's command table, PICTURE byte lists, VIEW from text art, SOUND, WORDS.TOK, OBJECT). Each probe records what the interpreter did cycle by cycle into variables and prints them, so its frames do not depend on timing. | | `tests/agiGame.py`, `tests/probes.py` | Builds AGI games from scratch (LOGIC assembler from the spec's command table, PICTURE byte lists, VIEW from text art, SOUND, WORDS.TOK, OBJECT). Each probe records what the interpreter did cycle by cycle into variables and prints them, so its frames do not depend on timing. |
| `scripts/oracle.sh` | Runs a probe (or a KQ3 session) on Sierra's interpreter in DOSBox under a private Xvfb, driven by xdotool, and on JoeyAGI, then compares every shot with `tests/tools/compareFrames.py` (EGA colours, text read with each interpreter's own font -- JoeyAGI's font is its own -- and Sierra's 40-column text mode compared cell by cell). A probe whose script says `record` is recorded instead: DOSBox's video capture (every 70 Hz frame) on Sierra's side, every frame on JoeyAGI's, compared by `tests/tools/shakeMeasure.py`. | | `scripts/oracle.sh` | Runs a probe (or a KQ3 session) on Sierra's interpreter in DOSBox under a private Xvfb, driven by xdotool, and on JoeyAGI, then compares every shot with `tests/tools/compareFrames.py` (EGA colors, text read with each interpreter's own font -- JoeyAGI's font is its own -- and Sierra's 40-column text mode compared cell by cell). A probe whose script says `record` is recorded instead: DOSBox's video capture (every 70 Hz frame) on Sierra's side, every frame on JoeyAGI's, compared by `tests/tools/shakeMeasure.py`. |
| `scripts/test-agi.sh` | The host gate: resources, probes and KQ3 sessions against `tests/expected/`. | | `scripts/test-agi.sh` | The host gate: resources, probes and KQ3 sessions against `tests/expected/`. |
A caution learned on the way: DOSBox's screenshot key (Ctrl+F5) reaches the A caution learned on the way: DOSBox's screenshot key (Ctrl+F5) reaches the
@ -63,7 +63,7 @@ checkpoints below were each taken with a single screenshot.
The interpreter at the start (commit 96c72ca) was a VM/host split with many The interpreter at the start (commit 96c72ca) was a VM/host split with many
pieces stubbed: `sound` set its flag at once; `save.game`, `restore.game`, pieces stubbed: `sound` set its flag at once; `save.game`, `restore.game`,
`status`, `show.obj`, `menu.input`, `obj.status.v` did nothing; `status`, `show.obj`, `menu.input`, `obj.status.v` did nothing;
`restart.game` quit; object collisions and `block` were not modelled; `restart.game` quit; object collisions and `block` were not modeled;
`posn` tests were approximations; ego stepped a fixed 2 pixels; and an argv `posn` tests were approximations; ego stepped a fixed 2 pixels; and an argv
"starting room" let you skip "a title screen the VM can't yet advance "starting room" let you skip "a title screen the VM can't yet advance
through". The IIgs disk gate passed, but on the IIgs the old engine was the through". The IIgs disk gate passed, but on the IIgs the old engine was the
@ -71,7 +71,7 @@ one drawing.
Nearly every module was rewritten (agi.c, agiRes, agiPic, agiView, agiObj, Nearly every module was rewritten (agi.c, agiRes, agiPic, agiView, agiObj,
agiText, agiVm) or written new (agiGfx, agiFont, agiInput, agiSound, agiText, agiVm) or written new (agiGfx, agiFont, agiInput, agiSound,
agiSave), each behaviour settled against the specifications and then against agiSave), each behavior settled against the specifications and then against
Sierra's interpreter. Sierra's interpreter.
## Subsystems ## Subsystems
@ -97,7 +97,7 @@ Sierra's interpreter.
| have.key | Works: a key this cycle that nothing else took; a logic that asks again in the same cycle (KQ3's help screen spins on it) waits a frame per ask and takes any key | `pictures`, `priority`, `textscreen`, `ego` probes; `screenshots/kq3-help-f1.png` | | have.key | Works: a key this cycle that nothing else took; a logic that asks again in the same cycle (KQ3's help screen spins on it) waits a frame per ask and takes any key | `pictures`, `priority`, `textscreen`, `ego` probes; `screenshots/kq3-help-f1.png` |
| Save / restore / restart | Work: restore rebuilds the picture from the script and ends the cycle so logic 0 sees f12 next (KQ3 relies on it); restart keeps menus and set.key mappings (KQ3 builds them only once) | `kq3-saverestore` session; restart + menu matches Sierra | | Save / restore / restart | Work: restore rebuilds the picture from the script and ends the cycle so logic 0 sees f12 next (KQ3 relies on it); restart keeps menus and set.key mappings (KQ3 builds them only once) | `kq3-saverestore` session; restart + menu matches Sierra |
| Memory | Logics other than 0 are freed at new.room and so are the views no object shows, as Sierra frees a room's resources | ASan run of every session clean | | Memory | Logics other than 0 are freed at new.room and so are the views no object shows, as Sierra frees a room's resources | ASan run of every session clean |
| Picture output | Every machine pushes a picture rectangle with one jlSurfacePasteIndexed call, each AGI pixel doubled (colour runs through jlFillRect on the planar machines before, two jlDrawPixel calls per AGI pixel before that) | IIgs frame outside a moving object byte-identical to the old code; JoeyLib's UBER indexed checks on every port | | Picture output | Every machine pushes a picture rectangle with one jlSurfacePasteIndexed call, each AGI pixel doubled (color runs through jlFillRect on the planar machines before, two jlDrawPixel calls per AGI pixel before that) | IIgs frame outside a moving object byte-identical to the old code; JoeyLib's UBER indexed checks on every port |
## Bugs found and fixed in this dive, with causes ## Bugs found and fixed in this dive, with causes
@ -110,7 +110,7 @@ Sierra's interpreter.
moved into the jlAlloc'd state). moved into the jlAlloc'd state).
2. **ST, Amiga, X68000: picture output.** Planar stages have no chunky bytes, 2. **ST, Amiga, X68000: picture output.** Planar stages have no chunky bytes,
and the fallback drew two jlDrawPixel calls per AGI pixel. Then runs of and the fallback drew two jlDrawPixel calls per AGI pixel. Then runs of
one colour per row through jlFillRect. (An 8x8 tile path was tried first one color per row through jlFillRect. (An 8x8 tile path was tried first
and came out striped: jlTileT's bytes are in each port's own layout.) and came out striped: jlTileT's bytes are in each port's own layout.)
Since 2026-10-17 every machine pastes the rectangle with Since 2026-10-17 every machine pastes the rectangle with
jlSurfacePasteIndexed (doubleWide), widened to whole 2-pixel pairs: IIgs jlSurfacePasteIndexed (doubleWide), widened to whole 2-pixel pairs: IIgs
@ -118,8 +118,8 @@ Sierra's interpreter.
drawn. drawn.
3. **Text attributes.** set.text.attribute in picture mode stores a background 3. **Text attributes.** set.text.attribute in picture mode stores a background
of white (or black); text.screen clears to that; on the text screen the of white (or black); text.screen clears to that; on the text screen the
colours are used as given; clear.lines and clear.text.rect on the text colors are used as given; clear.lines and clear.text.rect on the text
screen take the colour as a PC attribute byte (so 0..15 clear to black). screen take the color as a PC attribute byte (so 0..15 clear to black).
The old rule (background | 8) was wrong. The old rule (background | 8) was wrong.
4. **Sound on at start.** Sierra's interpreter sets f9 itself (its status line 4. **Sound on at start.** Sierra's interpreter sets f9 itself (its status line
reads "Sound:on" in a game that never touches f9). reads "Sound:on" in a game that never touches f9).
@ -236,14 +236,14 @@ KQ3's own version, shown by the game). At startup `detectVersion`
(agiRes.c) reads the version from AGIDATA.OVL, else from the AGI file, else (agiRes.c) reads the version from AGIDATA.OVL, else from the AGI file, else
takes 2.435, the version measured, into `game.interpreterVersion`; takes 2.435, the version measured, into `game.interpreterVersion`;
`agiQuirk()` answers from one table of version ranges (`kQuirkVersions`), `agiQuirk()` answers from one table of version ranges (`kQuirkVersions`),
the one place per-version behaviour is decided. The IIgs disk now carries the one place per-version behavior is decided. The IIgs disk now carries
AGIDATA.OVL, and `resources-kq3` checks KQ3 reads as 2435. AGIDATA.OVL, and `resources-kq3` checks KQ3 reads as 2435.
Only one Sierra interpreter, 2.435, was at hand, so whether the quirks Only one Sierra interpreter, 2.435, was at hand, so whether the quirks
differ between versions could not be measured: all three are on for 2.000 differ between versions could not be measured: all three are on for 2.000
to 2.999 (every version JoeyAGI plays), and narrowing a range is a one-line to 2.999 (every version JoeyAGI plays), and narrowing a range is a one-line
change once another interpreter is measured. Off, each falls back to the change once another interpreter is measured. Off, each falls back to the
plain behaviour (a clipped window, an echo from the end of the typed text, plain behavior (a clipped window, an echo from the end of the typed text,
an exact line). an exact line).
- **AGI_QUIRK_WINDOW_BYTE_X -- print.at at column 0 or 1.** A window's box - **AGI_QUIRK_WINDOW_BYTE_X -- print.at at column 0 or 1.** A window's box
@ -261,7 +261,7 @@ an exact line).
the box and its top, bottom and left lines are drawn a row down, 91 in, the box and its top, bottom and left lines are drawn a row down, 91 in,
and the right side and the text stay in place. Columns 2 and on are not and the right side and the text stay in place. Columns 2 and on are not
affected. `winedge` probe: nine windows at columns 0 to 3, one to three affected. `winedge` probe: nine windows at columns 0 to 3, one to three
lines, on a coloured picture, open and closed: 18/18 frames identical. lines, on a colored picture, open and closed: 18/18 frames identical.
- **AGI_QUIRK_ECHO_STEPS_BACK -- set.cursor.char mid-line.** The input line - **AGI_QUIRK_ECHO_STEPS_BACK -- set.cursor.char mid-line.** The input line
echo keeps its own position: a redraw (Enter, accept.input) leaves it echo keeps its own position: a redraw (Enter, accept.input) leaves it
after the prompt, the text and the cursor; with a cursor set, a typed after the prompt, the text and the cursor; with a cursor set, a typed
@ -309,7 +309,7 @@ an exact line).
Every AGI v2 command is implemented. "KQ3 uses" counts the command in KQ3's Every AGI v2 command is implemented. "KQ3 uses" counts the command in KQ3's
logics; "Probe" marks commands the probe games exercise against Sierra's logics; "Probe" marks commands the probe games exercise against Sierra's
interpreter (everything KQ3 uses is also exercised by the KQ3 sessions or interpreter (everything KQ3 uses is also exercised by the KQ3 sessions or
checkpoints). FIXED marks behaviour corrected in this dive's later half; checkpoints). FIXED marks behavior corrected in this dive's later half;
the earlier rewrite touched nearly every command. the earlier rewrite touched nearly every command.
| # | Command | KQ3 uses | Probe | Status | | # | Command | KQ3 uses | Probe | Status |
@ -419,7 +419,7 @@ the earlier rewrite touched nearly every command.
| 0x66 | print.v | 27 | | works | | 0x66 | print.v | 27 | | works |
| 0x67 | display | 70 | yes | works | | 0x67 | display | 70 | yes | works |
| 0x68 | display.v | 5 | | works | | 0x68 | display.v | 5 | | works |
| 0x69 | clear.lines | 17 | yes | works; FIXED (text-screen colour is an attribute byte) | | 0x69 | clear.lines | 17 | yes | works; FIXED (text-screen color is an attribute byte) |
| 0x6A | text.screen | 3 | yes | works; FIXED (clears to the attribute background) | | 0x6A | text.screen | 3 | yes | works; FIXED (clears to the attribute background) |
| 0x6B | graphics | 3 | yes | works | | 0x6B | graphics | 3 | yes | works |
| 0x6C | set.cursor.char | 1 | yes | works (no cursor before it: FIXED; changed mid-line moves the echo as Sierra's: FIXED) | | 0x6C | set.cursor.char | 1 | yes | works (no cursor before it: FIXED; changed mid-line moves the echo as Sierra's: FIXED) |

78
agi.c
View file

@ -43,7 +43,7 @@
#define STATUS_TEXT_MAX 160u #define STATUS_TEXT_MAX 160u
// The AGI palette: EGA's sixteen colours in JoeyLib's $0RGB. // The AGI palette: EGA's sixteen colors in JoeyLib's $0RGB.
static const uint16_t kAgiPalette[AGI_COLOR_COUNT] = { static const uint16_t kAgiPalette[AGI_COLOR_COUNT] = {
0x000u, 0x00Au, 0x0A0u, 0x0AAu, 0x000u, 0x00Au, 0x0A0u, 0x0AAu,
0xA00u, 0xA0Au, 0xA50u, 0xAAAu, 0xA00u, 0xA0Au, 0xA50u, 0xAAAu,
@ -82,16 +82,16 @@ static void changeRoom(void) {
vmDiscardLogics(); vmDiscardLogics();
agiViewDiscardUnused(); agiViewDiscardUnused();
saveScriptClear(); saveScriptClear();
gAgi->playerControl = true; gAgi->playerControl = true;
gAgi->blockActive = false; gAgi->blockActive = false;
gAgi->horizon = AGI_DEFAULT_HORIZON; gAgi->horizon = AGI_DEFAULT_HORIZON;
gAgi->vars[VAR_PREV_ROOM] = gAgi->vars[VAR_ROOM]; gAgi->vars[VAR_PREV_ROOM] = gAgi->vars[VAR_ROOM];
gAgi->vars[VAR_ROOM] = gAgi->newRoomNumber; gAgi->vars[VAR_ROOM] = gAgi->newRoomNumber;
gAgi->vars[VAR_OBJ_EDGE_OBJ] = 0u; gAgi->vars[VAR_OBJ_EDGE_OBJ] = 0u;
gAgi->vars[VAR_OBJ_EDGE] = 0u; gAgi->vars[VAR_OBJ_EDGE] = 0u;
gAgi->vars[VAR_BAD_WORD] = 0u; gAgi->vars[VAR_BAD_WORD] = 0u;
ego = &gAgi->objects[AGI_EGO]; ego = &gAgi->objects[AGI_EGO];
gAgi->vars[VAR_EGO_VIEW] = ego->viewId; gAgi->vars[VAR_EGO_VIEW] = ego->viewId;
switch (gAgi->vars[VAR_EGO_EDGE]) { switch (gAgi->vars[VAR_EGO_EDGE]) {
case AGI_EDGE_TOP: case AGI_EDGE_TOP:
ego->y = AGI_MAX_Y; ego->y = AGI_MAX_Y;
@ -111,7 +111,7 @@ static void changeRoom(void) {
gAgi->vars[VAR_EGO_EDGE] = AGI_EDGE_NONE; gAgi->vars[VAR_EGO_EDGE] = AGI_EDGE_NONE;
(void)vmLoadLogic(gAgi->vars[VAR_ROOM]); (void)vmLoadLogic(gAgi->vars[VAR_ROOM]);
gAgi->logicLoaded[gAgi->vars[VAR_ROOM]] = true; gAgi->logicLoaded[gAgi->vars[VAR_ROOM]] = true;
gAgi->flags[FLAG_ENTERED] = 0u; gAgi->flags[FLAG_ENTERED] = 0u;
gAgi->flags[FLAG_NEW_ROOM] = 1u; gAgi->flags[FLAG_NEW_ROOM] = 1u;
inputReset(); inputReset();
} }
@ -127,7 +127,7 @@ static void cycle(void) {
gAgi->vars[VAR_BAD_WORD] = 0u; gAgi->vars[VAR_BAD_WORD] = 0u;
if (gAgi->menuRequested) { if (gAgi->menuRequested) {
gAgi->menuRequested = false; gAgi->menuRequested = false;
chosen = textMenuInput(); chosen = textMenuInput();
if (chosen != AGI_NO_CONTROLLER) { if (chosen != AGI_NO_CONTROLLER) {
gAgi->controllers[chosen] = 1u; gAgi->controllers[chosen] = 1u;
} }
@ -157,11 +157,11 @@ static void cycle(void) {
} }
steerEgo(ego); steerEgo(ego);
textUpdateStatus(); textUpdateStatus();
gAgi->vars[VAR_OBJ_EDGE_OBJ] = 0u; gAgi->vars[VAR_OBJ_EDGE_OBJ] = 0u;
gAgi->vars[VAR_OBJ_EDGE] = 0u; gAgi->vars[VAR_OBJ_EDGE] = 0u;
gAgi->flags[FLAG_NEW_ROOM] = 0u; gAgi->flags[FLAG_NEW_ROOM] = 0u;
gAgi->flags[FLAG_RESTART] = 0u; gAgi->flags[FLAG_RESTART] = 0u;
gAgi->flags[FLAG_RESTORED] = 0u; gAgi->flags[FLAG_RESTORED] = 0u;
gAgi->flags[FLAG_FIRST_LOGIC0] = 0u; gAgi->flags[FLAG_FIRST_LOGIC0] = 0u;
if (!gAgi->textMode) { if (!gAgi->textMode) {
objUpdateAll(); objUpdateAll();
@ -276,32 +276,32 @@ void agiResetState(void) {
for (i = 0u; i < AGI_MAX_ITEMS; i++) { for (i = 0u; i < AGI_MAX_ITEMS; i++) {
gAgi->itemRoom[i] = agiItemStartRoom(&gAgi->game, (uint8_t)i); gAgi->itemRoom[i] = agiItemStartRoom(&gAgi->game, (uint8_t)i);
} }
gAgi->menuRequested = false; gAgi->menuRequested = false;
gAgi->horizon = AGI_DEFAULT_HORIZON; gAgi->horizon = AGI_DEFAULT_HORIZON;
gAgi->blockActive = false; gAgi->blockActive = false;
gAgi->playerControl = true; gAgi->playerControl = true;
gAgi->picRow = DEFAULT_PIC_ROW; gAgi->picRow = DEFAULT_PIC_ROW;
gAgi->inputRow = DEFAULT_INPUT_ROW; gAgi->inputRow = DEFAULT_INPUT_ROW;
gAgi->statusRow = DEFAULT_STATUS_ROW; gAgi->statusRow = DEFAULT_STATUS_ROW;
gAgi->statusOn = false; gAgi->statusOn = false;
gAgi->inputOn = false; gAgi->inputOn = false;
gAgi->textMode = false; gAgi->textMode = false;
gAgi->textFg = AGI_COLOR_WHITE; gAgi->textFg = AGI_COLOR_WHITE;
gAgi->textBg = AGI_COLOR_BLACK; gAgi->textBg = AGI_COLOR_BLACK;
gAgi->cursorChar = AGI_NO_CURSOR; gAgi->cursorChar = AGI_NO_CURSOR;
gAgi->windowOpen = false; gAgi->windowOpen = false;
gAgi->inputLine[0] = '\0'; gAgi->inputLine[0] = '\0';
gAgi->inputLen = 0u; gAgi->inputLen = 0u;
gAgi->lastLine[0] = '\0'; gAgi->lastLine[0] = '\0';
gAgi->wordCount = 0u; gAgi->wordCount = 0u;
gAgi->newRoomPending = false; gAgi->newRoomPending = false;
gAgi->abortLogic = false; gAgi->abortLogic = false;
gAgi->vars[VAR_CYCLE_DELAY] = DEFAULT_CYCLE_DELAY; gAgi->vars[VAR_CYCLE_DELAY] = DEFAULT_CYCLE_DELAY;
gAgi->vars[VAR_INPUT_MAX] = DEFAULT_INPUT_MAX; gAgi->vars[VAR_INPUT_MAX] = DEFAULT_INPUT_MAX;
gAgi->vars[VAR_FREE_PAGES] = DEFAULT_FREE_PAGES; gAgi->vars[VAR_FREE_PAGES] = DEFAULT_FREE_PAGES;
gAgi->vars[VAR_COMPUTER] = COMPUTER_IBM_PC; gAgi->vars[VAR_COMPUTER] = COMPUTER_IBM_PC;
gAgi->vars[VAR_SOUND_TYPE] = SOUND_PC; gAgi->vars[VAR_SOUND_TYPE] = SOUND_PC;
gAgi->vars[VAR_MONITOR] = MONITOR_EGA; gAgi->vars[VAR_MONITOR] = MONITOR_EGA;
gAgi->flags[FLAG_FIRST_LOGIC0] = 1u; gAgi->flags[FLAG_FIRST_LOGIC0] = 1u;
gAgi->flags[FLAG_SOUND] = 1u; gAgi->flags[FLAG_SOUND] = 1u;
saveScriptClear(); saveScriptClear();

4
agi.h
View file

@ -45,7 +45,7 @@
#define AGI_TEXT_ROWS 25u #define AGI_TEXT_ROWS 25u
#define AGI_CELL_PX 8 #define AGI_CELL_PX 8
// Colours. The picture starts white with priority 4 (red) everywhere. // Colors. The picture starts white with priority 4 (red) everywhere.
#define AGI_COLOR_BLACK 0u #define AGI_COLOR_BLACK 0u
#define AGI_COLOR_RED 4u #define AGI_COLOR_RED 4u
#define AGI_COLOR_WHITE 15u #define AGI_COLOR_WHITE 15u
@ -184,7 +184,7 @@ extern const uint8_t kAgiFont[AGI_FONT_GLYPHS][AGI_FONT_ROWS];
// folder has one; otherwise the version the quirks were measured on. // folder has one; otherwise the version the quirks were measured on.
#define AGI_VERSION_MEASURED 2435u #define AGI_VERSION_MEASURED 2435u
// Behaviour of particular Sierra interpreter versions that games may lean // Behavior of particular Sierra interpreter versions that games may lean
// on; agiQuirk says whether the game's version has one (the version ranges // on; agiQuirk says whether the game's version has one (the version ranges
// are agiRes.c's kQuirkVersions). // are agiRes.c's kQuirkVersions).
typedef enum { typedef enum {

View file

@ -1,10 +1,10 @@
// The picture buffers and the screen objects drawn into them. // The picture buffers and the screen objects drawn into them.
// //
// Like Sierra's interpreter, JoeyAGI keeps one 160x168 picture buffer pair // Like Sierra's interpreter, JoeyAGI keeps one 160x168 picture buffer pair
// -- a colour and a priority byte per AGI pixel -- that holds the picture // -- a color and a priority byte per AGI pixel -- that holds the picture
// with the visible objects blitted into it. An object's blit saves the // with the visible objects blitted into it. An object's blit saves the
// bytes under its cel, then draws the cel's pixels whose priority is at // bytes under its cel, then draws the cel's pixels whose priority is at
// least the buffer's, writing the object's priority with the colour, so a // least the buffer's, writing the object's priority with the color, so a
// lower object drawn later stays hidden behind it. Erasing puts the saved // lower object drawn later stays hidden behind it. Erasing puts the saved
// bytes back, so blits stack: the buffer is always erased in the reverse // bytes back, so blits stack: the buffer is always erased in the reverse
// order it was drawn. Objects that are not updated (stop.update) are drawn // order it was drawn. Objects that are not updated (stop.update) are drawn
@ -34,7 +34,7 @@
// Areas waiting to be copied to the stage; more are merged into one. // Areas waiting to be copied to the stage; more are merged into one.
#define DIRTY_MAX 24u #define DIRTY_MAX 24u
#define NIBBLE_SHIFT 4u #define NIBBLE_SHIFT 4u
// A cel run byte: the colour in the high nibble, the length in the low. // A cel run byte: the color in the high nibble, the length in the low.
#define RLE_COUNT_MASK 0x0Fu #define RLE_COUNT_MASK 0x0Fu
// add.to.pic's margin box: margins 0..3 are control values; 4 and above // add.to.pic's margin box: margins 0..3 are control values; 4 and above
// mean no box. // mean no box.
@ -136,8 +136,8 @@ static void blitObject(uint8_t n) {
} }
o->saveUnder = &gSaveArena[gSaveTop]; o->saveUnder = &gSaveArena[gSaveTop];
saveRect(o->saveUnder, left, o->blitY, o->blitW, o->blitH); saveRect(o->saveUnder, left, o->blitY, o->blitW, o->blitH);
gSaveTop = (uint16_t)(gSaveTop + need); gSaveTop = (uint16_t)(gSaveTop + need);
o->blitted = true; o->blitted = true;
gBlitStack[gBlitCount] = n; gBlitStack[gBlitCount] = n;
gBlitCount++; gBlitCount++;
if ((o->flags & OBJ_FIX_PRIORITY) == 0u) { if ((o->flags & OBJ_FIX_PRIORITY) == 0u) {
@ -150,7 +150,7 @@ static void blitObject(uint8_t n) {
// The screen as a shake beat shows it: moved SHAKE_DX right and SHAKE_DY // The screen as a shake beat shows it: moved SHAKE_DX right and SHAKE_DY
// down over black. Chunky surfaces move their bytes; planar ones draw the // down over black. Chunky surfaces move their bytes; planar ones draw the
// screen as one big sprite (colour 0 is transparent, and the surface is // screen as one big sprite (color 0 is transparent, and the surface is
// black beneath). // black beneath).
static void buildShifted(jlSurfaceT *shifted, const jlSurfaceT *normal) { static void buildShifted(jlSurfaceT *shifted, const jlSurfaceT *normal) {
jlSpriteT *sprite; jlSpriteT *sprite;
@ -349,7 +349,7 @@ static void restoreRect(const uint8_t *save, int16_t x, int16_t y, uint8_t w, ui
} }
// Keep a rectangle of both buffers, a row of colours then a row of // Keep a rectangle of both buffers, a row of colors then a row of
// priorities at a time (2 * w * h bytes). // priorities at a time (2 * w * h bytes).
static void saveRect(uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h) { static void saveRect(uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h) {
uint8_t row; uint8_t row;
@ -642,13 +642,13 @@ void gfxShowObjectCel(uint8_t viewId) {
void gfxShowPic(void) { void gfxShowPic(void) {
gPicShown = true; gPicShown = true;
gDirtyCount = 0u; gDirtyCount = 0u;
gfxRestorePicture(); gfxRestorePicture();
} }
// show.pri.screen: the priority buffer as colours until a key. // show.pri.screen: the priority buffer as colors until a key.
void gfxShowPriority(void) { void gfxShowPriority(void) {
uint8_t *saved; uint8_t *saved;

View file

@ -140,7 +140,7 @@ static void handleKey(uint16_t key) {
if (dir != AGI_DIR_NONE) { if (dir != AGI_DIR_NONE) {
// A second press of the direction ego is walking stops it. // A second press of the direction ego is walking stops it.
if (gAgi->playerControl) { if (gAgi->playerControl) {
gAgi->vars[VAR_EGO_DIR] = (gAgi->vars[VAR_EGO_DIR] == dir) ? AGI_DIR_NONE : dir; gAgi->vars[VAR_EGO_DIR] = (gAgi->vars[VAR_EGO_DIR] == dir) ? AGI_DIR_NONE : dir;
gAgi->objects[AGI_EGO].direction = gAgi->vars[VAR_EGO_DIR]; gAgi->objects[AGI_EGO].direction = gAgi->vars[VAR_EGO_DIR];
} }
return; return;
@ -319,7 +319,7 @@ void inputParse(const char *line) {
while (n > 0u && text[n - 1u] == ' ') { while (n > 0u && text[n - 1u] == ' ') {
n--; n--;
} }
text[n] = '\0'; text[n] = '\0';
gAgi->wordCount = 0u; gAgi->wordCount = 0u;
gAgi->vars[VAR_BAD_WORD] = 0u; gAgi->vars[VAR_BAD_WORD] = 0u;
if (n == 0u) { if (n == 0u) {
@ -430,8 +430,8 @@ void inputPoll(void) {
void inputProcess(void) { void inputProcess(void) {
uint16_t key; uint16_t key;
gKeyUnused = false; gKeyUnused = false;
gHaveKeyAsked = false; gHaveKeyAsked = false;
gAgi->vars[VAR_KEY] = 0u; gAgi->vars[VAR_KEY] = 0u;
while ((key = dequeue()) != AGI_KEY_NONE) { while ((key = dequeue()) != AGI_KEY_NONE) {
handleKey(key); handleKey(key);

View file

@ -107,8 +107,8 @@ static void advanceCel(uint8_t n) {
} }
if ((o->cycleMode == AGI_CYCLE_END_LOOP) ? (o->cel >= last) : (o->cel == 0u)) { if ((o->cycleMode == AGI_CYCLE_END_LOOP) ? (o->cel >= last) : (o->cel == 0u)) {
gAgi->flags[o->loopFlag] = 1u; gAgi->flags[o->loopFlag] = 1u;
o->flags = (uint16_t)(o->flags & (uint16_t)~OBJ_CYCLING); o->flags = (uint16_t)(o->flags & (uint16_t)~OBJ_CYCLING);
o->cycleMode = AGI_CYCLE_NORMAL; o->cycleMode = AGI_CYCLE_NORMAL;
} }
break; break;
default: default:
@ -186,7 +186,7 @@ static bool legalPosition(uint8_t n, int16_t x, int16_t y) {
bool insideNow; bool insideNow;
bool insideNew; bool insideNew;
o = &gAgi->objects[n]; o = &gAgi->objects[n];
anyWater = false; anyWater = false;
allWater = true; allWater = true;
signal = false; signal = false;
@ -314,8 +314,8 @@ static void stopSpecialMotion(uint8_t n) {
} }
} }
gAgi->flags[o->motionFlag] = 1u; gAgi->flags[o->motionFlag] = 1u;
o->motion = AGI_MOTION_NORMAL; o->motion = AGI_MOTION_NORMAL;
o->direction = AGI_DIR_NONE; o->direction = AGI_DIR_NONE;
if (n == AGI_EGO) { if (n == AGI_EGO) {
gAgi->vars[VAR_EGO_DIR] = AGI_DIR_NONE; gAgi->vars[VAR_EGO_DIR] = AGI_DIR_NONE;
} }
@ -417,7 +417,7 @@ void objFixPosition(uint8_t n) {
void objMoveTo(uint8_t n, uint8_t x, uint8_t y, uint8_t step, uint8_t flag) { void objMoveTo(uint8_t n, uint8_t x, uint8_t y, uint8_t step, uint8_t flag) {
AgiObjectT *o; AgiObjectT *o;
o = &gAgi->objects[n]; o = &gAgi->objects[n];
o->motion = AGI_MOTION_MOVE; o->motion = AGI_MOTION_MOVE;
o->targetX = x; o->targetX = x;
o->targetY = y; o->targetY = y;
@ -551,7 +551,7 @@ void objUnanimateAll(void) {
gfxEraseAll(); gfxEraseAll();
for (i = 0u; i < AGI_MAX_OBJECTS; i++) { for (i = 0u; i < AGI_MAX_OBJECTS; i++) {
o = &gAgi->objects[i]; o = &gAgi->objects[i];
o->flags = OBJ_SKIP_STEP; o->flags = OBJ_SKIP_STEP;
o->motion = AGI_MOTION_NORMAL; o->motion = AGI_MOTION_NORMAL;
o->cycleMode = AGI_CYCLE_NORMAL; o->cycleMode = AGI_CYCLE_NORMAL;

View file

@ -200,7 +200,7 @@ static void fill(const PicDrawT *d, int16_t x, int16_t y) {
if (x < 0 || x > AGI_MAX_X || y < 0 || y > AGI_MAX_Y) { if (x < 0 || x > AGI_MAX_X || y < 0 || y > AGI_MAX_Y) {
return; return;
} }
// Filling with the colour being searched for would never end. // Filling with the color being searched for would never end.
if (d->visualOn) { if (d->visualOn) {
if (d->color == AGI_PIC_BG_COLOR) { if (d->color == AGI_PIC_BG_COLOR) {
return; return;
@ -393,7 +393,7 @@ static void plot(const PicDrawT *d, int16_t x, int16_t y) {
} }
// Plot the current pen centred on (x, y): a rectangle or circle size+1 // Plot the current pen centered on (x, y): a rectangle or circle size+1
// wide and 2*size+1 tall, solid or masked by a texture's bits. // wide and 2*size+1 tall, solid or masked by a texture's bits.
static void plotPen(const PicDrawT *d, int16_t x, int16_t y, uint8_t texture) { static void plotPen(const PicDrawT *d, int16_t x, int16_t y, uint8_t texture) {
uint8_t size; uint8_t size;
@ -453,7 +453,7 @@ bool agiPicDecode(uint8_t *visual, uint8_t *priority, const uint8_t *data, uint1
d.color = 0u; d.color = 0u;
d.priorityColor = 0u; d.priorityColor = 0u;
d.pen = 0u; d.pen = 0u;
pos = 0u; pos = 0u;
while (pos < length) { while (pos < length) {
op = data[pos++]; op = data[pos++];
switch (op) { switch (op) {

View file

@ -96,7 +96,7 @@ static const QuirkRangeT kQuirkVersions[AGI_QUIRK_COUNT] = {
static void buildVolName(char *out, uint8_t volNum) { static void buildVolName(char *out, uint8_t volNum) {
uint8_t n; uint8_t n;
n = 0u; n = 0u;
out[n++] = 'V'; out[n++] = 'V';
out[n++] = 'O'; out[n++] = 'O';
out[n++] = 'L'; out[n++] = 'L';

View file

@ -103,7 +103,7 @@ static void playNote(uint8_t voice, const uint8_t *note) {
jlAudioNoise(gNoisePitch, gNoiseAtten); jlAudioNoise(gNoisePitch, gNoiseAtten);
return; return;
} }
divisor = (uint16_t)(((uint16_t)(note[2] & DIVISOR_HIGH_MASK) << DIVISOR_HIGH_SHIFT) | (note[3] & DIVISOR_LOW_MASK)); divisor = (uint16_t)(((uint16_t)(note[2] & DIVISOR_HIGH_MASK) << DIVISOR_HIGH_SHIFT) | (note[3] & DIVISOR_LOW_MASK));
gToneDivisor[voice] = divisor; gToneDivisor[voice] = divisor;
gToneHz[voice] = (divisor == 0u) ? 0u : (uint16_t)(PSG_CLOCK_HZ / divisor); gToneHz[voice] = (divisor == 0u) ? 0u : (uint16_t)(PSG_CLOCK_HZ / divisor);
gToneAtten[voice] = atten; gToneAtten[voice] = atten;
@ -230,7 +230,7 @@ void soundStart(uint8_t soundId, uint8_t flag) {
soundStop(); soundStop();
gFlag = flag; gFlag = flag;
gAgi->flags[flag] = 0u; gAgi->flags[flag] = 0u;
if (gAgi->flags[FLAG_SOUND] == 0u) { if (gAgi->flags[FLAG_SOUND] == 0u) {
gAgi->flags[flag] = 1u; gAgi->flags[flag] = 1u;
return; return;
@ -238,7 +238,7 @@ void soundStart(uint8_t soundId, uint8_t flag) {
gData = agiResLoad(&gAgi->game, AGI_RES_SOUND, soundId, &gLength); gData = agiResLoad(&gAgi->game, AGI_RES_SOUND, soundId, &gLength);
if (gData == NULL || gLength < SOUND_HEADER_BYTES) { if (gData == NULL || gLength < SOUND_HEADER_BYTES) {
jlFree(gData); jlFree(gData);
gData = NULL; gData = NULL;
gAgi->flags[flag] = 1u; gAgi->flags[flag] = 1u;
return; return;
} }

View file

@ -6,7 +6,7 @@
// stage. In graphics mode Sierra's interpreter draws only white-on-black // stage. In graphics mode Sierra's interpreter draws only white-on-black
// style text: a set.text.attribute background other than black shows as // style text: a set.text.attribute background other than black shows as
// black on white. A message window is the wrapped text (30 columns at // black on white. A message window is the wrapped text (30 columns at
// most) on white, inside a red frame, centred over the picture unless // most) on white, inside a red frame, centered over the picture unless
// print.at places it; the screen under it is saved and put back when it // print.at places it; the screen under it is saved and put back when it
// closes. Geometry measured from Sierra's interpreter. // closes. Geometry measured from Sierra's interpreter.
@ -129,7 +129,7 @@ static bool gShownSound;
// ----- Internal helpers (alphabetical) ----- // ----- Internal helpers (alphabetical) -----
// The text attribute's colours. On the text screen they are used as they // The text attribute's colors. On the text screen they are used as they
// are; on the picture a background makes the text black on white. // are; on the picture a background makes the text black on white.
static void attrColors(uint8_t *fg, uint8_t *bg) { static void attrColors(uint8_t *fg, uint8_t *bg) {
if (gAgi->textMode) { if (gAgi->textMode) {
@ -147,7 +147,7 @@ static void attrColors(uint8_t *fg, uint8_t *bg) {
} }
// The column that centres a line of text on the screen. // The column that centers a line of text on the screen.
static uint8_t centerCol(const char *text) { static uint8_t centerCol(const char *text) {
size_t len; size_t len;
@ -156,8 +156,8 @@ static uint8_t centerCol(const char *text) {
} }
// clear.lines and clear.text.rect: on the picture any colour but black // clear.lines and clear.text.rect: on the picture any color but black
// clears to white; on the text screen the colour is a PC text attribute // clears to white; on the text screen the color is a PC text attribute
// whose high nibble is the background. // whose high nibble is the background.
static uint8_t clearColor(uint8_t color) { static uint8_t clearColor(uint8_t color) {
if (gAgi->textMode) { if (gAgi->textMode) {
@ -853,8 +853,8 @@ void textMenuAdd(const char *name) {
m = &gAgi->menus[gAgi->menuCount]; m = &gAgi->menus[gAgi->menuCount];
strncpy(m->name, name, AGI_MENU_NAME_MAX); strncpy(m->name, name, AGI_MENU_NAME_MAX);
m->name[AGI_MENU_NAME_MAX] = '\0'; m->name[AGI_MENU_NAME_MAX] = '\0';
m->firstItem = gAgi->menuItemCount; m->firstItem = gAgi->menuItemCount;
m->itemCount = 0u; m->itemCount = 0u;
gAgi->menuCount++; gAgi->menuCount++;
} }
@ -868,8 +868,8 @@ void textMenuAddItem(const char *name, uint8_t controller) {
it = &gAgi->menuItems[gAgi->menuItemCount]; it = &gAgi->menuItems[gAgi->menuItemCount];
strncpy(it->text, name, AGI_MENU_ITEM_MAX); strncpy(it->text, name, AGI_MENU_ITEM_MAX);
it->text[AGI_MENU_ITEM_MAX] = '\0'; it->text[AGI_MENU_ITEM_MAX] = '\0';
it->controller = controller; it->controller = controller;
it->enabled = true; it->enabled = true;
gAgi->menuItemCount++; gAgi->menuItemCount++;
gAgi->menus[gAgi->menuCount - 1u].itemCount++; gAgi->menus[gAgi->menuCount - 1u].itemCount++;
} }
@ -878,10 +878,10 @@ void textMenuAddItem(const char *name, uint8_t controller) {
// menu.input: let the player pick from the menus; a chosen, enabled // menu.input: let the player pick from the menus; a chosen, enabled
// item's controller fires. Returns the controller, or 0xFF for none. // item's controller fires. Returns the controller, or 0xFF for none.
uint8_t textMenuInput(void) { uint8_t textMenuInput(void) {
uint8_t menu; uint8_t menu;
uint8_t item; uint8_t item;
uint16_t key; uint16_t key;
uint8_t result; uint8_t result;
jlSurfaceT *stage; jlSurfaceT *stage;
if (gAgi->menuCount == 0u) { if (gAgi->menuCount == 0u) {
@ -945,7 +945,7 @@ void textMessageBox(const char *text) {
} }
// Open a message window: centred over the picture when row and col are // Open a message window: centered over the picture when row and col are
// negative (print), else with its text at (row, col) of the picture area // negative (print), else with its text at (row, col) of the picture area
// (print.at). width 0 means the default 30 columns. // (print.at). width 0 means the default 30 columns.
void textPrint(const char *text, int16_t row, int16_t col, uint8_t width) { void textPrint(const char *text, int16_t row, int16_t col, uint8_t width) {
@ -980,7 +980,7 @@ void textPrint(const char *text, int16_t row, int16_t col, uint8_t width) {
// set.text.attribute. Set on the picture, a background only says "white" // set.text.attribute. Set on the picture, a background only says "white"
// (the text screen then clears to white and shows the foreground on it); // (the text screen then clears to white and shows the foreground on it);
// set on the text screen the colours are taken as they are. // set on the text screen the colors are taken as they are.
void textSetAttribute(uint8_t fg, uint8_t bg) { void textSetAttribute(uint8_t fg, uint8_t bg) {
gAgi->textFg = (uint8_t)(fg & AGI_COLOR_MASK); gAgi->textFg = (uint8_t)(fg & AGI_COLOR_MASK);
gAgi->textBg = (uint8_t)(bg & AGI_COLOR_MASK); gAgi->textBg = (uint8_t)(bg & AGI_COLOR_MASK);

View file

@ -2,9 +2,9 @@
// //
// A view is a header (loop count, description offset, a loop offset per // A view is a header (loop count, description offset, a loop offset per
// loop), each loop a cel count and cel offsets relative to the loop, each // loop), each loop a cel count and cel offsets relative to the loop, each
// cel a width, height and a byte holding the transparent colour (low // cel a width, height and a byte holding the transparent color (low
// nibble) and the mirroring bits (high nibble), then run-length rows: a // nibble) and the mirroring bits (high nibble), then run-length rows: a
// byte of colour (high nibble) and run length (low nibble), 0 ending a // byte of color (high nibble) and run length (low nibble), 0 ending a
// row. Loops that mirror each other share their cels; a cel whose // row. Loops that mirror each other share their cels; a cel whose
// mirroring bit is set is drawn flipped in every loop except the one its // mirroring bit is set is drawn flipped in every loop except the one its
// loop field names. // loop field names.
@ -114,7 +114,7 @@ static AgiViewT *load(const AgiGameT *game, uint8_t viewId) {
view->loopCount = loopCount; view->loopCount = loopCount;
view->loops = (AgiLoopT *)(view + 1); view->loops = (AgiLoopT *)(view + 1);
view->description = NULL; view->description = NULL;
descOffset = readLe16(&raw[VIEW_DESC_OFFSET_BYTE]); descOffset = readLe16(&raw[VIEW_DESC_OFFSET_BYTE]);
if (descOffset != 0u && descOffset < length) { if (descOffset != 0u && descOffset < length) {
view->description = (const char *)&raw[descOffset]; view->description = (const char *)&raw[descOffset];
} }

14
agiVm.c
View file

@ -338,7 +338,7 @@ static void cmdMisc(VmFrameT *f, uint8_t op, const uint8_t *a) {
gAgi->playerControl = false; gAgi->playerControl = false;
break; break;
case CMD_PLAYER_CONTROL: case CMD_PLAYER_CONTROL:
gAgi->playerControl = true; gAgi->playerControl = true;
gAgi->objects[AGI_EGO].motion = AGI_MOTION_NORMAL; gAgi->objects[AGI_EGO].motion = AGI_MOTION_NORMAL;
break; break;
case CMD_OBJ_STATUS_V: case CMD_OBJ_STATUS_V:
@ -1379,12 +1379,12 @@ void vmCallLogic(uint8_t logicId) {
jlLogF("call: no logic %u", (unsigned)logicId); jlLogF("call: no logic %u", (unsigned)logicId);
return; return;
} }
raw = gAgi->logicRaw[logicId]; raw = gAgi->logicRaw[logicId];
frame.code = raw + LOGIC_HEADER_BYTES; frame.code = raw + LOGIC_HEADER_BYTES;
frame.length = codeLength(raw); frame.length = codeLength(raw);
frame.pc = gAgi->logicScanStart[logicId]; frame.pc = gAgi->logicScanStart[logicId];
frame.logic = logicId; frame.logic = logicId;
frame.done = false; frame.done = false;
caller = gAgi->currentLogic; caller = gAgi->currentLogic;
gAgi->currentLogic = logicId; gAgi->currentLogic = logicId;
gCallDepth++; gCallDepth++;

View file

@ -362,7 +362,7 @@ def _rle(row, transparent):
def cel(art, transparent=0): def cel(art, transparent=0):
# A cel from rows of hex digits; '.' is the transparent colour. # A cel from rows of hex digits; '.' is the transparent color.
rows = [[transparent if ch == "." else int(ch, 16) for ch in line] for line in art] rows = [[transparent if ch == "." else int(ch, 16) for ch in line] for line in art]
return (rows, transparent) return (rows, transparent)

View file

@ -1,3 +1,3 @@
g1 0222bfaa g1 0222bfaa
t1 a3bbcf30 t1 08874fc5
g2 0222bfaa g2 0222bfaa

View file

@ -397,7 +397,7 @@ static void hostGoto(const char *label) {
rewind(gScript); rewind(gScript);
while (fgets(line, sizeof(line), gScript) != NULL) { while (fgets(line, sizeof(line), gScript) != NULL) {
line[strcspn(line, "#\r\n")] = '\0'; line[strcspn(line, "#\r\n")] = '\0';
p = line + strspn(line, " \t"); p = line + strspn(line, " \t");
if (strncmp(p, "label", 5) != 0 || (p[5] != ' ' && p[5] != '\t')) { if (strncmp(p, "label", 5) != 0 || (p[5] != ' ' && p[5] != '\t')) {
continue; continue;
} }
@ -673,11 +673,11 @@ static void hostStep(void) {
gSay = (op[0] == 's') ? HOST_SAY_TYPING : HOST_SAY_NONE; gSay = (op[0] == 's') ? HOST_SAY_TYPING : HOST_SAY_NONE;
strncpy(gTypeBuf, op + ((op[0] == 's') ? 4 : 5), sizeof(gTypeBuf) - 1u); strncpy(gTypeBuf, op + ((op[0] == 's') ? 4 : 5), sizeof(gTypeBuf) - 1u);
gTypeBuf[sizeof(gTypeBuf) - 1u] = '\0'; gTypeBuf[sizeof(gTypeBuf) - 1u] = '\0';
gTypePos = 0u; gTypePos = 0u;
continue; continue;
} }
op[strcspn(op, "#")] = '\0'; op[strcspn(op, "#")] = '\0';
arg = op + strcspn(op, " \t"); arg = op + strcspn(op, " \t");
if (*arg != '\0') { if (*arg != '\0') {
*arg = '\0'; *arg = '\0';
arg++; arg++;
@ -904,7 +904,7 @@ static bool hostWaitStep(void) {
} }
// Steer ego one frame towards the walk's target with the arrow keys, // Steer ego one frame toward the walk's target with the arrow keys,
// pressing ego's own arrow again to stop it there. A walk to a place // pressing ego's own arrow again to stop it there. A walk to a place
// keeps to the straight line from where it started, within // keeps to the straight line from where it started, within
// HOST_WALK_BAND pixels: it stays on one axis until it strays that far, // HOST_WALK_BAND pixels: it stays on one axis until it strays that far,
@ -1103,9 +1103,9 @@ void jlpPresent(const jlSurfaceT *src) {
for (y = 0u; y < JL_SURFACE_HEIGHT; y++) { for (y = 0u; y < JL_SURFACE_HEIGHT; y++) {
pal = src->palette[src->scb[y]]; pal = src->palette[src->scb[y]];
for (x = 0u; x < JL_SURFACE_WIDTH; x++) { for (x = 0u; x < JL_SURFACE_WIDTH; x++) {
byte = src->pixels[(uint32_t)y * JL_SURFACE_BYTES_PER_ROW + x / HOST_PIXELS_PER_BYTE]; byte = src->pixels[(uint32_t)y * JL_SURFACE_BYTES_PER_ROW + x / HOST_PIXELS_PER_BYTE];
nib = (uint8_t)((x & 1u) ? (byte & 0x0Fu) : (byte >> 4)); nib = (uint8_t)((x & 1u) ? (byte & 0x0Fu) : (byte >> 4));
rgb = pal[nib]; rgb = pal[nib];
gDisplay[y][x][0] = (uint8_t)(((rgb >> 8) & 0x0Fu) * HOST_NIBBLE_SCALE); gDisplay[y][x][0] = (uint8_t)(((rgb >> 8) & 0x0Fu) * HOST_NIBBLE_SCALE);
gDisplay[y][x][1] = (uint8_t)(((rgb >> 4) & 0x0Fu) * HOST_NIBBLE_SCALE); gDisplay[y][x][1] = (uint8_t)(((rgb >> 4) & 0x0Fu) * HOST_NIBBLE_SCALE);
gDisplay[y][x][2] = (uint8_t)((rgb & 0x0Fu) * HOST_NIBBLE_SCALE); gDisplay[y][x][2] = (uint8_t)((rgb & 0x0Fu) * HOST_NIBBLE_SCALE);

View file

@ -143,7 +143,7 @@ def recordFlag(L, flag, var):
# ----- Probes ----- # ----- Probes -----
def probePictures(): def probePictures():
# Every picture command: colours on and off, corners, absolute and # Every picture command: colors on and off, corners, absolute and
# relative lines, nested fills (visual, priority-only and both) and # relative lines, nested fills (visual, priority-only and both) and
# every pen: solid and splatter, circle and rectangle, all sizes. # every pen: solid and splatter, circle and rectangle, all sizes.
p = bytearray() p = bytearray()
@ -493,7 +493,7 @@ def probeSetView():
def controlPicture(): def controlPicture():
# Control lines on the priority screen, drawn in matching colours. # Control lines on the priority screen, drawn in matching colors.
p = bytearray() p = bytearray()
def vline(pri, x, y0, y1, vis): def vline(pri, x, y0, y1, vis):
@ -1086,7 +1086,7 @@ def probeTextScreen():
L.increment(V_CYCLE) L.increment(V_CYCLE)
with L.If(("equaln", V_CYCLE, 1)): with L.If(("equaln", V_CYCLE, 1)):
L.text_screen() L.text_screen()
L.display(2, 2, L.m("Text screen, default colours")) L.display(2, 2, L.m("Text screen, default colors"))
L.set_text_attribute(14, 1) L.set_text_attribute(14, 1)
L.display(4, 2, L.m("Yellow on blue")) L.display(4, 2, L.m("Yellow on blue"))
L.set_text_attribute(0, 15) L.set_text_attribute(0, 15)
@ -1144,7 +1144,7 @@ def probeShake():
def probeWindowEdge(): def probeWindowEdge():
# print.at windows at columns 0, 1 and 2 on a coloured picture. Sierra's # print.at windows at columns 0, 1 and 2 on a colored picture. Sierra's
# interpreter keeps a window's x in a byte and walks the screen as one # interpreter keeps a window's x in a byte and walks the screen as one
# line of 160-pixel rows, so a box left of the screen (column 0 or 1) # line of 160-pixel rows, so a box left of the screen (column 0 or 1)
# is saved, filled and put back 256 pixels on -- a row down, 96 in -- # is saved, filled and put back 256 pixels on -- a row down, 96 in --

View file

@ -1,15 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# compareFrames.py - compare a frame from Sierra's interpreter with one # compareFrames.py - compare a frame from Sierra's interpreter with one
# from JoeyAGI. Both are mapped to the 16 EGA colours, then compared one # from JoeyAGI. Both are mapped to the 16 EGA colors, then compared one
# 8x8 text cell at a time. JoeyAGI draws text in its own font, so a cell # 8x8 text cell at a time. JoeyAGI draws text in its own font, so a cell
# where both frames hold a character -- read with each interpreter's font, # where both frames hold a character -- read with each interpreter's font,
# captured from the "font" probe -- counts as equal when the characters # captured from the "font" probe -- counts as equal when the characters
# and colours match. Every other differing pixel is a real difference. # and colors match. Every other differing pixel is a real difference.
# #
# Sierra shows its text screen (text.screen, the inventory) in the PC's # Sierra shows its text screen (text.screen, the inventory) in the PC's
# 40-column text mode, which DOSBox captures at 360x400 (9x16 cells). # 40-column text mode, which DOSBox captures at 360x400 (9x16 cells).
# JoeyAGI draws the same screen on the bitmap, so those frames are # JoeyAGI draws the same screen on the bitmap, so those frames are
# compared cell by cell: blank or not, and the colours. # compared cell by cell: blank or not, and the colors.
# #
# compareFrames.py ORACLE JOEY [DIFF.png] [--fonts ORACLEFONT JOEYFONT] # compareFrames.py ORACLE JOEY [DIFF.png] [--fonts ORACLEFONT JOEYFONT]
# prints "real=N text=M" and the cells that differ; exit 1 if any do. # prints "real=N text=M" and the cells that differ; exit 1 if any do.
@ -58,7 +58,7 @@ def egaGrid(im):
def load(path): def load(path):
# A frame as rows of EGA colour numbers, scaled to 320x200 if need be. # A frame as rows of EGA color numbers, scaled to 320x200 if need be.
im = Image.open(path).convert("RGB") im = Image.open(path).convert("RGB")
if im.size != (WIDTH, HEIGHT): if im.size != (WIDTH, HEIGHT):
im = im.resize((WIDTH, HEIGHT), Image.NEAREST) im = im.resize((WIDTH, HEIGHT), Image.NEAREST)
@ -67,13 +67,13 @@ def load(path):
def textCells(grid, cellW, cellH): def textCells(grid, cellW, cellH):
# Each cell as (blank?, foreground, background): the background is # Each cell as (blank?, foreground, background): the background is
# the cell's commonest colour. # the cell's commonest color.
cells = [] cells = []
for cy in range(ROWS): for cy in range(ROWS):
row = [] row = []
for cx in range(COLS): for cx in range(COLS):
colours = Counter(grid[cy * cellH + y][cx * cellW + x] for y in range(cellH) for x in range(cellW)) colors = Counter(grid[cy * cellH + y][cx * cellW + x] for y in range(cellH) for x in range(cellW))
common = colours.most_common() common = colors.most_common()
if len(common) == 1: if len(common) == 1:
row.append((True, None, common[0][0])) row.append((True, None, common[0][0]))
else: else:
@ -93,7 +93,7 @@ def cellBits(g, cx, cy, bg):
return tuple(tuple(1 if g[cy * CELL + y][cx * CELL + x] != bg else 0 for x in range(CELL)) for y in range(CELL)) return tuple(tuple(1 if g[cy * CELL + y][cx * CELL + x] != bg else 0 for x in range(CELL)) for y in range(CELL))
def cellColours(g, cx, cy): def cellColors(g, cx, cy):
return Counter(g[cy * CELL + y][cx * CELL + x] for y in range(CELL) for x in range(CELL)) return Counter(g[cy * CELL + y][cx * CELL + x] for y in range(CELL) for x in range(CELL))
@ -108,12 +108,12 @@ def fontTemplates(path):
def readCell(g, cx, cy, templates): def readCell(g, cx, cy, templates):
# (character, foreground, background) or None when it is not text. # (character, foreground, background) or None when it is not text.
colours = cellColours(g, cx, cy) colors = cellColors(g, cx, cy)
if len(colours) > 2: if len(colors) > 2:
return None return None
if len(colours) == 1: if len(colors) == 1:
return (" ", None, next(iter(colours))) return (" ", None, next(iter(colors)))
(a, na), (b, nb) = colours.most_common() (a, na), (b, nb) = colors.most_common()
for bg, fg in ((a, b), (b, a)): for bg, fg in ((a, b), (b, a)):
ch = templates.get(cellBits(g, cx, cy, bg)) ch = templates.get(cellBits(g, cx, cy, bg))
if ch is not None: if ch is not None: