Skip to content

A PLAY EXPERIENCE MAKER'S WORK LOG FOR FUTURE SELF©2001 – 2023 Kyle Li 李肅綱 All Rights Reserved.

Author: admin

8bitworkshop Notes – Include binary files

Posted on October 5, 2026October 5, 2026 by admin

By default, 8bitworkshop uses chr_generic.s as the source of CHR data. The CHR pattern tables is embeds directly as byte arrays in the .s assembly file within the web editor. However, I can’t edit that file directly using YY-CHR or my custom Processing 4 tools. I have no intention of migrating to 8bitworkshop entirely, as I want to keep my established ROM hacking tools in my workflow.

  • Go to the top menu and select Upload… to upload your .chr file. Note that this step can be a bit unintuitive: you need to select [Open As New Project] so it gets saved as a standalone file in your workspace directory.
  • Once uploaded, add a resource directive at the very top of your main file to include the .chr asset.
// -----------------------------------------------------------------------------
// 8BITWORKSHOP ASSET LINK DIRECTIVES
// -----------------------------------------------------------------------------
//#resource "my.chr"
  • In my chr_include.s file (a new .s file I created based on chr_generic.s to avoid confusion), I replaced the original byte array with .incbin "my.chr".
  • My chr_include.s now looks like this, with all the original .byte lines removed:
;;{w:8,h:8,bpp:1,count:256,brev:1,np:2,pofs:8,remap:[0,1,2,4,5,6,7,8,9,10,11,12]};;
.segment "CHARS"
.incbin "my.chr"
  • After linking it in the main file, it was all set. Now I can edit the .chr file with my custom tools and update it whenever needed.”
// -----------------------------------------------------------------------------
// 8BITWORKSHOP ASSET LINK DIRECTIVES
// -----------------------------------------------------------------------------
//#resource "my.chr"
//#link "chr_include.s"

The great news is I can do the same for external Nametable files. Upload individual Nametable file and add a resource directive for each of them in the main file. In the “nametable.s”:

.export _nametable_0, _nametable_1, _nametable_2, _nametable_3
.export _nametable_4, _nametable_5, _nametable_6, _nametable_7

.segment "RODATA"

_nametable_0: .incbin "pic0.nam"
_nametable_1: .incbin "pic1.nam"
_nametable_2: .incbin "pic2.nam"
_nametable_3: .incbin "pic3.nam"
_nametable_4: .incbin "pic4.nam"
_nametable_5: .incbin "pic5.nam"
_nametable_6: .incbin "pic6.nam"
_nametable_7: .incbin "pic7.nam"

Compared to the .byte array format, this approach is much cleaner.

8bitworkshop Notes – Display an external Nametable V1

Posted on October 4, 2026October 10, 2026 by admin

Nametable file format:

The ultimate goal is to create an external file that stores all the nametable data.

.export _nametable_0, _nametable_1, _nametable_2, _nametable_3
.export _nametable_4, _nametable_5, _nametable_6, _nametable_7

.segment "RODATA"

_nametable_0: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

_nametable_1: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

_nametable_2: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

_nametable_3: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

_nametable_4: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

_nametable_5: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

_nametable_6: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

_nametable_7: .repeat 30
                  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$41,$41,$41,$41
              .endrepeat

Nametables are stored as raw byte arrays in PRG ROM and copied or decompressed into PPU VRAM when loaded.

  • Uncompressed Nametables ($1,024$ bytes each): I can store roughly 15 to 20 uncompressed Nametables before filling up your 32 KB PRG ROM (leaving ~12 KB for game engine code, physics, music, and logic).
  • RLE-Compressed Nametables (~150 to 300 bytes each): I can easily link 60 to 100+ compressed Nametables in 32 KB PRG ROM using vram_unrle().

Suggested Memory Budget Breakdown in 32 KB PRG ROM:

  • C Engine & Game Logic ~10 KB
  • neslib Library & Runtime ~2 KB
  • Audio Engine & SFX (FamiTone2) ~3 KB
  • Available Space for Backgrounds ~17 KB
  • Max Nametable Capacity:
    • Uncompressed ~17 Full Screens
    • *RLE Compressed Allocation ~60 to 100+ Screens

