Unused Citadel bits

reminisce about bbc micro & electron games like chuckie egg, repton, elite & exileRelated forum: adventures


Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Unused Citadel bits

Post by Diminished » Sat Dec 09, 2017 12:10 pm

Tile 005 at 0x990 in the BBC disc version. Encountered while re-implementing the game's tile and room unpackers in PHP.

Game never reads from its memory (beyond the first byte, which is needed to find the following item in the chain). It may have been intended as a texture rather than an item, but in order to be sure it was rendered correctly I decided I'd rather have the game display it than my own code.
item-5-full.png
Gold bar? Tile 031 at 0xafd.
gold-bar.png

User avatar
davidb
Posts: 2087
Joined: Sun Nov 11, 2007 10:11 pm
Contact:

Re: Unused Citadel bits

Post by davidb » Sat Dec 09, 2017 1:13 pm

Very interesting! I was looking to explore Citadel's graphics and screen data at some point but never got round to it. It's good to see someone making progress with it. The second item certainly looks like a gold bar. :)

Are you going to introduce yourself to us? :)

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sat Dec 09, 2017 4:48 pm

Thanks.

The seductive thing about Citadel was that until the day you racked up that 99 score, you could never be sure you'd seen all of it. Now, I've got a 99 score many times, but some people are just goddamned stubborn. :P

I've been using the White Flame online 6502 disassembler for this task. This is flawed in a few ways, but it also has some appealing aspects. Worth a look if you've not seen it before, although it is very much geared towards the C64 (supports PETSCII but not ASCII, would you believe?)

http://www.white-flame.com/wfdis/

