{"id":17720150,"url":"https://github.com/jbreckmckye/nortis","last_synced_at":"2025-03-31T07:08:02.090Z","repository":{"id":208117596,"uuid":"692699862","full_name":"jbreckmckye/nortis","owner":"jbreckmckye","description":"Tetris clone for PlayStation 1 (PSX)","archived":false,"fork":false,"pushed_at":"2024-08-25T10:05:25.000Z","size":5116,"stargazers_count":300,"open_issues_count":0,"forks_count":3,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-08-25T23:40:52.237Z","etag":null,"topics":["c","game","playstation","playstation-homebrew","ps1","psnoobsdk","psone","psone-game","psx","retrogaming","tetris-clone"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jbreckmckye.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-17T10:03:36.000Z","updated_at":"2024-08-25T21:59:50.000Z","dependencies_parsed_at":"2024-08-24T23:25:58.282Z","dependency_job_id":"f2350038-7a08-4137-9447-e4e3bb79259f","html_url":"https://github.com/jbreckmckye/nortis","commit_stats":null,"previous_names":["jbreckmckye/notris","jbreckmckye/nortis"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Fnortis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Fnortis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Fnortis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Fnortis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jbreckmckye","download_url":"https://codeload.github.com/jbreckmckye/nortis/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246429469,"owners_count":20775806,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","game","playstation","playstation-homebrew","ps1","psnoobsdk","psone","psone-game","psx","retrogaming","tetris-clone"],"created_at":"2024-10-25T15:26:28.084Z","updated_at":"2025-03-31T07:08:02.070Z","avatar_url":"https://github.com/jbreckmckye.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"# Nortis: a Tetris clone for the PlayStation 1\n\nNortis (formerly Notris) is a homebrew PSX game, written in C using modern tooling. It's fully playable on original hardware and is\npowered by [PSNoobSDK](https://github.com/Lameguy64/PSn00bSDK).\n\n![img.png](blog/img.png)\n\nView the PSX codebase [here](psx).\n\n## Why write a PSX game in 2024?\n\nLast year I got my hands on a rare, black PlayStation 1. This is called a Net Yaroze and is a special console that can\nplay homebrew games as well as ordinary PSX titles. It was part of a special Sony project to get hobbyists and students\ninto the games industry.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/yaroze.jpg\" width=50% height=50% alt=\"a Net Yaroze console\"\u003e\n\u003c/p\u003e\n\nYaroze games were very limited, as Sony didn't want bedroom coders competing with commercial developers. They could only\nbe played on other Yarozes or on [special demo discs](https://www.breck-mckye.com/blog/2016/11/net-yaroze/). They had to\nfit entirely within system RAM without access to the CD-ROM. Despite these limitations, the Yaroze fostered a passionate\ncommunity of indie developers.\n\nAnd now I had my own. Which got me thinking: what was it actually like, to write a PlayStation game?\n\nThis is about how I wrote a simple homebrew PSX game myself, using an open-source version of the libraries but still\nrunning on original hardware and written in classic C.\n\n## PlayStation development in the 1990s\n\n[Skip this section](#writing-my-own-psx-game)\n\nPSX games were typically written in C on Windows 9X workstations. The official devkit was a pair of ISA expansion cards\nthat slotted into a common IBM PC motherboard and contained the entire PSX system chipset, video out, and extra RAM (8mb\ninstead of 2mb). This provided TTY and debugger output to the host machine.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/devkit.jpg\" width=50% height=50% alt=\"a DTL-H2000 devkit\"\u003e\n\u003c/p\u003e\n\nYou might have heard about blue PlayStations. These were for QA rather than development and are identical to retail\nunits except that they can play burned CD-ROMs. However, at least one company sold a special addon to convert them into\ndevkits:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/devkit-blue.jpg\" width=50% height=50% alt=\"blue debug devkit\"\u003e\n\u003c/p\u003e\n\nThe design was very developer-friendly. You could play your game on CRT with normal controllers whilst stepping through\nGDB breakpoints on your Windows 95 PC, leafing through a thick textbook of C SDK functions.\n\nIn principle, a PSX developer could work entirely in C. The SDK comprised a set of C libraries called PSY-Q, and\nincluded a compiler program `ccpsx` that was really just a frontend over GCC. This supported a range of optimisations, \nlike code inlining and loop unrolling, although performance critical sections still warranted hand-optimised assembly.\n\n(You can read about those optimisation in [these SCEE conference slides](https://psx.arthus.net/sdk/Psy-Q/DOCS/CONF/SCEE/96April/optimize.pdf)).\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/optimise.png\" width=50% height=50% alt=\"example optimisations\"\u003e\n\u003c/p\u003e\n\nC++ was supported by `ccpsx` but had a reputation for generating 'bloated' code, as well as slower compile times.\nReally, C was the lingua franca of PSX development, but some projects made use of dynamic scripting languages on top of \na base engine. For example, _Metal Gear Solid_ used TCL for level scripting; and the _Final Fantasy_ games went some\nways further and implemented their own bytecode languages for battles, field and minigame systems. (You can learn more about \nthis [here](https://youtu.be/S-8PVydb9CM?si=oU0Rqy6bsd0EVq_F)).\n\n(_For further reading take a look at https://www.retroreversing.com/official-playStation-devkit_)\n\n## Writing my own PSX game\n\n[Skip this section](#hello-psx)\n\nBut I came to this from a very different perspective: a software engineer in 2024 who mostly worked on web applications.\nMy professional experience had almost exclusively been in high level languages like JavaScript and Haskell; I'd done a\nlittle OpenGL work and C++, but modern C++ is almost a completely different language to C.\n\nI knew PSX SDKs existed for languages like Rust, but I wanted to experience the flavour of 'real' PSX programming, the \nway it had been done back in the 90s. So it would be modern toolchains and open source libraries, but C all the way\nthrough.\n\nThe game needed to be something 2D that could be prototyped in a couple of days. I settled for a Tetris clone - I \nfigured that would be complex _enough_ to experience what I wanted to.\n\n### Prototyping in JavaScript\n\nThe first step was to build a prototype in a familiar technology. This would allow me to nail down the basic design,\nthen the logic could be translated piecemeal into C.\n\nAs a web developer the most obvious technology for prototyping was JavaScript: it's simple, concise, easy to debug, and\nit sports the HTML5 `\u003ccanvas\u003e` graphics API. Things came together very quickly\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/web.png\" width=50% height=50% alt=\"web version of tetris\"\u003e\n\u003c/p\u003e\n\nAt the same time, I was wary that more high-level JavaScript features would be difficult to port. Anything using\nclasses or closures would need to be completely rewritten, so I was careful to restrict myself to a simple, procedural\nsubset of the language.\n\n### Learning C!\n\nNow, I actually had an ulterior motive taking on this project: it was an excuse to finally learn C. The language loomed\nlarge in my mind and I'd begun to develop an inferiority complex over not knowing it.\n\nC has an intimidating reputation and I feared horror stories of dangling pointers, misaligned reads and the dreaded\n`segmentation fault`. More precisely: I was worried that if I tried to learn C, and failed, I'd discover\nthat I wasn't actually a very good programmer after all.\n\nTo keep things easy I figured I could use [SDL2](http://www.libsdl.org/) to handle the input and graphics, and compile for my desktop \nenvironment (MacOS). That would give me a fast build / debug cycle and make the learning curve as gentle as possible.\n\nDespite my fears, I found C incredibly fun. Very quickly it 'clicked' for me. You start from very simple primitives - \nstructs, chars, functions - and build them up into layers of abstraction to eventually find yourself sat atop an entire \nworking system.\n\n![\"Nortis\" with SDL2](blog/sdl2.png)\n\nThe game only took a couple of days to port, and I was very satisfied with my first true C project. And I hadn't had a\nsingle segfault!\n\nSDL had been a pleasure to work with, but there were a few aspects that required me to allocate memory dynamically.\nThis would be a no-no on the PlayStation, where the `malloc` provided by the PSX kernel doesn't work properly. And the \ngraphics pipeline would be an even bigger leap...\n\n## Hello PSX!\n\nWhen it comes to PlayStation homebrew there are two major choices for your SDK. Either:\n\n1. The original **Psy-Q** library with a modernised toolchain (\"Nugget\")\n2. The new **PSNoobSDK** which is open source\n\nThere are a couple of other options like the C++ **Psy-Qo**, and you can even forgo any SDK just to do memory-mapped I/O\nyourself - but I was not brave enough for that.\n\nThe biggest issue with Psy-Q is that it's still Sony proprietary code, even 30 years later. Legally, any homebrew built\nwith it is at risk. That is what sunk the [Portal64](https://github.com/Valkirie/portal64) demake: it statically linked `libultra`, which is Nintendo's\nproprietary N64 SDK.\n\nBut to be honest, the main reason I chose [PSNoobSDK](https://github.com/Lameguy64/PSn00bSDK) was that it's very well \ndocumented and simple to set up. The API is _very_ similar to Psy-Q: in fact for many functions I could just consult the\nprinted references that came with my Yaroze.\n\nIf me using a non-authentic SDK offends the PSX purist in you, feel free to quit reading now in disgust.\n\n![PSNoobSDK](blog/psnoob.png)\n\nMy first task was a kind of hello world: two squares on a coloured background. Sounds simple, right?\n\n## A primer on PSX graphics\n\n[Skip this section](#show-me-some-code)\n\n(*Some of this is simplified. For a more authoritative guide read the [PSNoobSDK tutorial](http://lameguy64.net/tutorials/pstutorials/chapter1/1-display.html))\n\nTo start with, think of the PSX VRAM as a big 1024 by 512 canvas of 16-bit pixels. Altogether that makes 1 megabyte of \nmemory shared by framebuffers and textures. We can choose the resolution of the output framebuffer - even up to 640x480 \npixels if we're greedy - but more resolution = fewer textures.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/psx-vram.png\" width=50% height=50% alt=\"psx vram model\"\u003e\n\u003c/p\u003e\n\nMost PSOne games (and... games generally) have a notion of dual-buffered rendering: whilst one frame is being prepared,\nthe other is sent to screen. So we need to allocate two frame buffers:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/psx-vram-allocation.png\" width=50% height=50% alt=\"psx vram model\"\u003e\n\u003c/p\u003e\n\n(Now you can see why 640x480 isn't practical - there isn't enough space for two 480p buffers. But this mode CAN be used\nby things like the PSX startup logo, which doesn't need much animation)\n\nThe buffers (referred to alternately as display and draw environments) are swapped every frame. Most PSX games target \n30fps (in North America) but the actual VSync interrupt comes at 60hz. Some games manage to run at full 60 fps - Tekken\n3 and Kula World (Roll Away) come to mind - but obviously then you need to render in half the time. Remember we only\nhave 33 Mhz of processing power.\n\n### Shapes to screen\n\nBut - how does the drawing process work? This is done by the GPU, but the PSX GPU works very differently to a modern\ngraphics card. Essentially, every frame the GPU is sent an ordered list of graphics 'packets' or commands. \"Draw a\ntriangle here\", \"load this texture to skin the next quad\", et cetera.\n\nThe GPU does not do 3D transformations; that is the job of the GTE (Geometry Transform Engine) coprocessor. The GPU \ncommands represent purely 2D graphics, already manipulated by 3D hardware.\n\nThat means the path of a PSX pixel goes as follows:\n\n![Pixel path](blog/pixel-path.png)\n\n1. The program on the CPU creates the primitive (e.g. a textured triangle)\n2. (Optionally) the GTE does 3D maths / transformations on the primitive\n3. These primitives / packets are linked into an 'ordering table'\n4. An SDK function goes through the ordering table and sends the packets to the GPU\n5. The GPU processes the packets / commands from a FIFO queue\n6. The GPU outputs VRAM pixels (rasterisation)\n7. The framebuffers are swapped and the displayEnv is set\n8. Video output hardware scans lines from the raster into a video signal\n9. Your (analog) TV turns lines into an electron scanning beam! Coloured dots of phosphor glow!\n\nSo in pseudocode the PSX frame loop (basically) goes like this\n\n```\nFrameBuffer [0, 1]\nOrderingTable [0, 1]\n\nid = 1 // flips every frame\n\nloop {\n  // Game logic\n\n  // Construct the next screen by populating the current ordering table\n  MakeGraphics(OrderingTable[id])\n  \n  // Wait for last draw to finish; wait for vertical blank\n  DrawSync()\n  VSync()\n  \n  // The other frame has finished drawing in background, so display it\n  SetDisplay(Framebuffer[!id])\n  \n  // Start drawing current frame\n  SetDrawing(Framebuffer[id])\n  \n  // Send ordering table contents to GPU via DMA\n  Transfer(OrderingTable[id])\n\n  // Flip\n  id = !id\n}\n```\n\nYou can see from this that whilst frame 1 is on-screen, frame 2 is still being painted, and frame 3 is potentially still\nbeing 'constructed' by the program itself. Then after DrawSync / VSync we send frame 2 to the TV, and get the GPU\ndrawing frame 3.\n\n![Graphics cycle](blog/gfx-cycle.png)\n\n### Ordering tables and z-indexes\n\nAs mentioned, the GPU is a completely 2D piece of hardware, it doesn't know about z-coordinates in 3D space. There is no\n\"z-buffer\" to describe occlusions - i.e. which objects are in front of others. So how are items sorted in front of \nothers?\n\nThe way it works is that the ordering table comprises a reverse-linked chain of graphics commands. These are traversed\nback-to-front to implement the **painter's algorithm**.\n\n![How a scene is made from an ordering table](blog/ordering-table-scene.png)\n\nTo be precise, the ordering table is a reverse-linked list. Each item has a pointer to the previous item in the list, \nand we add primitives by inserting them into the chain. Generally OTs are initialised as a fixed array, with each\nelement in the array representing a 'level' or layer in the display. OTs can be nested for implementing complex scenes.\n\nThe following diagram helps explain it ([source](https://psx.arthus.net/sdk/Psy-Q/DOCS/TECHNOTE/ordtbl.pdf))\n\n![Ordering table](blog/ordering-table.png)\n\nThis approach isn't perfect and sometimes PSX geometry shows weird clipping, because each poly can only be at a single\n'z index' in screen space, but it works well enough for most games. These days such limitations are considered part of \nthe PSX's distinctive charm.\n\n## Show me some code!\n\n[Skip this section](#back-to-the-project)\n\nWe've talked a lot of theory - what does this look like in practice?\n\nThis section won't go through all the code line-by-line but should give you a taster for PSX graphics concepts. If you\nwant to see full code go to 👉 [`hello-psx/main.c`](hello-psx/main.c).\n\nAlternatively if you're not a coder, feel free to skip ahead. This is just for techies who are curious.\n\nThe first thing we need are some structs to contain our buffers. We will have a `RenderContext` that contains two\n`RenderBuffers`, and each `RenderBuffer` will contain:\n\n- a `displayEnv` (specifies VRAM area of current display buffer)\n- a `drawEnv` (specifies VRAM area of current draw buffer)\n- an `orderingTable` (reverse linked list that will contain pointers to graphics packets)\n- a `primitivesBuffer` (structs for graphics packets / commands - including all the polygons)\n\n```c\n#define OT_SIZE 16\n#define PACKETS_SIZE 20480 \n\ntypedef struct {\n  DISPENV displayEnv;\n  DRAWENV drawEnv;\n  uint32_t orderingTable[OT_SIZE];\n  uint8_t primitivesBuffer[PACKETS_SIZE];\n} RenderBuffer;\n\ntypedef struct {\n  int bufferID;\n  uint8_t* p_primitive; // next primitive\n  RenderBuffer buffers[2];\n} RenderContext;\n\nstatic RenderContext ctx = { 0 };\n```\n\nEvery frame we will invert the `bufferID` which means we can seamlessly work on one frame whilst the other is being\ndisplayed. A key detail is that the `p_primitive` is constantly kept pointed at the next byte in the current\n`primitivesBuffer`. It is **imperative** that this is incremented every time a primitive is allocated and reset at the \nend of every frame.\n\nPretty much before anything we need to set up our display and draw environments, in reverse configuration so that\n`DISP_ENV_1` uses the same VRAM as `DRAW_ENV_0`, and vice versa\n\n```c\n//                        x  y   width height\nSetDefDispEnv(DISP_ENV_0, 0, 0,   320, 240);\nSetDefDispEnv(DISP_ENV_1, 0, 240, 320, 240);\n\nSetDefDrawEnv(DRAW_ENV_0, 0, 240, 320, 240);\nSetDefDrawEnv(DRAW_ENV_1, 0, 0,   320, 240);\n```\n\nI am being quite condensed here - but from here every frame basically goes like\n\n```c\nwhile (1) {\n  // do game stuff... create graphics for next frame...\n\n  // at the end of loop body\n  \n  // wait for drawing to finish, wait for next vblank interval\n  DrawSync(0);\n  VSync(0);\n\n  DISPENV* p_dispenv = \u0026(ctx.buffers[ctx.bufferID].displayEnv);\n  DRAWENV* p_drawenv = \u0026(ctx.buffers[ctx.bufferID].drawEnv);\n  uint32_t* p_ordertable = ctx.buffers[ctx.bufferID].orderingTable;\n  \n  // Set display and draw environments\n  PutDispEnv(p_dispenv);\n  PutDrawEnv(p_drawenv);\n  \n  // Send ordering table commands to GPU via DMA, starting from the end of the table\n  DrawOTagEnv(p_ordertable + OT_SIZE - 1, p_drawEnv);\n  \n  // Swap buffers and clear state for next frame\n  ctx.bufferID ^= 1;\n  ctx.p_primitive = ctx.buffers[ctx.bufferID].primitivesBuffer;\n  ClearOTagR(ctx.buffers[0].orderingTable, OT_SIZE);\n}\n```\n\nThis might be a lot to take in. Don't worry.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blog/shocked.png\" width=50% height=50% alt=\"psx vram model\"\u003e\n\u003c/p\u003e\n\nIf you really want to understand this, the best thing is to take a look at [`hello-psx/main.c`](hello-psx/main.c).\nEverything is commented in a fair amount of detail. Alternatively, go through the\n[PSNoobSDK tutorial](http://lameguy64.net/tutorials/pstutorials/)... it's pretty terse and quite clearly written.\n\nNow... how do we draw stuff? We write structs into our primitives buffer. This buffer is typed as just a big ole list of \n`chars` so we cast into our shape / command struct, then advance the primitives buffer pointer using `sizeof`:\n\n```c\n// Create a tile primitive in the primitive buffer\n// We cast p_primitive as a TILE*, so that its char used as the head of the TILE struct\nTILE* p_tile = (TILE*)p_primitive;\nsetTile(p_tile); // very very important to call this macro\nsetXY0 (p_tile, x, y);\nsetWH  (p_tile, width, width);\nsetRGB0(p_tile, 252, 32, 3);\n  \n// Link into ordering table (z level 2)\nint z = 2;\naddPrim(ordering_table[buffer_id] + z, p_primitive);\n\n// Then advance buffer\nctx.p_primitive += sizeof(TILE);\n```\n\nWe just inserted a yellow square! 🟨 Try to contain your excitement.\n\n## Back to the project\n\n[Skip this section](#building-and-running)\n\nAt this point in my journey all I really had was a \"hello world\" demo program, with basic graphics and controller input.\nYou can see from the code in [`hello-psx`](hello-psx) that I was documenting as much as possible, really for my own\nbenefit. A working program was a positive step but not a real game.\n\n![Hello PSX game](blog/hello-psx.png)\n\nIt was time to _get real_.\n\n### Displaying text\n\nOur game needs to show the score.\n\nThe PSX doesn't really give you much in the way of text rendering. There is a debug font (shown above) but it's extremely\nbasic - for development and not much else.\n\nInstead, we need to create a font texture, and use that to skin quads. I created a monospace font with https://www.piskelapp.com/\nand exported that as a transparent PNG:\n\n![Font](blog/font.png)\n\nPSX textures are stored in a format called TIM. Each TIM file comprises:\n\n- a raster image (uncompressed) in 4, 8, 16 or 24 bits per pixel\n- a colour lookup table (CLUT) that acts like the texture's palette\n- coordinates for loading into VRAM\n\nBecause the VRAM location of the texture is 'baked into' the TIM file, you need a tool to  manage your texture \nlocations. I recommend https://github.com/Lameguy64/TIMedit for this.\n\nFrom there we just have a function to skin a bunch of quads, with the UV offsets based on each ASCII value.\n\n### The play frame\n\nWe need a space for the pieces to fit into. It would be easy to use a boring white rectangle for this, but I wanted\nsomething that felt more... PlayStation\n\n![Midpoint](blog/midpoint.png)\n\nOur user interface is coming together. What about the pieces?\n\n### Tetronimos (Notronimos?)\n\nNow comes some important visual design. Ideally each brick\nshould be visually distinct with sharp, shaded edges. We do this with two triangles and a quad:\n\n![Notronimos](blog/notronimos.png)\n\nAt 1x native resolution the effect would be less clear, but it still looks nice and chunky:\n\n![Native resolution](blog/notronimos-1x.png)\n\nIn the first prototype of my game I implemented a full naive rotation system, that would actually flip the block 90\ndegrees on a centre point. It turns out that isn't actually a great approach, because it causes the blocks to\n'wobble', shifting up and down as they rotate:\n\n![Wobble wobble](blog/rotate.png)\n\nInstead, the rotations are hardcoded to be 'nice' instead of 'accurate'. A Piece is defined within a grid of 4x4 cells, and\neach cell can be filled or unfilled. There are 4 rotations. Therefore: rotations can just be arrays of four 16-bit \nnumbers. Which looks like this:\n\n```c\n/**\n * Example: T block\n *\n * As a grid:\n *\n * .X.. -\u003e 0100\n * XXX. -\u003e 1110\n * .... -\u003e 0000\n * .... -\u003e 0000\n * \n * binary      = 0b0100111000000000\n * hexadecimal = 0x4E00\n * \n */\n\ntypedef int16_t ShapeBits;\n\nstatic ShapeBits shapeHexes[8][4] = {\n  { 0 },                              // NONE\n  { 0x0F00, 0x4444, 0x0F00, 0x4444 }, // I\n  { 0xE200, 0x44C0, 0x8E00, 0xC880 }, // J\n  { 0xE800, 0xC440, 0x2E00, 0x88C0 }, // L\n  { 0xCC00, 0xCC00, 0xCC00, 0xCC00 }, // O\n  { 0x6C00, 0x8C40, 0x6C00, 0x8C40 }, // S\n  { 0x0E40, 0x4C40, 0x4E00, 0x4640 }, // T\n  { 0x4C80, 0xC600, 0x4C80, 0xC600 }, // Z\n};\n```\n\nExtracting the cell values is just a case of simple bit masking:\n\n```c\n#define GRID_BIT_OFFSET 0x8000;\n\nint blocks_getShapeBit(ShapeBits s, int y, int x) {\n  int mask = GRID_BIT_OFFSET \u003e\u003e ((y * 4) + x);\n  return s \u0026 mask;\n}\n```\n\nThings are coming together now with momentum.\n\n### A title screen\n\nIt was at this point I hit a snag: randomisation. Pieces have to appear in a random fashion in order for the game to\nbe worth playing, but randomisation is hard with computers. On my MacOS version, I was able to 'seed' the random number\ngenerator with the system clock, but the PSX doesn't have an internal clock.\n\nInstead, a solution many games take is to make the player create the seed. The game displays a splash or title screen with\ntext like 'press start to begin', and then the timing is taken from that button press to create the seed.\n\nI created a 'graphic' by declaring some binary-encoded `int32`s where each `1` bit would be a 'pixel' in a row of\nbricks:\n\n!['Nortis' spelled out in binary numbers](blog/title-mask.png)\n\n\nWhat I wanted was for the lines to gradually dissolve into view. First I needed a function that would effectively 'keep\ntrack' of how many times it was called. C makes this easy with the `static` keyword - if used inside a function, the same\nmemory address and contents are re-used on the next invocation.\n\nThen inside this same function is a loop that goes through the x/y values of the 'grid', and decides whether enough\nticks have happened to show the 'pixel':\n\n```c\nvoid ui_renderTitleScreen() {\n  static int32_t titleTimer = 0;\n  titleTimer++;\n  \n  // For every 2 times (2 frames) this function is called, ticks increases by 1\n  int32_t ticks = titleTimer / 2;\n  \n  // Dissolve-in the title blocks\n  for (int y = 0; y \u003c 5; y++) {\n    for (int x = 0; x \u003c 22; x++) {\n      int matrixPosition = (y * 22) + x;\n      if (matrixPosition \u003e ticks) {\n        break; // because this 'pixel' of the display is not to be displayed yet\n      }\n    \n      int32_t titleLine = titlePattern[y];\n      int32_t bitMask = titleMask \u003e\u003e x;\n      if (titleLine \u0026 bitMask) { // there is a 'pixel' at this location to show\n        ui_renderBlock( /* skip boring details */);\n      }\n    }\n  }\n}\n```\n\nWe're _almost_ there now.\n\n![Title](blog/title.png)\n\n### Adding the SCEA logo\n\nClassic PSX games boot in two stages: first the Sony Computer Entertainment screen, then the PSX logo. But if we compile\nand run the `hello-psx` project it doesn't. The second screen is just black. Why is that?\n\n![startup.png](blog/startup.png)\n\nWell, the SCE splash comes from the BIOS, as does the PSX boot sound, but the famous logo is actually part of the disc \nlicense data. It's there to act like a 'seal of authenticity' - so anyone pirating a game is copying _Sony's_ IP as well\nas the publisher's. This gave Sony more legal instruments to crack down on software piracy.\n\nIf we want _our_ game to show the logo, we need to provide a license file extracted from an ISO, but for the sake of \ncopyright we have to `.gitignore` it. \n\n```xml\n\u003clicense file=\"${PROJECT_SOURCE_DIR}/license_data.dat\" /\u003e\n```\n\nOkay. _Now_ we are ready.\n\n## The moment of truth\n\nThis all started with an impulse purchase, my black Yaroze PlayStation. Ironically it wouldn't actually be playing my \ngame as it still possessed its anti-piracy hardware. I didn't fancy installing a modchip on such a priceless piece of \nPSX history - not with my soldering skills.\n\nInstead, I had to track down a modded grey PlayStation, one that still had a decent drive. I figured that the point of \nmy project was to write a _true_ PlayStation game and that meant using a _true_ PlayStation.\n\nI also had to find the right media. The PSX laser is quite picky and modern CD-Rs tend to be much less reflective than\npressed discs. My first attempts with grocery story CDs were a waste of time, and over the space of about two weeks I\ncreated a lot of coasters.\n\nThis was a dark moment. Had I gotten all this way, only to fail at _burning the CD_?\n\nAfter several weeks I got my hands on some special JVC Taiyo Yuden stock. From what I could read these were quite\nspecialist, and typically used in industrial applications. I burned the first disc in the platter and I expected the\nworst.\n\n[This was the moment of truth:](https://www.youtube.com/watch?v=oNlyFrWR-t0)\n\u003cdiv align=\"center\" style=\"position: relative\"\u003e\n      \u003ca href=\"https://www.youtube.com/watch?v=oNlyFrWR-t0\"\u003e\n         \u003cimg src=\"https://img.youtube.com/vi/oNlyFrWR-t0/0.jpg\"\u003e\n      \u003c/a\u003e\n\u003c/div\u003e\n\nThe PlayStation boot sequence boomed from my tiny monitor speakers and the classic \"PS\" logo splashed across the screen\nin vibrant 640-by-480 resolution. The BIOS had clearly found _something_ on that disc, but a lot could fail after this\npoint. The screen fell black and I strained my ears for the telltale _click-click-click_ of a drive error.\n\nInstead, one by one, little coloured squares began to blink in from the darkness. Line by line they spelled out a word:\n`NOTRIS`. Then: `PRESS START TO BEGIN`. The text beckoned at me. What would happen next?\n\nA game of Tetris, of course. Why was I surprised? **Writing your own PlayStation game in C is actually very simple: all \nit requires is to make no mistakes whatsoever**. That is computing for you, especially the low level stuff. It is hard,\nand sharp, and it is beautiful. Modern computing has softer edges but the essentials haven't changed.\n\nThose of us who love computers need to have something slightly wrong with us, an irrationality to our rationality, a \nway to deny all the evidence of our eyes and ears that the hostile box of silicon is dead and unyielding. And fashion by \ncunning machinery the illusion that it lives.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbreckmckye%2Fnortis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjbreckmckye%2Fnortis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbreckmckye%2Fnortis/lists"}