*An RLE-Compressed Nametable is a background screen layout that has been processed using Run-Length Encoding (RLE) to significantly reduce its size in PRG ROM.Instead of storing all 1,024 individual tile bytes for a full $32 \times 30$ grid, RLE compresses repeated identical tiles (like blank sky, floor blocks, or solid walls) into compact sequence commands.

Uncompressed (16 bytes):
$00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $4A, $4B, $00, $00, $00, $00

RLE Compressed (6 bytes):
[10x $00], [1x $4A], [1x $4B], [4x $00]

Display 1 nametable as the background:

//#link "chr_generic.s"
//#link "nametable.s"

#include <nes.h>
#include "neslib.h"


// Declare external assembly symbol exported in nametable.s
extern const unsigned char nametable_0[];

// Sample palette (Background palette 0)
const unsigned char palette[16] = {
    0x0f, 0x00, 0x10, 0x30, // Black background, Dark Gray, Light Gray, White
    0x0f, 0x00, 0x10, 0x30,
    0x0f, 0x00, 0x10, 0x30,
    0x0f, 0x00, 0x10, 0x30
};

void load_attributes_separately(const unsigned char *tiles, const unsigned char *attributes) {
    // 1. Write tile bytes
    vram_adr(0x2000);
    vram_write(tiles, 960);

    // 2. Write attribute bytes to $23C0 (Nametable 0 attribute offset)
    vram_adr(0x23C0);
    vram_write(attributes, 64);
}

void main(void) {
    bank_bg(0);  // Backgrounds from $0000 (Font/Tiles)
    bank_spr(1); // Sprites from $1000 (Player)
    // Disable rendering while writing to PPU memory
    ppu_off();

    // Load background color palette
    pal_bg(palette);

    // Set PPU address to Nametable 0 start ($2000)
    vram_adr(0x2000);

    // Write the full 960-byte nametable array from nametable.s
    // (32 rows x 30 columns = 960 bytes)
    vram_write(nametable_0, 960);

    // Reset scroll position
    scroll(0, 0);

    // Enable rendering
    ppu_on_all();

    // Infinite loop to keep rendering active
    while (1) {
        ppu_wait_frame();
    }
}

8bitworkshop Notes – Player Running and Jumping

Posted on October 4, 2026October 4, 2026 by admin
// -----------------------------------------------------------------------------
// 8BITWORKSHOP ASSET LINK DIRECTIVES
// -----------------------------------------------------------------------------
//#link "chr_generic.s"

#include "neslib.h"

// Physics Constants
#define FLOOR_Y     192
#define PLAYER_X    32
#define GRAVITY     1
#define JUMP_FORCE -10

// -----------------------------------------------------------------------------
// PALETTE SETUP
// -----------------------------------------------------------------------------
const unsigned char sprite_palette[16] = {
    0x0F, 0x16, 0x27, 0x30, // Palette 0: Black, Red, Orange, White
    0x0F, 0x0F, 0x0F, 0x0F,
    0x0F, 0x0F, 0x0F, 0x0F,
    0x0F, 0x0F, 0x0F, 0x0F
};

// -----------------------------------------------------------------------------
// 2x2 METASPRITES (16x16 pixels)
// Structure per tile: x_offset, y_offset, tile_id, attributes
// Terminated with 128 (0x80)
// -----------------------------------------------------------------------------
const unsigned char metasprite_run1[] = {
    0,  0, 0xDC, 0, // Top-Left tile
    8,  0, 0xDE, 0, // Top-Right tile
    0,  8, 0xDD, 0, // Bottom-Left tile
    8,  8, 0xDF, 0, // Bottom-Right tile
    128
};

const unsigned char metasprite_run2[] = {
    0,  0, 0xE0, 0,
    8,  0, 0xE2, 0,
    0,  8, 0xE1, 0,
    8,  8, 0xE3, 0,
    128
};