I modified B-em's debugger so that I could just take a 64K dump of the entire address space, and then tossed it into WFDIS. If you don't want your browser to eat 2 GB of RAM and freeze up for 15 minutes every time you load your work, you should probably avoid doing this (and, by the way, this is on an overclocked 4790K at 4.5 GHz with 16 GB in the tank; Linux/Firefox -- your mileage may vary, but I don't recommend doing what I did if you have anything vital open in a browser window). Once it's actually up and running, it's lethargic but usable, and I particularly like just being able to click on a label and be taken straight to its memory location, then use the browser "Back" button to get back to where I was originally. In fact, since I had my own code open alongside the assembly language, I started to get annoyed with my IDE for not working the same way as the disassembler.

Regarding Citadel-early; everyone can see where this version differs obviously from the retail version, but honestly it's the subtle changes that are easy to miss that I find more interesting. Have a gander at this: This is an animated GIF, so you'll have to click on it -- the preview won't animate it.
starport-dish-comparison.gif
In case it isn't obvious (it wasn't to me), the game uses PLOT 85 (or 86, I can't remember the difference) to draw optional triangles as part of a room's decor, and this can be used both constructively to make shapes and destructively to hollow out non-rectangular areas (e.g. Main Hall ceiling). These are used to draw the parabolic dish in the Star Port.

The antenna changed too, in order to fix up the hole cut out of the triangles by the tile:
antenna-comparison.png
How many people spotted this secret passage in the Temple, which allows access to the pulpit from the rope on the outer wall? I don't know why it was removed in the retail version -- perhaps to save RAM:
temple-secret.png
It's a neat engine. If you were reversing something like a NES or Game Boy game, all of the graphics would be cut into equally-sized rectangular tiles, so if you wanted to find a big sprite like Citadel's monks or the cannons, you'd have to go trawling through the tile data looking for a bit of cowl here or a bit of arm there. Citadel natively supports arbitrarily sized tiles, so you get the entire monk in one go. I admit I was slightly disappointed to discover that the game spends the overwhelming majority (80% ?) of its time doing absolutely nothing, though. :P

I am now wondering whether Jakobsen's earlier game (The Pyramid?) shares its tile format with Citadel, because it certainly shares some of the graphics. If so, the (still unfinished) tile unpacker I've been working on might also unpack the graphics from that game.

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sun Dec 10, 2017 5:56 pm

Not sure what forum policy on double posts is. Let me know if I've messed up.

About eight years ago I worked out the RLE used for the game's title screen, so here's a short C snippet which is capable of unpacking the game's title screen from the CITAT file. (I can't remember which version of the game this applies to, I'm afraid).

It spits out a 24 bpp RGB bitmap at 320x256 (GIMP will load this in RAW mode).
citat.png
citat.png (7.48 KiB) Viewed 737 times
Code below.

Code: Select all

#include <stdio.h>
#include <string.h>
// macro maps a mode 4 screen position to a linear one
#define OPOS(X) (24*((X/320)*320+(X%320)/8+(X%8)*40))
int main (int argc, char *argv[]) {
  unsigned int i=0;
  FILE *f1, *f2;
  char buf[3];
  if (argc!=3) {
    printf ("usage: citat_unpacker <CITAT source file> <target bitmap file>\n");
    return 1;
  } else if (!(f1=fopen(argv[1],"r"))) {
    printf ("Could not open source file \"%s\".\n", argv[1]);
    return 2;
  } else if (!(f2=fopen(argv[2],"w"))) {
    printf ("Could not open output bitmap file \"%s\".\n", argv[2]);
    fclose(f1);
    return 3;
  }
  while (i<0x2800) {
    int c,d=0,j;
    unsigned int k;
    // read one byte in c, if 0x00 or 0xff, read another in d:
    if ((c=getc(f1)) == EOF || ((!c || c==0xff) && ((d=getc(f1)) == EOF))) {
      fprintf (stderr, "error: EOF @ i=0x%0x\n", i);
      fclose(f1);
      fclose(f2);
      return 4;
    }
    switch (c&0xff) {
      case 0x00:
      case 0xff:
        // escaped ("packed") case
        for (j=0;j<d+1;j++) {
          if (fseek(f2, OPOS(i), SEEK_SET) == -1) {
            printf ("Error seeking to position 0x%0x in output file.\n", OPOS(i));
            fclose(f1);
            fclose(f2);
            return 5;
          }
          memset(buf,(~c)&0xff,3); // invert colours
          for (k=0;k<8;k++)
            fwrite(buf,1,3,f2); // write 24 bytes (= 8 pixels each with red, green, blue bytes)
          i++;
        }
        break;
      default:
        // standard bitmapped byte case
        if (fseek(f2, OPOS(i), SEEK_SET) == -1) {
          printf ("Error seeking to position 0x%0x in output file.\n", OPOS(i));
          fclose(f1);
          fclose(f2);
          return 5;
        }
        for (j=0;j<8;j++) {
          memset(buf,((((~c)&0xff)<<j)&0x80)?0xff:0,3); // invert colours
          fwrite(buf,1,3,f2); // red, green, blue
        }
        i++;
        break;
    }
  }
  fclose(f1);
  fclose(f2);
  return 0;
}

User avatar
sbadger
Posts: 314
Joined: Mon Mar 25, 2013 1:12 pm
Location: Farnham, Surrey
Contact:

Re: Unused Citadel bits

Post by sbadger » Sun Dec 10, 2017 6:03 pm

Interesting stuff, thanks for sharing.

At this point the question on the tip of my tongue is. "Can it be converted to NULA 16 colours easily?"
So many projects, so little time...

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sun Dec 10, 2017 7:18 pm

I confess, I had to google what NULA is. >_<

I spent 15 minutes thinking about it and it started to make my brain hurt. I don't know enough about either the NULA board or the game itself at this point to make an educated guess about how difficult that job would be.

Does the NULA board provide any extra RAM, or any way of easing the pressure on the standard 32K? Citadel's RAM usage is pretty tight (stands to reason, really). My feeling at this point is that it could be done, provided you could find some more RAM from somewhere. It might be a bit fiddly. Most of the game's drawing is achieved through a routine at 0x2564 in the BBC disc version, which blits "staged" MODE 2 pixel pairs from a memory buffer at 0x500, so you'd want to intercept that. IIRC there are a few places where it bypasses this, though, and operates on VRAM directly. Also, of course, the game's collision map is in VRAM too (by abusing the MODE 2 flashing colours), so you'd have to provide compatibility for that. Then there are some other things like the lightshows that occur when you use the teleporter, and the "ReProgramming" one, which might be hard to simulate with a different pixel format.

Short answer is I don't know. :) Not yet, anyway.

User avatar
Kecske Bak
Posts: 689
Joined: Wed Jul 13, 2005 7:03 am
Location: Treddle's Wharf, Chigley
Contact:

Re: Unused Citadel bits

Post by Kecske Bak » Sun Dec 10, 2017 7:21 pm

Diminished wrote:I admit I was slightly disappointed to discover that the game spends the overwhelming majority (80% ?) of its time doing absolutely nothing, though. :P
To be fair, Citadel also had to work on an Electron, which could only run in Mode 2 at an arthritic pace.

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sun Dec 10, 2017 7:34 pm

That's true, although the Elk version was kind of an afterthought, I believe?

At any rate, anyone who has tried the Cheat It Again Joe cheat for the game to increase the speed knows that the engine can absolutely rip along at full speed. The main wait loop lives at 0x4110:

Code: Select all

master_speed_wait   lda num_irqs_counter
                    ; CONSTANT: overall game speed
                    cmp #$04
                    bcc master_speed_wait
                    lda #$00
                    sta num_irqs_counter
                    rts
One way I was thinking it might be improved would be to stagger the animations, so it would update the player one frame, then on the next frame it could update one of the enemies, the next frame a different one, and so on. Everything would animate at the same speed, but you would get a much smoother overall effect.

User avatar
Rich Talbot-Watkins
Posts: 1289
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Unused Citadel bits

Post by Rich Talbot-Watkins » Mon Dec 11, 2017 10:18 am

Great post! And welcome to the forum :)

