Spaces:
Sleeping
Ability Bitpacking Analysis & Improvement Recommendations
Executive Summary
This document analyzes the current ability bitpacking implementation across the codebase and provides recommendations for improving readability. The system uses a complex multi-layer bitpacking approach that is difficult to maintain and understand.
Current Architecture Overview
Data Flow
Pseudocode (consolidated_abilities.json)
β
Compiler (ability.py - BytecodeEmitter)
β
Packing (generated_packer.py - pack_* functions)
β
Bytecode (cards_compiled.json - 5-word Γ 32-bit chunks)
β
Decoder (bytecode_readable.py - unpack_* functions)
β
Human-readable output
Key Files Involved
| File | Purpose |
|---|---|
engine/models/generated_packer.py |
Low-level pack/unpack functions with raw bit manipulation |
engine/models/ability.py |
High-level packing logic (BytecodeEmitter._pack_filter_attr) |
engine/models/bytecode_readable.py |
Decoding and formatting for human readability |
tools/sync_metadata.py |
Generates generated_packer.py from metadata |
Current Bit Layouts
64-bit Attribute Word (Filter/Condition)
| Bit Range | Field Name | Bits | Mask | Description |
|---|---|---|---|---|
| 0-1 | target_player |
2 | 0x3 | 0=self, 1=player, 2=opponent, 3=both |
| 2-3 | card_type |
2 | 0x3 | Card type filter |
| 4 | group_enabled |
1 | 0x1 | Enable group filtering |
| 5-11 | group_id |
7 | 0x7F | Group ID |
| 12 | is_tapped |
1 | 0x1 | Filter tapped units |
| 13 | has_blade_heart |
1 | 0x1 | Has blade heart |
| 14 | not_has_blade_heart |
1 | 0x1 | Does not have blade heart |
| 15 | unique_names |
1 | 0x1 | Unique names only |
| 16 | unit_enabled |
1 | 0x1 | Enable unit ID filter |
| 17-23 | unit_id |
7 | 0x7F | Unit ID to filter |
| 24 | value_enabled |
1 | 0x1 | Enable value threshold |
| 25-29 | value_threshold |
5 | 0x1F | Value comparison threshold |
| 30 | is_le |
1 | 0x1 | Less-than-or-equal comparison |
| 31 | is_cost_type |
1 | 0x1 | Cost-type filter |
| 32-38 | color_mask |
7 | 0x7F | Heart color mask |
| 39-45 | char_id_1 |
7 | 0x7F | Character ID 1 |
| 46-52 | char_id_2 |
7 | 0x7F | Character ID 2 |
| 53-55 | zone_mask |
3 | 0x7 | Zone mask |
| 56-58 | special_id |
3 | 0x7 | Special ID |
| 59 | is_setsuna |
1 | 0x1 | Setsuna filter |
| 60 | compare_accumulated |
1 | 0x1 | Compare accumulated value |
| 61 | is_optional |
1 | 0x1 | Optional filter |
| 62 | keyword_energy |
1 | 0x1 | Energy keyword |
| 63 | keyword_member |
1 | 0x1 | Member keyword |
32-bit Slot Word
| Bit Range | Field Name | Bits | Mask | Description |
|---|---|---|---|---|
| 0-7 | target_slot |
8 | 0xFF | Target slot |
| 8-15 | remainder_zone |
8 | 0xFF | Remainder zone |
| 16-19 | source_zone |
4 | 0xF | Source zone |
| 20-23 | dest_zone |
4 | 0xF | Destination zone |
| 24 | is_opponent |
1 | 0x1 | Opponent flag |
| 25 | is_reveal_until_live |
1 | 0x1 | Reveal until live |
| 26 | is_empty_slot |
1 | 0x1 | Empty slot only |
| 27 | is_wait |
1 | 0x1 | Wait flag |
| 28 | is_dynamic |
1 | 0x1 | Dynamic target |
| 29-31 | area_idx |
3 | 0x7 | Area index (left/center/right) |
Identified Issues
Issue 1: Magic Numbers Everywhere (Critical)
Location: generated_packer.py, ability.py
Problem: Bit positions and masks are hardcoded as magic numbers throughout the codebase.
Example:
# Current code - impossible to understand at a glance
res |= (target_player & 0x3) << 0
res |= (card_type & 0x3) << 2
res |= (group_enabled & 0x1) << 4
res |= (group_id & 0x7F) << 5
Impact:
- Developers cannot understand the bit layout without extensive tracing
- Easy to introduce bugs when modifying bit positions
- No way to visualize the overall structure
Issue 2: Duplication Between Pack/Unpack (High)
Location: generated_packer.py
Problem: Pack and unpack functions are separate but mirror each other exactly.
Example:
# Pack function
res |= (target_player & 0x3) << 0
# Unpack function - exact inverse
"target_player": (val >> 0) & 0x3,
Impact:
- Twice as much code to maintain
- Easy for pack/unpack to get out of sync
- No single source of truth for the bit layout
Issue 3: Scattered Bit Manipulation Logic (High)
Location: ability.py lines 1500-1700+, bytecode_readable.py
Problem: Bit manipulation is scattered across multiple files with no central definition.
Example - In ability.py:
# Line 1507-1512
if "LEFT" in a_str:
slot |= 1 << 29
elif "CENTER" in a_str:
slot |= 2 << 29
elif "RIGHT" in a_str:
slot |= 3 << 29
Impact:
- No single place to see the complete bit layout
- Hard to track all the different bit usages
- Risk of conflicts when adding new fields
Issue 4: No Visual Documentation (Medium)
Problem: There is no visual representation of the 64-bit and 32-bit layouts.
Impact:
- Hard to understand the overall structure
- Difficult to onboard new developers
- No quick reference for debugging
Issue 5: Inconsistent Naming (Medium)
Problem: Some fields use different names in different contexts.
Example:
target_playervstarget(in some Effect classes)is_levscompare_accumulated- unclear what these actually do
Issue 6: Generated Code Not Human-Editable (Medium)
Location: generated_packer.py
Problem: This file is auto-generated by sync_metadata.py, so any manual improvements will be overwritten.
Impact:
- Improvements must be made to the generator, not the generated file
- Need to coordinate changes across two levels
Recommended Improvements
Priority 1: Create Named Constants
Create a central constants file with well-named bit position and mask constants:
# engine/models/bitfield_constants.py
class AttrBits:
"""64-bit Attribute word bit positions and masks."""
# Target Player (bits 0-1)
TARGET_PLAYER_SHIFT = 0
TARGET_PLAYER_MASK = 0x3
# Card Type (bits 2-3)
CARD_TYPE_SHIFT = 2
CARD_TYPE_MASK = 0x3
# Group (bits 4-11)
GROUP_ENABLED_SHIFT = 4
GROUP_ENABLED_MASK = 0x1
GROUP_ID_SHIFT = 5
GROUP_ID_MASK = 0x7F
# ... etc
Priority 2: Add Visual Layout Diagrams
Add Mermaid diagrams to the documentation:
flowchart TD
A[64-bit Attribute Word] --> B[Bits 0-31]
A --> C[Bits 32-63]
B --> B1[target_player 0-1]
B --> B2[card_type 2-3]
B --> B3[group 4-11]
B --> B4[tapped/blade 12-14]
B --> B5[unit 16-23]
B --> B6[value 24-29]
B --> B7[is_le/is_cost 30-31]
C --> C1[color_mask 32-38]
C --> C2[char_id 39-52]
C --> C3[zone 53-55]
C --> C4[special 56-58]
C --> C5[flags 59-63]
Priority 3: Refactor Generator to Use Constants
Modify tools/sync_metadata.py to generate code using the constants:
# Instead of generating:
res |= (target_player & 0x3) << 0
# Generate:
res |= (target_player & AttrBits.TARGET_PLAYER_MASK) << AttrBits.TARGET_PLAYER_SHIFT
Priority 4: Create Specification-Driven Code Generation
Create a single specification that drives both pack and unpack generation:
# Define layout once
ATTR_LAYOUT = [
("target_player", 2, 0),
("card_type", 2, 2),
("group_enabled", 1, 4),
("group_id", 7, 5),
# ... etc
]
# Generate both pack and unpack from this
def generate_packer(layout):
# Generate pack function
pass
def generate_unpack(layout):
# Generate unpack function
pass
Priority 5: Add Self-Documenting Helper Functions
Instead of raw bit manipulation, use named helpers:
def pack_field(value: int, shift: int, bits: int) -> int:
"""Pack a field value into a bit range."""
mask = (1 << bits) - 1
return (value & mask) << shift
def unpack_field(value: int, shift: int, bits: int) -> int:
"""Unpack a field value from a bit range."""
mask = (1 << bits) - 1
return (value >> shift) & mask
# Usage becomes:
res |= pack_field(target_player, TARGET_PLAYER_SHIFT, 2)
# Instead of:
res |= (target_player & 0x3) << 0
Priority 6: Create Debug Visualization Tool
Add a tool to visualize any packed value:
def visualize_packed_attr(value: int) -> str:
"""Return a visual representation of a packed attribute."""
lines = ["64-bit Attribute Layout:"]
lines.append("-" * 40)
for field, _, _ in ATTR_LAYOUT:
field_val = unpack_field(value, FIELD_SHIFTS[field], FIELD_BITS[field])
lines.append(f" {field:20} = {field_val:3} (bits {shift}-{shift+bits-1})")
return "\n".join(lines)
Implementation Roadmap
Phase 1: Constants Definition (Low Risk)
- Create
engine/models/bitfield_constants.pywith all bit positions and masks - Update
generated_packer.pyto use constants (will be overwritten on next sync) - Update
ability.pyto use constants where possible
Phase 2: Generator Enhancement (Medium Risk)
- Modify
tools/sync_metadata.pyto use constants in generated code - Add visual diagram output to the generator
- Test that generated code compiles correctly
Phase 3: Documentation (No Risk)
- Add comprehensive docstrings to constants file
- Create visual layout diagrams in SKILL.md
- Add examples of packed values
Phase 4: Optional Advanced Improvements
- Implement specification-driven code generation
- Add debug visualization tools
- Consider using Python's
dataclasseswith__post_init__for validation
Risk Assessment
| Change | Risk Level | Reason |
|---|---|---|
| Adding constants file | Low | Pure addition, no breaking changes |
| Updating ability.py | Medium | Existing logic may have edge cases |
| Modifying sync_metadata.py | Medium | Changes affect generated code |
| Refactoring packer generation | High | Could break existing bytecode compatibility |
Recommendation: Start with Phase 1 (constants) and Phase 3 (documentation). Avoid Phase 2 and 4 until the system is stable.
Files to Modify
| File | Changes |
|---|---|
plans/ability_bitpacking_analysis.md |
This document |
engine/models/bitfield_constants.py |
NEW - Central constants |
engine/models/ability.py |
Use constants where applicable |
tools/sync_metadata.py |
Generate using constants |
.agent/skills/ability_compilation_bytecode/SKILL.md |
Add visual diagrams |
Conclusion
The current bitpacking implementation works but lacks readability. The main improvements should focus on:
- Named constants - Replace magic numbers with named bit positions and masks
- Visual documentation - Add diagrams showing the complete layout
- Generator improvements - Make the code generator produce more readable output
- Debug tools - Help developers visualize packed values
The highest-impact, lowest-risk approach is to create a central constants file and add comprehensive documentation. More ambitious changes (like refactoring the code generator) should be carefully tested.