const unsigned char metasprite_jump[] = {
    0,  0, 0xE8, 0,
    8,  0, 0xEA, 0,
    0,  8, 0xE9, 0,
    8,  8, 0xEB, 0,
    128
};

// Running animation frame table
const unsigned char* const run_animation[] = {
    metasprite_run1,
    metasprite_run2
};

// -----------------------------------------------------------------------------
// PLAYER STATE VARIABLES
// -----------------------------------------------------------------------------
unsigned char player_y = FLOOR_Y;
signed char player_vy = 0;
unsigned char is_grounded = 1;

unsigned char anim_timer = 0;
unsigned char anim_frame = 0;

// -----------------------------------------------------------------------------
// FUNCTIONS
// -----------------------------------------------------------------------------

void update_player(unsigned char pad) {
    // Jump trigger on A button
    if ((pad & PAD_A) && is_grounded) {
        player_vy = JUMP_FORCE;
        is_grounded = 0;
    }

    // Apply Velocity & Gravity
    player_y += player_vy;
    player_vy += GRAVITY;

    // Floor Boundary
    if (player_y >= FLOOR_Y) {
        player_y = FLOOR_Y;
        player_vy = 0;
        is_grounded = 1;
    }
}

void update_animation(void) {
    if (is_grounded) {
        anim_timer++;
        if (anim_timer >= 6) { // Advance frame every 6 ticks (~10 FPS)
            anim_timer = 0;
            anim_frame = (anim_frame + 1) % 2; // Toggle between frame 0 and 1
        }
    }
}

void draw_player(void) {
    const unsigned char* current_metasprite;

    // Select airborne or grounded MetaSprite
    if (!is_grounded) {
        current_metasprite = metasprite_jump;
    } else {
        current_metasprite = run_animation[anim_frame];
    }

    // Render 2x2 MetaSprite to OAM
    oam_meta_spr(PLAYER_X, player_y, 0, current_metasprite);
}

// -----------------------------------------------------------------------------
// MAIN PROGRAM LOOP
// -----------------------------------------------------------------------------
void main(void) {
    // Write sprite palettes to PPU
    pal_spr(sprite_palette);

    // Turn on graphics rendering
    ppu_on_all();

    while (1) {
        // Read Controller 1 Input
        unsigned char pad = pad_trigger(0);

        update_player(pad);
        update_animation();

        // Clear sprite hardware memory and re-draw
        oam_clear();
        draw_player();

        // Synchronize with NES TV refresh rate (60 FPS)
        ppu_wait_nmi();
    }
}

Using C with the neslib library in 8bitworkshop lets you prototype game loops, player gravity, and scrolling background mechanics cleanly without writing raw 6502 assembly right away.

Core Technical Concept

To simulate an endless runner on NES:

  1. Player Position: The player stays fixed horizontally on the X-axis (e.g., $X = 32$) and only moves vertically on the Y-axis when jumping or falling under gravity.
  2. Scrolling: The NES background hardware fine-scrolls horizontally (scroll_x++). As background tiles move off-screen to the left, new tiles and obstacles spawn on the right seam.
  3. Collision Detection: Check bounding box collisions between the fixed player character sprite and incoming obstacle coordinates.

[Breakpoint] The code runs, but nothing shows up on the screen.

To handle a character made of 4 hardware tiles ($16 \times 16$ pixels in a $2 \times 2$ grid), you can use MetaSprites or standard relative coordinate drawing functions. neslib provides built-in functions like oam_meta_spr() specifically designed for multi-tile objects.

[Breakpoint] I added logic for character animation and collision, and while the code checked out, the screen just flashed between black and red with nothing rendering. I soon realized I hadn’t linked the default CHR file to the game likely because my original prompt didn’t include details about the CHR data and color palette.

// -----------------------------------------------------------------------------
// METASPRITES
// -----------------------------------------------------------------------------
const unsigned char metasprite_run1[] = {
    0,  0, 0x01, 0, // Top-Left
    8,  0, 0x02, 0, // Top-Right
    0,  8, 0x11, 0, // Bottom-Left
    8,  8, 0x12, 0, // Bottom-Right
    128
};