Citadel used to fascinate me when I was young. Back in school, the Citadel 'Zap' hack was going round, which had the option for super speed and added a key to fly. When I got my Master, I was utterly disappointed to discover that it didn't run (it got as far as writing "Welcome to..." and then hung), and I never figured out what the problem was. It was many years later that I borrowed (ahem) a friend's copy of Play It Again Sam on tape which had a Master-compatible version and I was able to play it once more.

Citadel really had a unique look and feel - the use of PLOT 85 triangles as well as conventional tile sprites really made it stand out. I remember seeing all the room names written backwards in the executable, scattered throughout the code. It's great that you've started to reverse-engineer it - it'd be really cool to see a disassembly one day (and to figure out why the Master wasn't happy to run it).

User avatar
davidb
Posts: 2087
Joined: Sun Nov 11, 2007 10:11 pm
Contact:

Re: Unused Citadel bits

Post by davidb » Mon Dec 11, 2017 3:53 pm

It would also be interesting to know why the splashes occur. :)

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Mon Dec 11, 2017 4:46 pm

Oh, I am with you on the splashes, 100%. I've wanted to know the answer to that puzzle since I was ten, but I don't have the answer yet. It is indeed calling the splash subroutine erroneously (that's at 0x414b, and confirmed by breakpoint), but I don't know why. Another puzzle that I got the answer to is whether the "fast climb down" (hold down and jump/action while on a rope or a ladder) was a bug or a feature. (It's a feature -- game specifically checks the "action button is down" flag in the "climb down" code, and I can't see any reason why else it would do that unless that behaviour was intentional.)

It wouldn't run on a Master? I'm surprised -- it's not *that* naughty. It even ran on my 32K Model A!

I have no idea why. I thought it might be that it writes directly to MOS RAM locations for things like the graphics cursor, text cursor, plot colours, and the VSYNC interrupt vector -- but AllMem.txt seems to think those addresses didn't move on the Master. So that's another puzzle to solve.

It's interesting that you bring up the room names being stored backwards and also being "scattered around the code", because one of those things is actually a consequence of the other. There's a master table of room names in one page at 0x2400. This makes sense, since many room names are used several times (The Desert, The Well, The Pyramid, The Temple etc.), and the room data references these. However, rooms whose names are not in the master table have them inlined into the room data itself, immediately after the initial length byte. And this is also why the names are backwards -- since there are two possible sources for the room names, the game stages them on the stack before they're printed. And since stacks are LIFO, they have to be stored backwards.

Another cute thing is that there is a bit in the room data which dictates whether the name should have the word "The" prepended to it -- even those few bytes were precious. (Actually, it's not prepended, it's appended; it goes onto the stack last, and comes off first.)

Here's the room name printing code, which uses the MOS routine (I think this was known as OSWRCH? I've just called it "putchar". Kernighan & Richie come to mind before Wilson & Furber, I'm afraid.)

Code: Select all

                    ; ROOM NAME
                    iny
                    ; cur_room_data_pos=1
                    sty cur_room_data_pos
                    ; fetch first byte of room data, room[1]
                    jsr get_next_room_byte
                    bmi L3486
                    
                    ; if (room[1] & 0x80 == 0), room name is inline
P_push_inline_name  inx
                    ; push name to stack
                    pha
                    tay
                    ; terminator: if (room[1+x] & 0x80) then quit
                    bmi Lroomname_push_done
                    ; room[2+x]
                    jsr get_next_room_byte
                    ; or, if the following byte (room[2+x]) is zero, print "The"
                    beq Lpush_The
                    bne P_push_inline_name
                    
                    ; } else { // room name uses master room table
L3486               and #$7f
                    ; reference in low 7 bits of room[1]
                    tax
                    ldy #$ff
                    
                    ; find pointer to the room name of the Xth room in master table; count the terminators
P_find_room_name    iny
                    lda GLOBAL_ROOM_NAMES,y
                    bpl P_find_room_name
                    dex
                    bpl P_find_room_name
                    
P_push_room_name    pha
                    inx
                    iny
                    lda GLOBAL_ROOM_NAMES,y
                    ; again, if the following byte is zero, end with a "The"; if the top bit is set, there isn't
                    bmi Lroomname_push_done
                    bne P_push_room_name
                    
                    ; } // endif room[1] & 0x80 thing
