Unused Citadel bits
Re: Unused Citadel bits
(Sorry to spam)
I'm looking at that hacked Citadel image with the screen browsing.. looking at the platform move I wonder I've noticed another characteristic that MAY be unique to it - when they're moving down and touch the floor their movement terminates at an irregular Y offset instead of when it's level with it's Y-cell offset. I don't know if the tile's small height is already accounted for in movement logics though.
I'm looking at that hacked Citadel image with the screen browsing.. looking at the platform move I wonder I've noticed another characteristic that MAY be unique to it - when they're moving down and touch the floor their movement terminates at an irregular Y offset instead of when it's level with it's Y-cell offset. I don't know if the tile's small height is already accounted for in movement logics though.
streaksy (at) gmail (dot) com
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Yeah, I mentioned this. Bits 0-3 choose an "A-type" animation subroutine for the tile from this pair of tables:
Code: Select all
; note that these are all unique
.T_ANIM_A_SUBS_LOW
equb (witch_thing_maybe and &ff)
equb (L3e90 and &ff)
equb (L3e92 and &ff)
equb (animation_thing_1 and &ff)
equb (L3ea6 and &ff)
equb (L3eb4 and &ff)
equb (L3ec5 and &ff)
equb (temple_moose_maybe and &ff)
equb (L3ee2 and &ff)
equb (L3efb and &ff)
equb (temple_guardians and &ff)
.T_ANIM_A_SUBS_HIGH
equb (witch_thing_maybe div &100)
equb (L3e90 div &100)
equb (L3e92 div &100)
equb (animation_thing_1 div &100)
equb (L3ea6 div &100)
equb (L3eb4 div &100)
equb (L3ec5 div &100)
equb (temple_moose_maybe div &100)
equb (L3ee2 div &100)
equb (L3efb div &100)
equb (temple_guardians div &100)
Code: Select all
.T_ANIM_SPEEDS_MASTER equb &01, &02, &03, &04, &05, &07, &0a, &0d, &10, &14, &19, &1e, &28, &3c, &50, &64
Technically these four bits are just a number which just pick another pair of animation subroutines (I called them "B-type" and "C-type") from another four tables:Bit 0 - Tile on horizontal or vertical set movement path flag. Such as rope climbers.
Bit 1 - Tile on diagonal irregular set movement path flag. Such as flying sparklies.
Bit 2 - Tile chase flag. Such as monks.
Bit 3 - Suspect not used. Only some wall tiles (81,88) have this flag set, for no obvious reason.
Code: Select all
.T_ANIM_B_SUBS_LOW
equb (anim_B_sub_hori and &ff)
equb (anim_B_sub_vert and &ff)
equb (anim_B_sub_hori_vert and &ff)
equb (anim_B_sub_4 and &ff)
equb (anim_B_sub_chase and &ff)
equb (anim_B_sub_mummy and &ff)
equb (anim_B_sub_monk and &ff)
equb (anim_B_sub_fires and &ff)
.T_ANIM_B_SUBS_HIGH
equb (anim_B_sub_hori div &100)
equb (anim_B_sub_vert div &100)
equb (anim_B_sub_hori_vert div &100)
equb (anim_B_sub_4 div &100)
equb (anim_B_sub_chase div &100)
equb (anim_B_sub_mummy div &100)
equb (anim_B_sub_monk div &100)
equb (anim_B_sub_fires div &100)
[...]
.T_ANIM_C_SUBS_LOW
equb (draw_room_7 and &ff)
equb (selfmod3_special_1 and &ff)
equb (selfmod3_special_2 and &ff)
equb (anim_c_sub_1a and &ff)
equb (anim_c_sub_1 and &ff)
equb (anim_c_sub_1 and &ff)
equb (anim_c_sub_1 and &ff)
equb (draw_room_7 and &ff)
.T_ANIM_C_SUBS_HIGH
equb (draw_room_7 div &100)
equb (selfmod3_special_1 div &100)
equb (selfmod3_special_2 div &100)
equb (anim_c_sub_1a div &100)
equb (anim_c_sub_1 div &100)
equb (anim_c_sub_1 div &100)
equb (anim_c_sub_1 div &100)
equb (draw_room_7 div &100)
Note that the subroutine used to animate the fires has bit 2 set, but they don't chase the player, so it's not quite true to call bit 2 the "chase bit".
These tables only have 8 entries each, so if bit 3 is set then that's theoretically a bug -- it will overflow the table and very quickly crash the CPU. However, if it only applies to wall tiles, they're not animated, so this situation wouldn't actually come up.
These unused bits (3 and 4) are intriguing, and I wonder if there are any clues as to what they were for in Citadel-early.
The lever logic is hardcoded in the final version, for example in the West Wing:streaks wrote: ↑Thu Nov 01, 2018 8:02 amOnly the platform tile has byte[1] bit 4 set? I thought you meant those two-colour platforms and I thought it accounted for that mysterious splash effect. Moving platforms? Maybe it's to make it respond to levers..? Or like... maybe it means, since they respond to levers, fewer things can be taken for granted in terms of static/mobile logic and certain hard-to-define shortcuts can't be applied to it..? Or maybe like.. it means use the regular movement logic bits to define the movement, but this bit indicates that there's more involved than just HOW it moves (also WHEN it moves)... I dunno.
Code: Select all
cpy #C_RID_WEST_WING_SWITCH_MONK ; &80
bne L2d27
lda #&4
; bring lift online? why does it need two anim slots?
; if (t_anims_y_velocities[0] != 0) {
; t_anims_y_velocities[0] = 0;
; t_anims_y_velocities[1] = 0;
; } else {
; t_anims_y_velocities[0] = 4;
; t_anims_y_velocities[1] = 4;
; }
ldx t_anims_y_velocities
beq L2d21
lda #&0
.L2d21 sta t_anims_y_velocities
sta t_anims_y_velocities + 1 ;&6c
bpl L2d44
As 6502 pointed out, tiles 1-5 fetch their graphical data from tile 0, so it's the other way round; tile 5 can't actually exist without tile 0's data.The mystery crystal tile... I suspect that it was a prototype tile used while developing and tweaking the tile logic, and wasn't even nessecarily intended to be a crystal. It could just be that the five crystals (that don't actually look like crystals) adopted the shape of that test sprite because the shape was pleasing or had aquired sentiment. I've done things like that many times. Since it's deadly and garbled I suggest it was just a dev guinea pig tile that was kept as a reference or testing-ground for bitplay.
IIRC I was wrong about that and the discarded splash actually turned out to be something tangible, possibly the water surface tile, but I don't have my notes on this computer so I can't check. I forget.
It probably just uses the normal collision detection mechanism to decide when to reverse. I haven't checked this though.streaks wrote: ↑Thu Nov 01, 2018 8:26 amI'm looking at that hacked Citadel image with the screen browsing.. looking at the platform move I wonder I've noticed another characteristic that MAY be unique to it - when they're moving down and touch the floor their movement terminates at an irregular Y offset instead of when it's level with it's Y-cell offset. I don't know if the tile's small height is already accounted for in movement logics though.
Last edited by Diminished on Thu Nov 01, 2018 3:59 pm, edited 3 times in total.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Nope. I figured he'd chime in if he was interested in doing so.
No need to go stalking the guy.
No need to go stalking the guy.
Re: Unused Citadel bits
@Diminished
Thank you for correcting my errors. Hope you don't mind me muscling in on your thread.
I've been busy converting the tile data part of the binary file into a beebasm assembly file. This .asm file making viewing and altering the tiles much easier. Thought better to be included as an external file to citadel.asm as it's quite long.
I've checked it outputs the correct binary with a hash function, and it does. I'll post it here when ready, just needs tidying up and commenting. You can add it to your project if you like, but if you don't, that's fine with me too.
Also, found out that if tile date byte[4] is zero then the height of the tile is increased to 32px. Only the monks use this function. Byte[5] then contains info that would of been in byte[4].
Thank you for correcting my errors. Hope you don't mind me muscling in on your thread.
I've been busy converting the tile data part of the binary file into a beebasm assembly file. This .asm file making viewing and altering the tiles much easier. Thought better to be included as an external file to citadel.asm as it's quite long.
I've checked it outputs the correct binary with a hash function, and it does. I'll post it here when ready, just needs tidying up and commenting. You can add it to your project if you like, but if you don't, that's fine with me too.
Also, found out that if tile date byte[4] is zero then the height of the tile is increased to 32px. Only the monks use this function. Byte[5] then contains info that would of been in byte[4].
Last edited by 6502 on Sat Nov 03, 2018 9:01 am, edited 1 time in total.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Nope, not a problem. Information is free, and more eyeballs are welcome. I don't claim any right to the game. But there are certain insights that you'll only get from the asm. (Equally, there are likely to be some things I've got wrong which can be tested by fiddling with the data).
Re: Unused Citadel bits
In the early version of Citadel, once you took a cyan block to Stonehenge and walked under the arch, your energy increased and the cyan block turned into this gold one. Not sure what you was suppose to do with it after that.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Aha. Nice find.
I had a very quick look through Citadel-early to see if there was any clue about what the gold bar might have been for -- I didn't look too deeply into it, but quick searches don't reveal anything being done with tile 31 (&1F). So it doesn't look like MJ had any grand plan for those bars.
Here's the code (only in citadel-early) which performs the blue/gold bar swap:
Code: Select all
2ECD: C9 65 CMP #65
2ECF: D0 13 BNE 2EE4
2ED1: A5 24 LDA 24
2ED3: C9 32 CMP #32
2ED5: D0 0C BNE 2EE3
2ED7: A9 1E LDA #1E <- stone bar
2ED9: A2 1F LDX #1F <- gold bar
2EDB: 20 34 40 JSR 4034 <- item consumption subroutine
2EDE: 90 03 BCC 2EE3
2EE0: 4C 0F 30 JMP 300F
2EE3: 60 RTS
Code: Select all
; CONSTANT: 0x65 is Stonehenge
.Lnot_witch cmp #C_RID_STONEHENGE ; &65
bne Lnot_stonehenge
lda player_x
cmp #C_X_STONEHENGE ; &32
bne L2edb ; return
lda #C_TID_STONE_SLAB ;&1e
jsr consume_item_a
bcc L2edb ; return
; CONSTANT: stone slabs are worth +40 energy? NOTE BCD
lda #C_E_STONEHENGE ; &40
jmp add_remove_energy_d
.L2edb rts
Last edited by Diminished on Sun Nov 11, 2018 5:20 pm, edited 1 time in total.
Re: Unused Citadel bits
Any clues in Citadel-early what the barrel was supposed to be for?
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
I always thought the barrel was intended for jumping onto the roof of the Witch's House. Trampolines may have been added later on (tile ID &3a for the trampos but &18 for the barrel); so I suspect this was the originally intended solution and the trampolines were added to the game later.
- Kecske Bak
- Posts: 720
- Joined: Wed Jul 13, 2005 8:03 am
- Location: Treddle's Wharf, Chigley
- Contact:
Re: Unused Citadel bits
I believe the barrel was originally intended for getting the crown in the cellar.
Re: Unused Citadel bits
Wow, I just found a second Crown in Citadel-early!
The Witch's House in this version has a green crown instead of a yellow one. But there's actually a yellow one in screen (5,3)! Access it by putting a trampoline at the top right of the East Tower, on the very end of the wall. Then use that trampoline to jump to the right as high as possible. I used Escape to slow my fall down a couple of times but that may not be necessary.
So that means there are two Crowns in this version of the game, giving a possible total of 102 points if the game didn't automatically end at 99 (crowns are worth 3 points in this version).
There's also an annoying glitch whereby some items don't respawn when you start a new game. Crystals, the chicken, Egyptian heads, the skull and bones are amongst the items that don't respawn, this being after I completed the game with 99 points on the playthrough before I found the extra crown.
The Witch's House in this version has a green crown instead of a yellow one. But there's actually a yellow one in screen (5,3)! Access it by putting a trampoline at the top right of the East Tower, on the very end of the wall. Then use that trampoline to jump to the right as high as possible. I used Escape to slow my fall down a couple of times but that may not be necessary.
So that means there are two Crowns in this version of the game, giving a possible total of 102 points if the game didn't automatically end at 99 (crowns are worth 3 points in this version).
There's also an annoying glitch whereby some items don't respawn when you start a new game. Crystals, the chicken, Egyptian heads, the skull and bones are amongst the items that don't respawn, this being after I completed the game with 99 points on the playthrough before I found the extra crown.
Last edited by avengahM on Wed Jun 12, 2019 8:45 pm, edited 2 times in total.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Cool find.
I sort of want to have a trawl through Citadel-early and identify what bits of code are different; there might be some interesting artifacts in there and I'm guessing the rendering code is probably identical since you'd have to have that working long before you could start putting a game together, but I'm probably too lazy to do it. That, and I don't really want to deal with the WFDIS disassembler again.
I sort of want to have a trawl through Citadel-early and identify what bits of code are different; there might be some interesting artifacts in there and I'm guessing the rendering code is probably identical since you'd have to have that working long before you could start putting a game together, but I'm probably too lazy to do it. That, and I don't really want to deal with the WFDIS disassembler again.
Re: Unused Citadel bits
How on earth did you manage to find that little gem?
I can't imagine any scenario I would happen to be jumping that direction on a trampoline.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Unfinished business.
I think it's been a couple of years since last I visited my attempt to reimplement the room and tile decoders in a high-level language. At the time I got distracted by slightly more glamorous hacks such as the Randomiser and the Citadel reassembly (for some admittedly very broad definition of "glamorous").
However, I picked this back up a couple of days ago. As I recalled, the tile decoder was working somewhere close to 100%, but the room decoder was still in a bit of a state.
The first thing I wanted to do is get an idea for how many rooms the unpacker could currently handle. It turned out I'd never made a comprehensive list of which room IDs in the game were actually defined, so that was the first job.
I grabbed the screenshot map that's been floating around for a few years, and also dug out my own attract mode patch (the one that allows you to cycle through the rooms during the game's attract mode), and made a new map with the room IDs numbered in hex and decimal.
This is probably the most logical way to lay out the rooms in terms of game logic. Going upwards from any room results in the room ID being decremented by 20. Going downwards results in the room ID being incremented by 20. Moving left and right causes the room ID to be decremented or incremented by 1. So, it makes some sense to lay the map out with a width of 20 cells. Meanwhile, you can have very long horizontal runs of rooms just by laying them out above and below one another. You can see this looking at room 8B/139 and 8C/140, which appear on opposite sides of the map, but are automatically connected in-game. Indeed, with this layout, only two connections needed to be special-cased in the game's code (79 <-> C9 and 64 <-> EF):
This is likely to be how Mike Jakobsen laid the rooms out on his piece of squared paper, so that's kind of interesting.
Next, I modified my PHP so that rather than just having the room ID to be displayed hard-coded, it took it as a command-line parameter. This then allowed me to write a shellscript containing all the defined room IDs. I also took this opportunity to modify the code to output proper PNG files rather than ad-hoc RGBA bitmaps. Now I could get an idea for the script's overall coverage just by running the shellscript. (It didn't do especially well.)
The parts of the room drawing code I currently have reimplemented are as follows:
The next problem was that the PLOT 85 triangles were also unimplemented. The PHP to consume the room bytes and decode the triangles existed, but once again I had made no attempt actually to draw anything to the screen. The triangles were likely one of the reasons why I gave up working on this a couple of years ago, since the PLOT 85 code is obviously part of the MOS, and doesn't exist within Citadel itself at all. I really didn't fancy trying to crib parts of OS 1.2 and reimplement them.
PHP's GD extensions, which I was already using for PNG output, do offer a facility for drawing filled polygons. GD will not trivially draw the Model B-only, "out-of-band", ROM-overflow-exploiting triangles sporting fetching vertical stripes. Nevertheless, it seemed like GD's filled polygon code would at least be a good place to start.
There was a problem, though -- my code was rendering everything to a buffer which was laid out as a MODE 2 screen, rather than linearly (with the buffer being converted to a chunky bitmap immediately before export). I could not easily use GD to draw triangles to a MODE 2 buffer. Eventually I decided the most sensible course of action would be to ditch the MODE 2 back-end representation and just rework everything to use a linear RGBA buffer. All simulated 6502 VRAM operations would now have to go through a translation layer, and each MODE 2 bytewise read or write would have to address the appropriate pair of RGBA pixels instead. I made these modifications and they more or less worked first try, so now I could get the triangle code working.
The existing triangle code was a miasma of misapprehensions. One weirdness here is that vertices 1 and 2 are poked directly to MOS workspace, but vertex 3 is sent as a high level PLOT 85. This effectively means that the first two vertices use MOS internal 160-by-176 graphics coordinates, but the final vertex uses external 1280-by-1024 ones. (It didn't help that AllMem actually has this partially wrong). Another thing I had missed was that the game's setup code redefines the MOS graphics origin to (0, -32). Fortunately I was able to find this setup operation in a nicely readable form in my own BASIC loader for the Citadel reassembly, so I tracked this problem down without too much head-scratching. With the new GD triangle code in place, a small minority of rooms (e.g. Main Hall) now displayed triangles correctly, but many others still resembled abstract expressionist disasters.
A few modifications to beebjit later, I discovered that the triangle code was actually fine, but it was being wrong-footed by the fact that the prior rectangle decoder contained a bug which was leaving the room data pointer in the wrong place. This was caused by the rectangle loop having two ways of jumping back to the top. One 6502 code path decrements the "rectangles remaining" variable before jumping back to the top of the loop, but the other code path does not decrement the counter before jumping. My code was just using a for loop with the decrement operation in the for statement itself, and a continue to simulate the earlier of the two jumps back to the top, incorrectly decrementing the counter in the process. (That is what I get for prematurely replacing ASM-style do/while constructs.) Anyway, for now I just hacked this up by incrementing the counter before the continue statement, fixing both the rectangles and triangles in one go.
The rectangle routine currently uses simulated 6502 code translated stupidly from the disassembly. At some point I will strip this out and instead do rectangle drawing using GD, which will hopefully eliminate more hard-to-understand 6502 soup from the equation.
Lots of rooms are still being displayed wrongly, but this seems to be a bug in the "N3X" function which draws decor tiles, so that will be the next thing to track down. The rope/pillar/ladder code is still absent, so that will follow afterwards. After that, there are stars, item pads, items on those pads, flasks ...
As a curio, here is the Pyramid's lowest chamber, showing its subtractive triangles. I'm using a debug mode which only displays triangle outlines. (The ladder code obviously doesn't exist yet).
I think it's been a couple of years since last I visited my attempt to reimplement the room and tile decoders in a high-level language. At the time I got distracted by slightly more glamorous hacks such as the Randomiser and the Citadel reassembly (for some admittedly very broad definition of "glamorous").
However, I picked this back up a couple of days ago. As I recalled, the tile decoder was working somewhere close to 100%, but the room decoder was still in a bit of a state.
The first thing I wanted to do is get an idea for how many rooms the unpacker could currently handle. It turned out I'd never made a comprehensive list of which room IDs in the game were actually defined, so that was the first job.
I grabbed the screenshot map that's been floating around for a few years, and also dug out my own attract mode patch (the one that allows you to cycle through the rooms during the game's attract mode), and made a new map with the room IDs numbered in hex and decimal.
This is probably the most logical way to lay out the rooms in terms of game logic. Going upwards from any room results in the room ID being decremented by 20. Going downwards results in the room ID being incremented by 20. Moving left and right causes the room ID to be decremented or incremented by 1. So, it makes some sense to lay the map out with a width of 20 cells. Meanwhile, you can have very long horizontal runs of rooms just by laying them out above and below one another. You can see this looking at room 8B/139 and 8C/140, which appear on opposite sides of the map, but are automatically connected in-game. Indeed, with this layout, only two connections needed to be special-cased in the game's code (79 <-> C9 and 64 <-> EF):
Code: Select all
; MAP FLOW HACKS
42ab: P_main_roomchange cpx #$7a
; if (room_id == 0x7a) then room_id = 0xc9;
42ad: bne L42b1
42af: ldx #$c9
; if (room_id == 0xc8) then room_id = 0x79;
42b1: L42b1 cpx #$c8
42b3: bne L42b7
42b5: ldx #$79
; if (room_id == 0x63) then room_id = 0xef;
42b7: L42b7 cpx #$63
42b9: bne L42bd
42bb: ldx #$ef
; if (room_id == 0xf0) then room_id = 0x64;
42bd: L42bd cpx #$f0
42bf: bne L42c3
42c1: ldx #$64
Next, I modified my PHP so that rather than just having the room ID to be displayed hard-coded, it took it as a command-line parameter. This then allowed me to write a shellscript containing all the defined room IDs. I also took this opportunity to modify the code to output proper PNG files rather than ad-hoc RGBA bitmaps. Now I could get an idea for the script's overall coverage just by running the shellscript. (It didn't do especially well.)
The parts of the room drawing code I currently have reimplemented are as follows:
- tile flood fill
- "N3X" decor, round one
- rectangles
- triangles
- "N3X" decor, round two
The next problem was that the PLOT 85 triangles were also unimplemented. The PHP to consume the room bytes and decode the triangles existed, but once again I had made no attempt actually to draw anything to the screen. The triangles were likely one of the reasons why I gave up working on this a couple of years ago, since the PLOT 85 code is obviously part of the MOS, and doesn't exist within Citadel itself at all. I really didn't fancy trying to crib parts of OS 1.2 and reimplement them.
PHP's GD extensions, which I was already using for PNG output, do offer a facility for drawing filled polygons. GD will not trivially draw the Model B-only, "out-of-band", ROM-overflow-exploiting triangles sporting fetching vertical stripes. Nevertheless, it seemed like GD's filled polygon code would at least be a good place to start.
There was a problem, though -- my code was rendering everything to a buffer which was laid out as a MODE 2 screen, rather than linearly (with the buffer being converted to a chunky bitmap immediately before export). I could not easily use GD to draw triangles to a MODE 2 buffer. Eventually I decided the most sensible course of action would be to ditch the MODE 2 back-end representation and just rework everything to use a linear RGBA buffer. All simulated 6502 VRAM operations would now have to go through a translation layer, and each MODE 2 bytewise read or write would have to address the appropriate pair of RGBA pixels instead. I made these modifications and they more or less worked first try, so now I could get the triangle code working.
The existing triangle code was a miasma of misapprehensions. One weirdness here is that vertices 1 and 2 are poked directly to MOS workspace, but vertex 3 is sent as a high level PLOT 85. This effectively means that the first two vertices use MOS internal 160-by-176 graphics coordinates, but the final vertex uses external 1280-by-1024 ones. (It didn't help that AllMem actually has this partially wrong). Another thing I had missed was that the game's setup code redefines the MOS graphics origin to (0, -32). Fortunately I was able to find this setup operation in a nicely readable form in my own BASIC loader for the Citadel reassembly, so I tracked this problem down without too much head-scratching. With the new GD triangle code in place, a small minority of rooms (e.g. Main Hall) now displayed triangles correctly, but many others still resembled abstract expressionist disasters.
A few modifications to beebjit later, I discovered that the triangle code was actually fine, but it was being wrong-footed by the fact that the prior rectangle decoder contained a bug which was leaving the room data pointer in the wrong place. This was caused by the rectangle loop having two ways of jumping back to the top. One 6502 code path decrements the "rectangles remaining" variable before jumping back to the top of the loop, but the other code path does not decrement the counter before jumping. My code was just using a for loop with the decrement operation in the for statement itself, and a continue to simulate the earlier of the two jumps back to the top, incorrectly decrementing the counter in the process. (That is what I get for prematurely replacing ASM-style do/while constructs.) Anyway, for now I just hacked this up by incrementing the counter before the continue statement, fixing both the rectangles and triangles in one go.
The rectangle routine currently uses simulated 6502 code translated stupidly from the disassembly. At some point I will strip this out and instead do rectangle drawing using GD, which will hopefully eliminate more hard-to-understand 6502 soup from the equation.
Lots of rooms are still being displayed wrongly, but this seems to be a bug in the "N3X" function which draws decor tiles, so that will be the next thing to track down. The rope/pillar/ladder code is still absent, so that will follow afterwards. After that, there are stars, item pads, items on those pads, flasks ...
As a curio, here is the Pyramid's lowest chamber, showing its subtractive triangles. I'm using a debug mode which only displays triangle outlines. (The ladder code obviously doesn't exist yet).
Re: Unused Citadel bits
Thanks for writing up your latest progress! I like decoding old map formats, so this is all very interesting stuff.
Now we know what the layout looks like, we can imagine where all those passages down the well could lead to.
And, to me, it looks like access to the title screen was an intentional Easter egg, too.
Now we know what the layout looks like, we can imagine where all those passages down the well could lead to.

And, to me, it looks like access to the title screen was an intentional Easter egg, too.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
I would dearly have loved to have found an unreachable room somewhere. Of course, it would already have been found by hackers decades ago. One slightly disappointing aspect of old Beeb games compared to those of their ROM-based console cousins is that Beeb RAM was so precious there was a much lower chance of unused assets making an appearance. I don't know precisely how much RAM this game has left over (which is a rather loosely-defined quantity anyway), but it is very, very little. The engine isn't at Elite or Exile levels of cunning, but it is viciously optimised by its own standards. I'm certain MJ spent months refining his routines for compactness, just so he could reclaim every last vestige of RAM to express the game world itself.
I think so. The superfluous trampoline placed close by has been noted before. The logical positioning of the room itself is telling when you can clearly see all the other empty places he might have put it.And, to me, it looks like access to the title screen was an intentional Easter egg, too.
By the way, walking off the right side of the title screen puts you into the undefined room 209. You then fall through rooms 229 and 249, and a theoretical room 269; 8-bit wraparound turns 269 into (269 - 256) = 13, which is indeed exactly the room in which you reappear at the top of the map.
So there's no special case or anything there; being able to land on the battlements and skip the bucket puzzle just looks like serendipity.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
That bug didn't last long. It wasn't a tile problem, it was another rectangle problem.
There is a check in the asm to make sure rectangular fills don't underflow video RAM and start merrily scribbling code. My code duplicates this check, but I was setting one of the rectangle loop variables wrongly as a side-effect. So rectangles that touch the top of the screen were being drawn much smaller than they should have been.
Here's the Star Port dish construction.
The green triangle here is a subtractive one that's used to destroy the dish after the statue is recovered from the alien planet.
Coverage is looking pretty good now! Maybe >80% of rooms are perfect (disregarding the parts that still lack code). Still a few strange problems in places, though.
There is a check in the asm to make sure rectangular fills don't underflow video RAM and start merrily scribbling code. My code duplicates this check, but I was setting one of the rectangle loop variables wrongly as a side-effect. So rectangles that touch the top of the screen were being drawn much smaller than they should have been.
Here's the Star Port dish construction.
The green triangle here is a subtractive one that's used to destroy the dish after the statue is recovered from the alien planet.
Coverage is looking pretty good now! Maybe >80% of rooms are perfect (disregarding the parts that still lack code). Still a few strange problems in places, though.
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Collision dumping is now properly implemented (I think).
Ever wonder why the moose head in the Temple only becomes aggressive after you walk past the centre of the room?
I assumed it was done with special code, but dumping the collision for this room reveals this invisible, impermeable rail:
Ever wonder why the moose head in the Temple only becomes aggressive after you walk past the centre of the room?
I assumed it was done with special code, but dumping the collision for this room reveals this invisible, impermeable rail:
Re: Unused Citadel bits
Just wanted to say that it's very interesting to read all of this about one of my favourite games 

0xC0DE
"I program my home computer / Beam myself into the future"
Follow me on Twitter
Visit my YouTube channel featuring my games and demos for Acorn Electron and BBC Micro
"I program my home computer / Beam myself into the future"


- Rich Talbot-Watkins
- Posts: 1707
- Joined: Thu Jan 13, 2005 5:20 pm
- Location: Palma, Mallorca
- Contact:
Re: Unused Citadel bits
+1
Always happy when this thread resurfaces! Citadel has always been a standout game on the Beeb, and it's fascinating to learn some of its inner secrets.
Always happy when this thread resurfaces! Citadel has always been a standout game on the Beeb, and it's fascinating to learn some of its inner secrets.
- Dave Footitt
- Posts: 935
- Joined: Thu Jun 22, 2006 10:31 am
- Location: Abandoned Uranium Workings
- Contact:
Re: Unused Citadel bits
Yeah I love reading about this - Citadel was a fantastic game and it's really interesting to see some of the internals!
Keep up the good work
Keep up the good work

- scarybeasts
- Posts: 608
- Joined: Tue Feb 06, 2018 7:44 am
- Contact:
Re: Unused Citadel bits
Love me some Citadel!
I think it's one of the few games I properly completed. I still remember the emotions from when I first accidentally blundered into one of the hidden crown rooms.
Very interesting to see how it works behind the scenes -- some of the level layout and renderer is quite unlike other platform games.
When beebjit hits v1.0 (timing model stable for all significant test cases), I'll be looking for a Citadel expert to record a replayable speedrun to see how fast a competent speed run takes when replayed at full emulator turbo speed
Cheers
Chris
I think it's one of the few games I properly completed. I still remember the emotions from when I first accidentally blundered into one of the hidden crown rooms.
Very interesting to see how it works behind the scenes -- some of the level layout and renderer is quite unlike other platform games.
When beebjit hits v1.0 (timing model stable for all significant test cases), I'll be looking for a Citadel expert to record a replayable speedrun to see how fast a competent speed run takes when replayed at full emulator turbo speed

Cheers
Chris
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
Thanks, gents.
I have a few more notes -- nothing earth-shatteringly interesting but I'll write them down or I'll forget them.
MODE 2 AND COLLISIONS
MODE 2, as you know, maps one byte to a pixel pair, interleaving the bits. Each byte written to the screen in Citadel looks like this:
b, g and r are blue, green and red. L and R refer to "left pixel" and "right pixel". All fairly predictable. Rather than messing around endlessly shifting and masking to divorce the left pixel from the right one, MJ tended to take the approach of simply pre-defining a pool of two-pixel, single-byte building blocks from which everything was constructed (I believe I've referred to these as "base pairs" in some parts of the disassembly). So if you wanted to draw an object in black and white, you'd build it by choosing from four bytes: B/B, B/W, W/B and W/W. I imagine this wasn't an uncommon way of doing things.
With palettes selected so that the effects of bits 6 and 7 are invisible on-screen, those two digits c0 and c1 are employed to encode two bits of collision detection. Effectively this means that the collision detection operates at half the horizontal resolution of the graphics, since the high bits of both the left pixel and the right pixel are borrowed for this purpose.
The unpacker's collision dumper currently encodes c0 as red and c1 as green, yielding:
Green -- the pain collision type -- is weird. There may be a bug in what I have done, but it seems that all painful objects are striped, rather than solid "green".
Room 138: Collision: I am not sure why it is done that way -- I will need to revisit the collision detection routines to see exactly how the hit detection works.
One curiosity with this, though, is the Witch's House roof, which is (assuming no bugs) solidly "green".
House: Collision: Solid green therefore seems to work similarly to yellow, in that you can stand on it. But if you've ever wondered why catching the edge of this roof a glancing blow causes you to lose energy, well, the above may be why:
(Note 3 energy loss) Bug? Feature? To be fair, jumping off a trampoline into the edge of a roof won't do you much good.
PYRAMID
PLOT 85 triangles may be drawn with alternative logical plot modes. The only room which seems to use anything other than the overwrite mode is the main Pyramid, which XORs the triangle in order that the door at the bottom (which is for some reason drawn first) remains visible.
ROM WRITES
Citadel gleefully scribbles the ROM area. I added a switch which extends the screen from 176 pixels high to 192, so you can see what's in the overwrite. It's not very interesting, but it seems it was a way of compacting some of the tile routines a little bit by omitting some boundary checking:
PROGRESS
Now added:
- item pads
- ladders
- ropes
- stars (working, but not being displayed in some rooms yet for some reason)
I have a few more notes -- nothing earth-shatteringly interesting but I'll write them down or I'll forget them.
MODE 2 AND COLLISIONS
MODE 2, as you know, maps one byte to a pixel pair, interleaving the bits. Each byte written to the screen in Citadel looks like this:
Code: Select all
&80 &40 &20 &10 &8 &4 &2 &1
c1 c0 bL bR gL gR rL rR
With palettes selected so that the effects of bits 6 and 7 are invisible on-screen, those two digits c0 and c1 are employed to encode two bits of collision detection. Effectively this means that the collision detection operates at half the horizontal resolution of the graphics, since the high bits of both the left pixel and the right pixel are borrowed for this purpose.
The unpacker's collision dumper currently encodes c0 as red and c1 as green, yielding:
Code: Select all
00 black no collision
01 red climbable
10 green painful [*]
11 yellow impermeable
Room 138: Collision: I am not sure why it is done that way -- I will need to revisit the collision detection routines to see exactly how the hit detection works.
One curiosity with this, though, is the Witch's House roof, which is (assuming no bugs) solidly "green".
House: Collision: Solid green therefore seems to work similarly to yellow, in that you can stand on it. But if you've ever wondered why catching the edge of this roof a glancing blow causes you to lose energy, well, the above may be why:
(Note 3 energy loss) Bug? Feature? To be fair, jumping off a trampoline into the edge of a roof won't do you much good.
PYRAMID
PLOT 85 triangles may be drawn with alternative logical plot modes. The only room which seems to use anything other than the overwrite mode is the main Pyramid, which XORs the triangle in order that the door at the bottom (which is for some reason drawn first) remains visible.
ROM WRITES
Citadel gleefully scribbles the ROM area. I added a switch which extends the screen from 176 pixels high to 192, so you can see what's in the overwrite. It's not very interesting, but it seems it was a way of compacting some of the tile routines a little bit by omitting some boundary checking:
PROGRESS
Now added:
- item pads
- ladders
- ropes
- stars (working, but not being displayed in some rooms yet for some reason)
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
I might be able to do this. I actually have some background in speed running, although I'm keeping quiet about the details...scarybeasts wrote: ↑Thu Oct 15, 2020 8:45 amWhen beebjit hits v1.0 (timing model stable for all significant test cases), I'll be looking for a Citadel expert to record a replayable speedrun to see how fast a competent speed run takes when replayed at full emulator turbo speed![]()
A few notes on this:
I know that at normal speed the game can be done in a little under an hour. Basic strategies include exploiting pressing ESCAPE to backtrack quickly to room entry points, although you have to keep an eye on your energy budget. A question I'm not 100% sure on is what the fastest route is from the Main Hall out to the Wasteland (a trip that needs to be made a fair amount in completing the game). You can either go along the ground floor, or along the first floor, and timewise there's not much in it.
The observant may have noticed when playing this game that the initial position of enemies and lifts is to some degree randomised -- sometimes you have to wait for lifts and enemies to move into their needed positions before you can proceed, which can cost you seconds here and there. So, at the margin, there's also some scope there for what speed runners refer to as "RNG manipulation".
- Snuggsy187
- Posts: 173
- Joined: Wed Apr 03, 2019 9:53 pm
- Contact:
Re: Unused Citadel bits
Always wondered about that damage from the edge of the roof !Bug? Feature? To be fair, jumping off a trampoline into the edge of a roof won't do you much good.

Thanks for the description on the sprite storage - took me ages to vaguely understand this some years ago, and had since forgotten all about it !
Top work, glad you picked this up again, it's an excellent read.

PUSH PARCHMENT > POKE LOCK > PULL PARCHMENT
Re: Unused Citadel bits
Thank you so much for a fascinating thread.
I loved Citadel, these details you have uncovered are wonderful to know after all these years
I’ve dug out my old Citadel maps, which I painstakingly drew out ‘back then’ and popped them onto Flickr, as someone reading this thread might enjoy them too. https://www.flickr.com/gp/jez/7e04S2
Thanks again for your time delving into Citadel, maybe it’s time to play it through again on my trusty Beeb, feeling inspired.
It would be great to extract the sounds as well as the sprites. The sound design was simple but very distinctive.
Interesting side note on Citadel, when I play this on the MiSTer FPGA, when going between each room the whole screen flashes disturbingly, in the same colour of the room title text, as if it is changing mode or something and the LCD/HDMI does not mask this, but when I play the MiSTer through a CRT this does not happen. Do you know what exactly is happening between each screen transition?
Cheers,
Jez
I loved Citadel, these details you have uncovered are wonderful to know after all these years
I’ve dug out my old Citadel maps, which I painstakingly drew out ‘back then’ and popped them onto Flickr, as someone reading this thread might enjoy them too. https://www.flickr.com/gp/jez/7e04S2
Thanks again for your time delving into Citadel, maybe it’s time to play it through again on my trusty Beeb, feeling inspired.
It would be great to extract the sounds as well as the sprites. The sound design was simple but very distinctive.
Interesting side note on Citadel, when I play this on the MiSTer FPGA, when going between each room the whole screen flashes disturbingly, in the same colour of the room title text, as if it is changing mode or something and the LCD/HDMI does not mask this, but when I play the MiSTer through a CRT this does not happen. Do you know what exactly is happening between each screen transition?
Cheers,
Jez
- Diminished
- Posts: 598
- Joined: Fri Dec 08, 2017 9:47 pm
- Contact:
Re: Unused Citadel bits
These are lovely. Fountain pen! Who still uses those?!j6wbs wrote: ↑Fri Oct 16, 2020 2:12 amI’ve dug out my old Citadel maps, which I painstakingly drew out ‘back then’ and popped them onto Flickr, as someone reading this thread might enjoy them too. https://www.flickr.com/gp/jez/7e04S2
I had similar ones (albeit with less conscientious cartography) but they haven't survived unfortunately. I think mine ended up scrawled across multiple sheets.
This is interesting.Interesting side note on Citadel, when I play this on the MiSTer FPGA, when going between each room the whole screen flashes disturbingly, in the same colour of the room title text, as if it is changing mode or something and the LCD/HDMI does not mask this, but when I play the MiSTer through a CRT this does not happen. Do you know what exactly is happening between each screen transition?
From memory, it does something like reprogram the CRTC to limit the visible screen area to the first few lines (the ones that contain the header text and inventory sprites) while it invisibly redraws the room. Then it switches it back to full screen afterwards. I'm not sure why this might cause different behaviour on an LCD compared to a CRT -- any theories, anyone?
Citadel is rebuildable now, so you can actually disable this behaviour and leave the CRTC configuration untouched during room redraw. Or you could probably change it to switch the screen off entirely rather than leaving the text and inventory lines visible.
I can probably produce some sort of modified version if it's a problem.
Re: Unused Citadel bits
Side note on the Electron version. I think it switches to MODE 6 with colour palette off between rooms to speed up drawing. MODE 2 is horribly slow on the Elk due to memory contention. It would be cool to investigate other differences between Elk and Beeb versions
0xC0DE
"I program my home computer / Beam myself into the future"
Follow me on Twitter
Visit my YouTube channel featuring my games and demos for Acorn Electron and BBC Micro
"I program my home computer / Beam myself into the future"