const unsigned char metasprite_run2[] = {
    0,  0, 0x03, 0,
    8,  0, 0x04, 0,
    0,  8, 0x13, 0,
    8,  8, 0x14, 0,
    128
};

const unsigned char metasprite_jump[] = {
    0,  0, 0x05, 0,
    8,  0, 0x06, 0,
    0,  8, 0x15, 0,
    8,  8, 0x16, 0,
    128
};

const unsigned char* const run_animation[] = {
    metasprite_run1,
    metasprite_run2
};

The flickering occurs because check_collision() is evaluating to 1 (true) constantly on every frame, causing the game loop to alternate palette color 0 between red (0x16) and black (0x0F).

The Cause: Initial Y-Coordinate Overlap

  1. FLOOR_Y is set to 192.
  2. player_y starts at 192.
  3. player_y + 16 equals 208.
  4. obstacle_y is set to FLOOR_Y + 8 = 200.

Because (player_y + 16) ($208$) is greater than or equal to obstacle_y ($200$), the vertical collision condition (player_y + 16) >= obstacle_y evaluates to TRUE continuously, even when the obstacle is on the far right of the screen ($X = 240$).

Adjust obstacle_y to sit at FLOOR_Y and refine the bounding box collision logic to require both top and bottom $Y$-axis overlaps:

[Breakpoint] Screen was still back, I finally realized I need to set up color palettes.

The color palette setup is incomplete. At boot, the NES PPU contains random or zeroed palette RAM. Calling pal_col(0, ...) only modifies index 0 (the background fill color), leaving all sprite colors uninitialized or black.

What Is Happening

  1. Undefined Sprite Palettes: Without initializing sprite palettes using pal_spr() or pal_bg(), the PPU reads color 0x0F (black) for every sprite pixel. The sprites are actually rendering, but they are invisible against the black background.
  2. False Collision Flashing: Because the sprite tiles are invisible, it felt like a random flashing bug. Modifying obstacle_x -= 2 altered how often the invisible obstacle bounding box collided with the player.

In NES hardware, palettes are defined in 16-byte arrays (4 palette groups of 4 colors each). Index 0 of each group is the shared background color.

Add palette initialization inside the main() setup before entering the while(1) loop:

// Define background and sprite palette arrays (16 bytes each)
const unsigned char bg_palette[16] = {
    0x0F, 0x00, 0x10, 0x30, // Palette 0 (Black, Gray, Light Gray, White)
    0x0F, 0x01, 0x11, 0x21, // Palette 1
    0x0F, 0x06, 0x16, 0x26, // Palette 2
    0x0F, 0x09, 0x19, 0x29  // Palette 3
};

const unsigned char sprite_palette[16] = {
    0x0F, 0x16, 0x27, 0x30, // Palette 0: Red, Orange, White (Player)
    0x0F, 0x11, 0x21, 0x30, // Palette 1: Blue, Light Blue, White (Obstacle)
    0x0F, 0x09, 0x19, 0x29, // Palette 2
    0x0F, 0x0F, 0x0F, 0x0F  // Palette 3
};

In 8bitworkshop, comment directives like //link "chr_generic.s" tell the build system to assemble and link the CHR graphics asset bank into the final ROM cartridge. Without it, the NES PPU renders completely blank or garbage graphics banks. This was the final missing link. Here is the working code for character running.

Native Web Speech API

Posted on September 16, 2026September 16, 2026 by admin

I was looking for a small murder mystery program, and I found this gem of a submission to js13kGames in 2021.

  • Blanks Detective (2021) — Procedural Deduction Mystery
  • Developer: Daniel Cohen
  • Zip Size: 12.38 KB
  • Format: A logic-grid mystery/detective game featuring procedurally generated crime cases. You cross-reference clues and fill out board deduction matrices (similar to Clue) to catch criminals.

Impressively, the game manages to include text-to-speech within its 13 KB budget. It is made possible with the Native Web Speech API. Here is a quick demo, it works so well!

🔍 Suspect Generator

🕵️‍♂️
Press “Generate Suspect” to inspect a case.
Initializing Web Speech API…