Lpush_The           lda #$20
                    pha
                    lda #$65
                    pha
                    lda #$68
                    pha
                    lda #$54
                    pha
                    inx
                    inx
                    inx
                    inx
Lroomname_push_done stx foo
                    txa
                    lsr a
                    sec
                    sbc #$09
                    eor #$ff
                    tax
                    ldy #$00
                    jsr move_cursor_print_char_a
                    ldx foo
P_print_room_name   pla
                    and #$7f
                    jsr putchar
                    dex
                    bpl P_print_room_name
                    ldx #$09
                    jsr move_cursor_print_char
                    ldx #$d3
                    lda v12d
                    bpl L34de
                    sed
                    lda #$00
                    sec
                    sbc v12d
                    cld
                    ldx #$dc
L34de               stx _text_bgcolour
                    stx _text_fgcolour
                    tay
                    and #$0f
                    ora #$30
                    tax
                    tya
                    and #$f0
                    lsr a
                    lsr a
                    lsr a
                    lsr a
                    beq L34f3
                    ora #$10
L34f3               ora #$20
                    ldy #$09
                    jsr move_cursor_print_char_b
                    ldx #$d3
                    lda not_in_subroom
                    bpl L3508
                    eor #$ff
                    clc
                    adc #$01
                    ldx #$dc
L3508               stx _text_bgcolour
                    stx _text_fgcolour
                    ora #$30
                    jsr putchar
Here's a peek at how my PHP reimplementation is doing. So, The Well Wheel is meant to look like this:
well-wheel-b.png
My code currently renders it like this:
well-wheel-a.png
The tile in the very top-left hand corner of the screen is the "flood tile". The entire screen is filled with this before any further drawing is done. I haven't bothered to duplicate that piece of code because it's trivial and not really very useful at this point -- instead I start with a blue buffer, so you can still see where the rectangle drawing "hollowing out" code is doing its thing.

The game seems to make a few passes at what I'm calling "decor". One of these occurs before the rectangles and triangles are drawn, and those are the green/black/cyan bricks at the bottom. Then the room is hollowed out by the rectangle code, and the PLOT 85 triangles are drawn (IIRC, there can be up to eight triangles). Then the game makes a second "decor" pass, which draws the shield in the above image. The shield makes use of a tile-flipping system which uses a buffer at 0x400, but that's currently buggy, as is shown by the vomit to the right of the shield.

If you'd like to see the game actually draw the rooms (it normally redefines the graphics window, so you can't see it happen), try these pokes (again, this is the BBC disc version. Well, I think it is. It's just Citadel.ssd on my hard drive, I don't know where I got it from):

EDIT: sorry, copied the wrong pokes ... I seem to have lost the correct ones. D:

EDIT2:
*(0x4122)=0xea
*(0x4123)=0xea
*(0x4124)=0xea
*(0x4127)=0xea
*(0x4128)=0xea
*(0x4129)=0xea
Last edited by Diminished on Fri Dec 15, 2017 8:54 pm, edited 1 time in total.

User avatar
Rich Talbot-Watkins
Posts: 1289
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Unused Citadel bits

Post by Rich Talbot-Watkins » Mon Dec 11, 2017 5:22 pm

Interesting details with the strings! So what's the format for each screen then? I guess it must need a few chunks of data:
- base texture
- filled/solid tilemap (1 bit each I guess)
- overlayed objects
- triangles

There's a thread here in which we talked about Citadel a little bit, and I mentioned the Master incompatibility thing again. I'd forgotten that the fix for the Master was actually to use Sideways RAM to store a bit of extra code which wouldn't fit in main RAM, so clearly the Master OS was much more fussy about not having its workspace trampled. Still can't imagine what would cause it to actually hang though.

One thing about the Master version is that some of the stripe fills (which exist as an undefined side effect of using GCOL with out of range values) look different. It also shows up the fact that the roof of the Witch's House is not straight (strange they didn't get the coordinates right), as on the Master PLOT 85 is more accurate (screengrabs courtesy of DaveJ from that other thread):

BBC B
Image

Master
Image

User avatar
Arcadian
Site Admin
Posts: 2936
Joined: Fri Nov 24, 2000 12:16 pm
Contact:

Re: Unused Citadel bits

Post by Arcadian » Mon Dec 11, 2017 5:35 pm

This is a fascinating thread - if there's a chance of a 16-colour adaption for NuLA that would be cracking.