AI: You are _____ correct.

Posted on August 23, 2026August 23, 2026 by admin

Me: When did official Famicom cartridges start using Hyundai chip?

AI: Official Japanese Famicom cartridges never used a Hyundai chip. Official Nintendo Famicom games used custom mask ROMs and mapper chips (such as MMC1, MMC3, or discrete logic chips) manufactured by companies like Sharp, Ricoh, and NEC. Hyundai (a South Korean company) only produced the licensed Hyundai Comboy (a localized NES/Famicom variant for South Korea), which is distinct from official Japanese hardware.

Could you clarify if you meant a specific mapper chip, a foreign variant like the Comboy, or a particular game title so I can find the exact hardware details you are looking for?

Me: There is a Hyundai chip inside of HAL’s Metal Slader Glory.

AI: You are entirely correct! Hyundai manufactured Static RAM (SRAM) chips.

While Nintendo’s custom mappers (like the MMC5 inside Metal Slader Glory) and the Mask ROM chips holding the game data were sourced from Japanese companies like Sharp, Ricoh, or NEC, Nintendo routinely outsourced standard SRAM work-RAM/save-RAM chips to third-party semiconductor companies—including Hyundai Electronics (now SK Hynix).

Nintendo began sourcing Hyundai SRAM chips for official game cartridges in 1989.

  • The Shift: Prior to 1989, Nintendo mostly used Japanese SRAM suppliers like Sanyo, Sony, or Sharp. As the Famicom and NES market exploded globally, Nintendo diversified its supply chain to South Korea’s rapidly growing semiconductor industry to cut costs and avoid component shortages.
  • The Chips: You will frequently find the Hyundai HY6264 (an 8KB SRAM chip) or the Hyundai HY6116 (a 2KB SRAM chip) soldered onto official Nintendo circuit boards from late 1989 through the mid-1990s.

Metal Slader Glory (released in July 1991) is a prime example of this sourcing. While the game famously relied on Nintendo’s highly complex MMC5 mapper to manage its massive 1MB of graphical and cinematic data, the standard underlying RAM on some of those specific game boards was a Hyundai-branded CMOS Static RAM chip used to handle the temporary memory.

Would you like to analyze the PCB layout and chip codes of other specific MMC5 games, or are you looking to trace SRAM manufacturer variations for retro game preservation/verification?

Me: You mentioned “Nintendo’s highly complex MMC5 mapper to manage its massive 1MB of graphical and cinematic data” but MSG uses a 512KB PRG ROM and a 512KB CHR ROM, doesn’t that mean half of that 1MB is program code?

AI: You are completely correct. My previous phrasing was inaccurate.

The 1MB (8 Megabits) of total storage in Metal Slader Glory is split exactly in half: 512KB of PRG (Program) ROM and 512KB of CHR (Character/Graphics) ROM.

Half of that data consists of program code, engine instructions, text scripts, and sound data, while the other half contains the graphical tiles.

How the MMC5 Used Both Halves for Visuals

While the code lives in the PRG side, the MMC5 mapper blurred the lines of how that program data translated to the screen. It managed the split data to achieve the game’s famous cinematic look through several unique hardware tricks:

  • ExRAM (Extended RAM): The MMC5 contains 1KB of internal RAM. This allowed the game to bypass the Famicom’s strict background limitations. It could assign unique palettes to individual 8×8 pixel tiles (instead of the standard 16×16 grids), allowing the PRG code to render highly detailed, comic-book-style backgrounds using the CHR data.
  • Vertical Split Screen: The mapper allowed the PRG code to split the screen vertically. One side could display static, highly detailed text or portraits, while the other side scrolled smoothly.
  • Massive CHR Bankswitching: The 512KB CHR ROM is massive for the Famicom. The MMC5 could instantly swap out graphic banks frame-by-frame. This allowed the PRG program to string together hundreds of unique graphical tiles rapidly, creating the illusion of full-screen, cinematic animations.

If you are interested, we can look closer at the MMC5’s ExRAM functionality, or we can break down how other large Famicom games (like Dragon Quest IV or Kirby’s Adventure) divided their PRG and CHR data. Which direction would you like to explore?

英傑大戦 PL084 Rem (レム)

Posted on July 1, 2026July 1, 2026 by admin

PL084 Rem (レム) from the Re:Zero collaboration is a highly specialized, incredibly fun combat unit with a built-in mechanical twist that can catch opponents completely off guard.

Here is a breakdown of how Rem works mechanically, and how to build a powerful team around her.

Understanding PL084 Rem’s Strengths

  • Faction: Blue (蒼 – Sou)
  • Type: 2.0 Cost Spearmen (槍兵) | Stats: 7 Might (武力) / 2 Intelligence (知力)
  • Traits: 技巧 (Skill), 先陣 (Vanguard), 鬼 (Oni)
  • Stratagem: Demon Girl’s Iron Flail (鬼少女の鉄槌 – Oni Shōjo no Tettsui) | Morale Cost: 5

When you trigger her stratagem, she gets a boost to her Might and Movement Speed. More importantly, a massive spiked iron flail begins orbiting her physical unit. Any enemy unit that touches this rotating flail takes continuous physical damage based on the difference between Rem’s Might and theirs.

💡 The Core Strategy: Rem is a mid-cost “brawler.” She isn’t a global army buffer; her job is to dive into the thick of combat, stick to high-priority targets, and melt them with her flail while her spearmen reach keeps cavalry away. Because her Intelligence is low (2), she is vulnerable to enemy damage spells (damage stratagems), so she needs support to shine.

Deck Archetypes for Rem

Since Rem belongs to the Blue (蒼) Faction, it makes sense to build a Mono-Blue or mostly Blue deck to ensure your starting Morale is maximized and your global team buffs affect her.

Here are two highly effective team compositions designed around her:

1. The Standard 4-Card Balanced “Gōrei” Deck

This is the most reliable, beginner-friendly way to run Rem. You pair her with a strong field commander who boosts the whole team’s Might, letting Rem’s flail deal catastrophic damage.

  • Total Cost: 8
  • Composition: 2.5 + 2.0 (Rem) + 2.0 + 1.5
  • Why it works: It’s straightforward. Push forward as a group, pop a global Might boost, and let Rem wade directly into the enemy lines. The speed boost from her iron flail allows her to chase down retreating units effortlessly.
CostCharacter / CardUnit TypeRole / Synergy
2.5 – 3.0蒼008 ER 織田信長
蒼049 SR 丹羽長秀
蒼PL062 SR 織田信長
Cavalry or ArcherThe Anchor / Win-Condition. His global command (Gōrei) raises the entire team’s Might. Activating his buff right before Rem goes in makes her flail hit significantly harder.
2.0蒼PL084 SR レムSpearmenThe Vanguard / Frontline Destroyer.
1.5 – 2.0蒼157 R 森蘭丸
蒼119 ER 帰蝶
Cavalry / SwordProvides high physical pressure and strong map control to clear out enemy ranged units while Rem locks down the center.
1.0 – 1.5蒼053 R 吉村虎太郎Spearmen / ArcherGives you utility, extra physical bulk, or a low-cost pocket stratagem to use if you don’t have enough Morale for Nobunaga.

2. The 5-Card Technical “Protect the Maid” Deck

If you want to lean fully into making Rem an unstoppable juggernaut, this composition focuses on keeping her alive, protecting her from magic, and letting her micro-manage the field.

  • Total Cost: 8
  • Composition: 2.0 (Rem) + 2.0 + 1.5 + 1.5 + 1.0
  • Why it works: This deck has more bodies on the floor, allowing you to body-block enemies so they cannot escape Rem’s orbiting flail. It requires more micro-management on the tabletop flat panel but offers high flexibility.
CostCharacterUnit TypeRole / Synergy
2.0蒼PL084 SR レムSpearmenThe Core Carry.
2.0CavalryEssential for flanking and forcing enemies to turn into Rem’s flail radius.
1.5a high-Int Blue unitRanged / SupportProvides ranged support and can deal with enemy high-defense units.
1.5Spearmen / ArcherLook for a unit with an Intelligence-boosting stratagem or a anti-mage debuff to protect Rem from being melted by enemy spellcasters.
1.0Any 1.0 Ambush (伏兵) unitAnyUsed to slow down the early game pace and scout out enemy hidden units.