Likewise - an Elk version that blanked off the garbage at the top/bottom of the screen would be very much welcome too. Is that a possibility?
For a "Complete BBC Games Archive" visit www.bbcmicro.co.uk

Image
ABug SOUTH (Hampshire) (1-3 June 2018)

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Mon Dec 11, 2017 8:04 pm

Rich Talbot-Watkins wrote:Interesting details with the strings! So what's the format for each screen then? I guess it must need a few chunks of data:
- base texture
- filled/solid tilemap (1 bit each I guess)
- overlayed objects
- triangles
I won't lie -- I don't know yet!

The trouble with trying to hack on a game like this is that you can't really modify it in any nontrivial way, because you can't reassemble it. Rather, you have to modify the emulator instead to effect the changes you wanted to make, and recompile that. Now, this is an entirely valid technique which I've used a lot, but it's too clunky to produce much insight.

I'm sure there are people out there who can stare at 8K of code and figure out its twists and turns and what it does just by staring at it -- but I'm not one of those individuals. At least, I reached a certain point where ogling the code and labelling variables and routines wasn't gaining me any extra insight. Part of the problem -- and I suspect everyone else already knows this, but I've never really done this before -- is the reuse of variables in the original game for multiple purposes. So, you can label a variable "vram_ptr" because the game uses it for that purpose in one place, but that's no guarantee that other code doesn't use the same location for some completely unrelated purpose. In order to actually understand the code, I was going to need to reimplement it.

So, I took the attitude that I was just going to do a very literal port of the machine code, not worrying much at all about whether I understood it or not. The only places I've really tried hard to understand what is going on is in areas that affect control flow, and that's because you have to normalise the deranged machine code branching in order to produce something that works in a structured language with no goto keyword.

Instead, my approach has been to instrument the emulator, printing out the program counter when it reaches locations that indicate branches taken, along with the values of relevant variables. The PHP code has also been heavily sprinkled with trace statements, printing out those same program counter addresses. Bugs in the reimplementation can then be found and fixed by comparing the two traces.

Once my code actually works, only then am I going to concern myself seriously with figuring out what it actually does, de-overloading variables that have multiple uses, and documenting the formats -- because at that point you have the power of trial and error on your side. Don't know if vram_ptr is actually used as a VRAM pointer in some other routine? Fine, just rename the variable in that routine and see what happens!

The back-of-my-mind goal here was to think about doing a remake using a modern engine. In the spirit of things like ScummVM, the idea was to have it use the game's original data files as its source material, because this neatly bypasses all issues with copyright. A remake would allow you to do some interesting things like have one continuous scrolling gameworld rather than the "flick-screen" employed by the original game, and there are some other potential gains, like being able to draw the vector stuff (triangles) at native resolution, yielding nice smooth edges. But I have no idea if I'll ever get to that point.

Having said all that, Rich -- I do know that the rectangle drawing code doesn't use a 1-bit bitmap as you suggest -- it's literally a list of rectangles (X, Y, width, height). One reason for this is that the rectangle code isn't just used for hollowing out -- the Witch's House window and door, for example, are non-black rectangles.

That's really interesting about the BBC Master and the Witch's House. I did just try pointing my code at the Witch's House to see if it could extract the triangle coordinates, but it's messing up somewhere before it gets to that point, so I'm not there yet.

Arcadian -- I don't think a non-garbaged Electron version would be possible, unfortunately. The BBC version just reprograms the ... CRTC (??) ... to mask off those garbage areas. If the Electron doesn't have the hardware to do this, I don't think it's possible. You would need to save several kilobytes of RAM in order to make those areas black. Perhaps something insane, like compressing the rooms, tiles, and room names with zlib would get you there ... but I don't know how else you could do it.

paulb
Posts: 811
Joined: Mon Jan 20, 2014 9:02 pm
Contact:

Re: Unused Citadel bits

Post by paulb » Mon Dec 11, 2017 8:44 pm

Diminished wrote:Arcadian -- I don't think a non-garbaged Electron version would be possible, unfortunately. The BBC version just reprograms the ... CRTC (??) ... to mask off those garbage areas. If the Electron doesn't have the hardware to do this, I don't think it's possible. You would need to save several kilobytes of RAM in order to make those areas black. Perhaps something insane, like compressing the rooms, tiles, and room names with zlib would get you there ... but I don't know how else you could do it.
Quite a few late Electron games blanked almost half the screen - I'm wondering if it is related to that tape counter interrupt - but I'd rather see garbage on the screen than have only the bottom half of the display being used by everything. Citadel is at least quite good at making it look like random noise compared to various Peter Scott games that just threw all the sprite data onto the screen. (I think Peter Scott used that half-screen blanking later on, if it was him who ported Predator.) The Tony Oakden games are interesting with regard to blanking, too, although it isn't always seamless and possibly slows those games down somewhat.