Some thoughts for Playing Rem

  1. Don’t trigger her too early: Her Intelligence is 2, meaning the duration of her Demon Girl’s Iron Flail is relatively short if not boosted. Wait until the frontline clash physically begins before spending the 5 Morale.
  2. The “Flail Dance”: Because the flail physically orbits her unit on screen, don’t just march her straight forward. Once she is touching an enemy, subtly jiggle or rotate her card slightly to ensure the flail’s collision box maximizes contacts with the enemy sprite.
  3. Watch out for Fire/Lightning: If you see an enemy deck featuring high-Intelligence spellcasters (like Zhou Yu or Zhuge Liang from the Three Kingdoms era), be incredibly careful. They can instantly wipe Rem out with a damage spell right after you spend your Morale on her. Bait out their spells with your other units first!
Posted on May 10, 2026May 10, 2026 by admin

BMP – Rearrange Colormap

Posted on February 16, 2026March 1, 2026 by admin

In my limited experience working with BMP, GIMP UI is more intuitive and easy to use in comparison to Photoshop in this particular task, especially when it comes to rearranging colors in the color palette. Also, the image quality after color reduction and shrinking is better effortlessly in my opinion.

Using GIMP

  1. Open Image: Open your image in GIMP.
  2. Indexed Mode: Go to Image > Mode > Indexed.
  3. Set Colors: Choose “Generate optimum palette” and set the Maximum number of colors to 4.
  4. Reorder Palette: Go to Colors > Map > Rearrange Colormap. Drag and drop the colors into your desired order (Index 0-3).
  5. Force 8-bit Colormap: Open the Colormap dialog (Windows > Dockable Dialogs > Colormap) and click the ‘+’ button to add colors, ensuring the total is greater than 16, which forces an 8-bit (256 color) palette.
    ※This is because the index image is reduced to 4 colors, GIMP automatically create a 4-bit (16 colors) map to save space. This will mess up how BMP is displayed in yyCHR.
  6. Export: Go to File > Export As, select BMP. 
Add 13 more colors in the colormap to force the 8-bit Colormap.
You can see the difference in Bit Depth in Properties/Details. On the left is the correct one with 8 bit depth (>16 colors).
8-bit Colormap
4-bit Colormap

Reset Allow USB Debugging? Notification

Posted on February 16, 2026February 18, 2026 by admin
  1. Go to C:\Users\<YourProfile>\.android and delete both adbkey files, if there are none, skip this step.
  2. Download adb here and extract it somewhere.
  3. Open command prompt and navigate to the folder you’ve just extracted, for example 
    cd C:\path\to\platform-tools
    ※right click on the unzipped folder and select Open in Terminal also work.
  4. With the headset connected and logged in an administrator account, run .\adb kill-server and then .\adb devices.
  5. The notification should appear.

The “Toggle” Fix (Most Common Solution)

The Developer Mode status can sometimes desync between the headset and the mobile app.

  1. Open the Meta Horizon (Quest) App on your phone.
  2. Navigate to Menu > Devices > Headset Settings > Developer Mode.
  3. Toggle the switch OFF, wait about 10 seconds, and then toggle it back ON.
  4. Restart your headset (hold the power button and select Restart).
  5. Plug it back into your PC; the prompt should now appear.

8-bit Notes

Posted on February 8, 2026February 16, 2026 by admin

8 Eyes
Look at that bird animation.

A列車で行こう
There is a whole Locomotive in the CHR for the title screen.

Akira

Posts navigation

Older posts

Recent Posts

  • 8bitworkshop Notes – Include binary files
  • 8bitworkshop Notes – Display an external Nametable V1
  • 8bitworkshop Notes – Player Running and Jumping
  • Native Web Speech API
  • AI: You are _____ correct.

Archives

Proudly powered by WordPress | Theme: MiniZen by Martin Stehle.