ThomasHarte
Posts: 458
Joined: Sat Dec 23, 2000 5:56 pm
Contact:

Re: Unused Citadel bits

Post by ThomasHarte » Mon Dec 11, 2017 8:48 pm

Diminished wrote:Arcadian -- I don't think a non-garbaged Electron version would be possible, unfortunately. The BBC version just reprograms the ... CRTC (??) ... to mask off those garbage areas. If the Electron doesn't have the hardware to do this, I don't think it's possible. You would need to save several kilobytes of RAM in order to make those areas black. Perhaps something insane, like compressing the rooms, tiles, and room names with zlib would get you there ... but I don't know how else you could do it.
It probably depends on how serious you were with that 20% CPU usage comment? Otherwise: when the end-of-display interrupt files busy loop until the end of the final garbage line, set the game palette and return from interrupt. When the 100Hz interrupt fires, busy wait until the first garbage line and then set the palette to all black and return.

Assuming top and bottom garbage sizes are exactly the same, I think that leaves about 100 lines of doing something other than busy waiting: it's however many lines of garbage there are at the bottom plus the amount of time from the top of the display to the 100Hz interrupt minus the number of garbage lines at the top. So it's just the number of lines to the 100Hz interrupt.

You're on an Electron though. So it's around 100 lines with only 24 available RAM cycles on each. So, umm, prima facie 2,400 cycles/frame. Unworkable.

So shift the display to the top, putting all the garbage at the bottom, then just do: from 100Hz interrupt busy wait for start of garbage, set black palette; from end-of-display set real palette and immediately return. Eyeballing it, it looks like about eight character lines of garbage total (?), so the busy loop would occupy about 92 lines, but they're all pixel lines. So I make that approximately 164 pixels lines plus 56 blank lines of real game processing — somewhere around 7,520 cycles/frame.

Which is 18.8% (!) as much as a BBC.

(Also: I'm pretty sure that Spellbound Dizzy, on the 8-bit micros, also uses a vector map with patterned fills plus some pixel sprite adornments, but that's just an assumption based on the switch in developers and corresponding simpler but much larger map, plus the contours involved)

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Mon Dec 11, 2017 11:27 pm

So, I typed up a post about the "20% CPU usage" thing, then I started second-guessing it. Then third-guessing it.

Earlier I posted this loop, where the game spends most of its time:

Code: Select all

master_speed_wait   lda num_irqs_counter
                    ; CONSTANT: overall game speed
                    cmp #$04
                    bcc master_speed_wait
                    lda #$00
                    sta num_irqs_counter
                    rts

I got tired of trying to work it out analytically, so I just modified the emulator to time it: Over the course of 10 minutes, in a room with a reasonable amount of animation going on, the game spent 419 seconds in this loop, which is 70% of its runtime.

It also spends some time waiting for VSYNC in another place; I timed this, too, and that eats an additional 7% of its runtime.

It's tight, isn't it? But it might just be possible.

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Thu Dec 14, 2017 9:40 pm

Finally ... that was more of a struggle than I expected. One self-modification I carelessly missed, an off-by-one bug, and ... some other bug I can't remember. (Git knows.) I'm pretty sure now that the tile rendering works 100%, so I just need to finish figuring out the room format:
well-wheel-attempt-2.png

User avatar
Rich Talbot-Watkins
Posts: 1289
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Unused Citadel bits

Post by Rich Talbot-Watkins » Fri Dec 15, 2017 8:25 am

I'm guessing the lack of the chimney shaft in that picture is some other off-by-one bug (maybe the last rectangle or something?). Does Citadel's sprite routine have the ability to recolour sprites as well as flip them (going by the shield whose right half has different colours to the left half)?

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Fri Dec 15, 2017 9:46 pm

Not sure whether it's an off-by-one on the chimney shaft ... I just looked at that code now, and altering the loop to draw one more rectangle doesn't seem to fix it. At this time my guess is that the rope drawing code is responsible for hollowing that section out, but although I've found and mostly annotated that piece of disassembly, I haven't made much effort to understand it yet. As Jakobsen mentioned when interviewed for Retro Gamer, the ropes and ladders are drawn procedurally.

I was also wrong about the shield. This is why I've avoided publishing anything yet. At some point I'll likely get sick of working on this, and I'll publish everything then, but posting things prematurely that are wrong is just a big waste of everyone's time ...

Now I have more confidence in the tile extracting code, I took a shot at divorcing it from the room code, i.e. calling it directly so that all the tiles could be extracted to bitmaps one after the other, and I got a surprise -- many of the tiles have multiple "layers". The shield is one of them:
057-shield.png
This doesn't use the flipping routine I mentioned earlier (at 0x3274), which mostly seems to be used by animations -- I've confirmed this by NOP-neutering it, and the shield is unaffected. That's not to say that the tile unpacker doesn't contain some piece of "flip and recolour" code which does a similar job, but if that code does exist then it's quickly running out of places to hide.

A mummy with white eyes also appears to be unused content, but I can't absolutely confirm that until a way can be found to make the game display it rather than my own scripts, because evidence suggests I'm not quite bug-free yet:
054-mummy.png
The game doesn't seem to use the white-eyed mummies, so it's hard to say if it's intentional or not ... but that last sprite doesn't look right ...

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sat Dec 16, 2017 1:16 am

Thought of a way to test it -- find another tile with >=4 layers, and modify the emulator to substitute one tile for the other. Don't know why that didn't occur to me immediately.
mummyswap.png
So, those white-eyed mummies can be added to the list of unused content. Also to my list of bugs. :(

User avatar
streaks
Posts: 212
Joined: Thu Oct 13, 2005 2:08 pm
Contact:

Re: Unused Citadel bits

Post by streaks » Sat Dec 16, 2017 4:51 am

My gawd, Beeb people are clever.

A modern native-rez version of the triangle cut-out stuff would be iffy. The lower the resolution the less exact coordinates have to be. Plus any variation in scale resolution throws it. It might work perfectly with the original vertices but I doubt it.

What an interesting thread this is. Remaking a functionally exact copy of Citadel isn't a big feat, but the fun is picking it a part and understanding a childhood favourite I suppose. Well beyond me. My only input to this is the upscaling to native rez stuff. :P
streaksy (at) gmail (dot) com

User avatar
streaks
Posts: 212
Joined: Thu Oct 13, 2005 2:08 pm
Contact:

Re: Unused Citadel bits

Post by streaks » Sat Dec 16, 2017 5:27 am

By the way I'm ALMOST SURE I've seen white-eyed mummies at some point.... trying to work out when....
streaksy (at) gmail (dot) com

User avatar
streaks
Posts: 212
Joined: Thu Oct 13, 2005 2:08 pm
Contact:

Re: Unused Citadel bits

Post by streaks » Sat Dec 30, 2017 9:11 am

Checking this thread sometimes, hoping for an update. It's interesting. :P
streaksy (at) gmail (dot) com

fuzzel
Posts: 299
Joined: Sun Jan 02, 2005 1:16 pm
Location: Oxbridge
Contact:

Re: Unused Citadel bits

Post by fuzzel » Sat Dec 30, 2017 1:43 pm

Hi Diminished, in which issue of Retro User was M. Jakobsen's interview ?

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sat Dec 30, 2017 2:34 pm

streaks wrote:My gawd, Beeb people are clever.
Well, some of them are -- most of them posted in the Kevin Edwards tape protection thread. I don't feel especially clever 90% of the time ...
streaks wrote:Remaking a functionally exact copy of Citadel isn't a big feat, but the fun is picking it a part and understanding a childhood favourite I suppose. Well beyond me. My only input to this is the upscaling to native rez stuff. :P
It's funny. I got here through a process sometimes referred to as "yak shaving". Over the past few years I've been half-heartedly working on a (PC) game engine. I made a series of improvements to it fairly recently (it gained, amongst other things, a dreadful Turing-complete programming language that I just made up) and decided I wanted to find something with which to test it. I thought to myself "you know, I could probably throw together a remake of Citadel in a few days with this -- I wonder how hard it would be to extract all the tiles and room data from the original game? Can't be that hard, surely. What's this I've found, an online 6502 disassembler? Hm. This shouldn't take long."

That was months ago.
streaks wrote:By the way I'm ALMOST SURE I've seen white-eyed mummies at some point.... trying to work out when....
I'm pretty sure I've never seen them anywhere in the BBC versions, so the only place I can think of that they might be hanging out is in the Electron version. As always, I'd be happy to be proven wrong.
streaks wrote:Checking this thread sometimes, hoping for an update. It's interesting. :P
Sorry -- I'm not the world's most mentally durable individual, and I'm prone to motivational lapses. Plus I had to play guitar for a charity gig a few days before Christmas so the last couple of weeks have mostly involved fretting about that. Haha, fretting. Don't get up. I'll see myself out.

Of course now I'm looking at the code again and it's making very little sense. I will either get back to working on it soon or I'll just publish what I've done and see if anyone else is brave enough to take over.
fuzzel wrote:Hi Diminished, in which issue of Retro User was M. Jakobsen's interview ?
May 2012. I don't actually have a proper copy, but to my shame I was able to dig up a dodgy version online.

EDIT: just to be clear, that's Retro Gamer, not Retro User.

User avatar
streaks
Posts: 212
Joined: Thu Oct 13, 2005 2:08 pm
Contact:

Re: Unused Citadel bits

Post by streaks » Sat Dec 30, 2017 9:04 pm

Did you decode the baddie/platform movement patterns? That's probably the main source of hassle in a pixel-perfect remake. Everything else can be eyeballed from a screenshot map and I've still got a fluctuating urge to remake it as well.

The mummies with white eyes... I remember seeing it and thinking "why is that mummy's eyes white?" They might have even flashed white for some reason. And I think I've only ever seen the Beeb version. Wasn't aware of other versions. You know.. it might be something weird like pressing break and their eyes turned white for a second before the screen clears and at the time I'd have attributed it to a palette change instead of a pointer change. I know how little sense that makes but the memory is vague and disconnected and I have no good ideas. I'm also happy to be proven wrong about it. Familiarity isn't proof. I might be committing brainwrong. Whatev..
streaksy (at) gmail (dot) com

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sat Dec 30, 2017 10:49 pm

I haven't looked deeply into the animations, since they're something that's dealt with after the initial room drawing is done.

Having said that, they probably go something like this:

- movement: Each sprite has a bounding box; when it hits one of the edges of the box, it changes direction. I have a feeling that this also happens automatically when the sprite encounters a wall, but that's a guess. Normally a sprite that hits its boundary just reverses its movement direction, but not always (e.g. the Arena bouncing squares make a 90 degree turn when they hit the walls). Of course the monks and the "follower"-type enemies (like the one in the room west of the Main Hall) break this rule by just homing in on you.

- animation: The game uses a few different animation modes -- e.g. layer cycling (Arena bouncing squares), horizontal flips (pig heads), etc. IIRC these should be quite easy to figure out because each different animation mode performs its updates by calling a dedicated subroutine, and the room data which defines the animations just picks one of these subroutines to use for that sprite.

- speed: Animation speeds come from one-byte entries in a constant table of length 16, meaning the speed is encoded as a single 4-bit value which just picks one of these table entries. Offhand I can't remember whether these apply to the movement speed, the animation speed, or both.

Another thing is that most of the game's coordinates are actually packed into the room data on a grid system rather than a pixel-accurate one (grid might be 16x10 but I don't have the code to hand). There's actually a dedicated subroutine for pulling a coordinate pair out of the room data, which is encoded as a single byte whose top and bottom nybbles give you the X and Y coordinates on the grid system.

I don't think any of it is that hard.

User avatar
streaks
Posts: 212
Joined: Thu Oct 13, 2005 2:08 pm
Contact:

Re: Unused Citadel bits

Post by streaks » Sat Dec 30, 2017 11:12 pm

Yeh the theory is easy, but the actual list of bound-box coords would be a big faff to get manually, watching every room with baddies in slow-motion to get the movement nodes. But if it's purely cell-based instead of pixel-based then that definitely takes the edge off. I just checked and I think the reason I thought it was pixel-based is because some of the ornaments (cauldrens etc) are drawn on irregular offsets which threw me.

Here's something... you know the rope monsters? Do you have a rip of that sprite without the rope xor'd over it? I could un-xor it manually I suppose but I've always wondered what they're supposed to look like not xor-imposed over rope. I've always thought of them as lobsters.
streaksy (at) gmail (dot) com

Diminished
Posts: 88
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: Unused Citadel bits

Post by Diminished » Sat Dec 30, 2017 11:29 pm

streaks wrote:Yeh the theory is easy, but the actual list of bound-box coords would be a big faff to get manually, watching every room with baddies in slow-motion to get the movement nodes. But if it's purely cell-based instead of pixel-based then that definitely takes the edge off. I just checked and I think the reason I thought it was pixel-based is because some of the ornaments (cauldrens etc) are drawn on irregular offsets which threw me.
Yeah, don't be fooled by the "flood" background tile repeats. The flood tile can be literally any size and the game will just repeat it at its native size, so the background won't necessarily line up with the grid.

I really should finish the room unpacker. All these tedious numbers would just magically fall out of it if it were working properly. :?
streaks wrote:Here's something... you know the rope monsters? Do you have a rip of that sprite without the rope xor'd over it? I could un-xor it manually I suppose but I've always wondered what they're supposed to look like not xor-imposed over rope. I've always thought of them as lobsters.
Yep:
041-ropecrawler.png
041-ropecrawler.png (330 Bytes) Viewed 737 times

Post Reply