# Mars MR97310 Still-Image Extraction and Translation Reference
> By Ethan J. Hulbert - https://www.ethanjhulbert.org - ethanjhulbert@gmail.com
> https://www.ethanjhulbert.org/photo/marscam-modern-driver-downloader/

**Scope:** hardware identification, USB access, photo-directory recovery, safe image extraction, raw format preservation, decompression, Bayer interpretation, rendering experiments, historical MARSCAM output comparisons, failure history, and reconstruction notes.

**Verified physical device:** `USB\VID_093A&PID_010E`  
**Known related family ID:** `VID_093A&PID_010F` (not physically validated in this work)  
**Reference implementation checkpoint:** MarsCam Modern `0.7.4` - publicly released Windows GUI (0.6.3 remains the historical CLI checkpoint)  
**Primary test host:** Windows 10 x64  
**Document date:** 2026-10-23

This document preserves the hardware/protocol/image-format research behind the **now-published** MarsCam Modern Windows application. Interface and distribution decisions are maintained separately in `MarsCam-Modern-Software-Product-Design-2026-10-23-Published.md`. Historical experiments, failures, and open questions remain in place for reproducibility; later observations are identified as such.

### Evidence labels

Statements in this document fall into four practical categories:

- **Verified on the test camera:** observed repeatedly on the physical `093A:010E` unit.
- **Historical reference:** derived from surviving manuals, libgphoto2 Mars code/protocol notes, old driver packages, or original MARSCAM-exported BMPs.
- **Inference:** a technically plausible interpretation supported by observations but not proven.
- **Unknown:** deliberately left unresolved rather than filled in by assumption.

Where the physical camera disagrees with historical assumptions, the physical observation is called out explicitly.


## The hardware

### 1.1 What camera is this?

The physical test camera is one of a large family of tiny early-2000s cameras built around the **Mars Semiconductor MR97310/MR97310A** architecture.

The retail name is unreliable. These cameras were rebadged heavily. Historical/manual material encountered during this project lists names including:

- Vivitar 3-in-1 Mini Digital Camera
- Crystal Digital
- Dual Smart
- Genesis Logic
- Innovage Outdoor Sports Mini Digital Camera
- L'espion Mini Spy Digital Camera
- Mini Digital Camera
- Philips Keychain Digital Camera
- Dual Mode Digital Camera
- MR97310 VGA Dual Mode Camera

Hardware identification should therefore rely primarily by **USB VID/PID and actual descriptors**, never by retail branding.

The primary verified device is:

```text
VID:PID 093a:010e
```

The known related VGA-family ID is:

```text
VID:PID 093a:010f
```

Current live downloading is intentionally restricted to the physically verified `010e` target.

---

### 1.2 Historical/manual specifications

Manual material recovered during the project describes the camera approximately as follows:

- storage: "16mb SDRAM" in the manual's wording;
- image sensor: 1/4-inch CMOS;
- aperture: f/2.8;
- exposure: automatic;
- white balance: automatic;
- shutter-speed specification: approximately 1/60 to 1/1500 second;
- power: one AAA battery;
- automatic power off: around 30 seconds idle;
- two-digit LCD;
- high resolution: `352 × 288`;
- low resolution: `176 × 144`;
- non-compressed approximate capacity:
  - HR: about 20 photographs,
  - LR: about 81 photographs;
- compressed approximate capacity:
  - HR: about 60 photographs,
  - LR: about 243 photographs;
- original OS support claims: Windows 98 / 98SE / ME / 2000 / XP;
- original software ecosystem referred to as **MARSCAM** / photo-processing software.

The exact physical unit is tiny, powered by one AAA battery, and has no image-preview LCD.

---

### 1.3 Physical controls and LCD modes

Observed/manual controls:

- **SNAP**: top button;
- **MODE / power**: front button.

Historical LCD labels include:

| LCD | Meaning |
|---|---|
| normal numeric count | normal shooting / remaining capacity |
| `OF` | power off |
| `St` | self timer |
| `Ct` | continuous shooting |
| `CP` | compressed mode |
| `nP` / `NP` | non-compressed mode |
| `AI` | AVI/video mode |
| `CL` | delete last photo |
| `CA` | delete all photos |
| `Hr` | high resolution |
| `Lr` | low resolution |
| `F5` | 50 Hz anti-flicker setting |
| `F6` | 60 Hz anti-flicker setting |

On the physical test camera, the observed default MODE-button sequence was approximately:

```text
normal
→ OF
→ Ct
→ AI
→ St
→ CL
→ CA
→ Lr
→ CP
→ F6
→ cycle
```

Mode toggles may change what appears on later passes. The exact sequence is relevant mainly when constructing controlled test sets.

**Critical operational caution:** do not press SNAP while `CL` or `CA` is selected unless intentionally deleting images.

---

### 1.4 Volatile-memory / power weirdness

The historical manual warns not to remove the battery before photographs are downloaded because the photographs may be lost.

During this project, the physical camera also appeared capable of losing photographs after spending a long time connected in PC/USB mode even without intentionally removing the battery. That observation is real, but its cause is **not established**. Possible explanations include:

- aging hardware;
- battery/USB power transition or brownout;
- internal reset;
- volatile SDRAM state loss;
- firmware state corruption after prolonged PC mode.

Do **not** convert that into a protocol fact.

For any implementation, avoid pointless background polling or constant Mars-protocol traffic. Windows can detect USB attachment without repeatedly querying the camera.

This caution was reinforced during the October 2026 field-test session. The camera was treated as volatile enough that the download was performed immediately after shooting; prolonged connection and physical disturbance were avoided because prior sessions had shown that the stored photographs could disappear after a reset-like event. The exact cause remains unknown, but the practical rule is clear: **recover first, experiment second**.

---

## Connector, cable, and retail-package evidence

The camera uses an uncommon early miniature USB connector rather than the later standardized 5-pin Mini-B or Micro-B plug. Photographs of the working cable show four contacts in the camera-end plug. The useful modern search terms are:

```text
USB Type-A to 4-pin Mini-USB
4-pin Mini USB
Mini USB 4P
4-pin Mini-USB-B / camera mini USB
```

The connector should be described cautiously in public documentation because several mechanically similar nonstandard camera connectors were sold under overlapping names. A replacement cable should be matched by **four contacts and shell shape**, not by the words "mini USB" alone. It is not 4-pin FireWire.

Original Vivitar retail-package photographs add several useful identification details:

- product name: **Vivitar Mini Digital Camera with Accessory Kit**;
- marketed as a **3-in-1** still camera, video camera, and web camera;
- package claim: **16 MB memory**;
- package claim: **stores up to 243 photos**;
- included software, USB cable, one AAA battery, carrying case, and instruction manual;
- listed Windows support: 98 / Me / 2000 / XP;
- suggested retail price visible on packaging: **US$39.99**;
- packaging copyright: **©2007 Innovage, Inc.**;
- package identifies the unit as a **Vivitar licensed product** made for Innovage under trademark license.

These details are useful for identification and rebadge history. They do not establish protocol behavior.


## Historical Windows driver research and the modern-driver decision

### 2.1 Original driver material

Historical driver archives discovered during research included packages such as:

```text
MR97310c_Mini_Digital_Camera_Drivers_Version_2.03_All_Functions_work.zip
setup_and_more.zip
innovage_MDC.zip
```

The old driver package contained approximately:

```text
MR97310c.sys
version 1.2.9.0
circa December 2002
```

It was an old Windows driver/WDM-era approach intended for the operating systems of that period.

Some web searches for this driver led to misleading "driver updater" executables, notably Outbyte-style installer/updater programs rather than the actual archived camera driver. Those were rejected.

---

### 2.2 Decision: do not revive the old kernel driver

The project deliberately **does not install or resurrect the old proprietary `.sys` driver** on a current machine.

Reasons:

- it is ancient kernel-mode code;
- modern Windows trust/signing requirements make it awkward;
- it increases risk and complexity;
- the camera protocol itself can be implemented in user mode;
- Microsoft's `winusb.sys` already provides a modern USB transport layer.

The architecture is therefore:

```text
MR97310 camera
    ↓ USB
Microsoft WinUSB (winusb.sys)
    ↓
MarsCam Modern C++ user-mode code
    ↓
protocol / PAT / image decoder / PNG exporter
```

This architecture avoids the need to revive the obsolete kernel driver.

---

### 2.3 Development-time WinUSB binding and later driver-loss recovery

The verified physical camera was manually bound through Windows Device Manager:

```text
Device Manager
→ Update driver
→ Browse my computer
→ Let me pick
→ Universal Serial Bus devices
→ WinUsb Device
```

After that, MarsCam could open it through WinUSB.

Current source packages do **not** ship a custom kernel driver.

A later Windows session in October 2026 provided a useful troubleshooting case. The camera still:

- made the normal USB connection sound;
- entered `PC` mode on its LCD;
- enumerated with the exact expected hardware ID:

  ```text
  USB\VID_093A&PID_010E&REV_0100
  USB\VID_093A&PID_010E
  ```

but MarsCam reported:

```text
No known MR97310 USB device interface found.
Expected VID:PID 093a:010e (CIF) or 093a:010f (VGA).
```

PowerShell showed the device as:

```text
Status       : Error
FriendlyName : Dual-Mode Digital Camera
InstanceId   : USB\VID_093A&PID_010E\...
```

and `DEVPKEY_Device_Service` was empty. In Device Manager it appeared under **Other devices** rather than as a usable WinUSB interface.

Rebinding the same `093A:010E` device to **WinUsb Device** restored MarsCam access immediately.

This establishes an important troubleshooting distinction:

```text
camera physically connected / camera says PC
≠
usable WinUSB interface is currently bound
```

A Windows update, driver cleanup, re-enumeration, or other system change can apparently leave the hardware present while the WinUSB service association is missing. A future GUI should detect this state separately from "camera absent."

How a public release should make the one-time/recovery WinUSB setup friendlier remains **TBD**. A suitable user-facing state is:

```text
Camera detected, but Windows is not using the WinUSB driver.
[ Setup / Repair WinUSB ]
```

Any automated binding must be researched carefully and restricted to exact supported device IDs. No custom kernel driver should be introduced.

---

## Build and project architecture

### 3.1 Toolchain

Development build:

```powershell
cmake -S . -B build -A x64
cmake --build build --config Release
ctest --test-dir build -C Release
```

Typical live commands in current CLI builds:

```powershell
.\build\Release\marscam.exe
.\build\Release\marscam.exe list
.\build\Release\marscam.exe download 1
.\build\Release\marscam.exe download-all
```

The offline image lab historically used:

```powershell
.\build\Release\marsrawlab.exe
```

---

### 3.2 Intended software layers

The project architecture is deliberately separable:

```text
front end / test harness
    ↓
camera/application engine
    ├── device discovery
    ├── WinUSB transport
    ├── Mars protocol
    ├── PAT parser
    ├── raw preservation
    ├── compressed decoder
    ├── Bayer demosaic
    ├── image-processing presets
    └── PNG/export
```


---

## USB identity and descriptors

The verified physical device reports approximately:

```text
VID:PID                  093a:010e
USB BCD                  0x0110
device BCD               0x0100
device class             255
device subclass          255
device protocol          255
interface                0
interface class          vendor-specific
```

Observed endpoints:

```text
0x81  isochronous IN
0x82  bulk IN        max packet 64
0x83  bulk IN        max packet 16
0x04  bulk OUT       max packet 16
0x85  interrupt IN
0x86  isochronous IN
0x07  bulk OUT
```

**Important correction discovered early:** endpoint `0x83` on the verified camera is **bulk IN**, not interrupt IN.

For still-photo/config work, the important pipes are:

```text
0x04  bulk OUT  → commands and status requests
0x83  bulk IN   → 16-byte command/status responses
0x82  bulk IN   → config blocks and image data
```

Current active code validates these endpoint types before protocol traffic.

Endpoint `0x07` is not used by MarsCam Modern.

---

## Mars command protocol - practical reconstruction

Protocol reconstruction was informed by historical libgphoto2 Mars support and then validated against the physical camera.

The key upstream reference files are:

```text
libgphoto2/camlibs/mars/mars.c
libgphoto2/camlibs/mars/library.c
libgphoto2/camlibs/mars/protocol.txt
```

Useful upstream project URL:

```text
https://github.com/gphoto/libgphoto2
```

A historically used raw-source URL during this project:

```text
https://raw.githubusercontent.com/gphoto/libgphoto2/master/camlibs/mars/mars.c
```

The implementation in MarsCam Modern is independently written, with attribution retained in `THIRD_PARTY.md`.

---

### 5.1 Core command bytes

```text
0x21       request/read status
0x19       Mars command token
0x19 0x51  start
0x19 0xB5  INIT operation
0x19 0x0F  GET_DATA operation
0x19 0x54  reset - NEVER IMPLEMENTED/SENT BY THIS PROJECT
single 0x19 historical jam-clear/final data-intent token depending context
```

The same single byte `0x19` is context-sensitive:

- in one narrowly defined `0x02` jam state it is used as the documented one-shot clear;
- at the end of a positioning sequence it is the final data-intent token.

Do not indiscriminately send it.

---

### 5.2 Basic status operation

To request status:

```text
OUT 0x04: 21
IN  0x83: 16 bytes
```

The first byte of the 16-byte response is generally the significant state indicator.

---

### 5.3 Command operation

For a command parameter `X`:

```text
OUT 0x04: 19 X
OUT 0x04: 21
IN  0x83: 16-byte status
```

---

### 5.4 Relevant observed status bytes

Observed first-byte states during development include:

```text
0x02  jammed / special historical recovery state
0x08  intermediate/busy-ish state
0x09  intermediate state seen in some positioning
0x0A  required memory-position acknowledgement
0xA2  observed final/data-ready style response on physical 010e
0xA8  observed in some direct responses / jam-clear patterns
0xB5  observed as final response in INIT/config operations
0x84  observed in at least one photo final response
```

The crucial lesson is:

> **Do not hard-code one exact final response such as sixteen `A8` bytes.**

Early versions did that and stopped even when the camera had successfully positioned itself. Successful `0x0A` positioning is the meaningful gate; final direct responses vary by phase/firmware/state and should be logged rather than overinterpreted.

---

### 5.5 Timing lessons

Historical `MARS_SLEEP` behavior corresponds to approximately:

```text
10,000 microseconds = 10 ms
```

Important timing behavior that stabilized the physical camera:

- short ~10 ms pause after final command/status transition before data read;
- positioning polls paced by about 2 ms rather than hammering the endpoint;
- INIT positioning:
  - approximately 250 ms wall-clock bound,
  - absolute poll cap;
- GET_DATA/photo positioning:
  - approximately 2000 ms wall-clock bound,
  - absolute 256-poll cap;
- finite WinUSB pipe timeouts:
  - command/status around 1500 ms,
  - image data around 5000 ms in current photo-download code.

This camera is old and stateful. Timing was not cosmetic.

---

## The narrowly scoped `0x02` jam recovery

A major early obstacle was status `0x02`.

Initial conservative builds simply stopped when `0x02` appeared. Historical libgphoto2 research showed a narrow non-destructive recovery exchange.

The final safety-limited behavior is:

```text
if status[0] == 0x02:
    OUT 0x04: 19
    IN  0x83: direct 16-byte response
    sleep ~10 ms
    request normal status again
    if still 0x02:
        STOP
```

Rules:

- try this **once per relevant phase**;
- never loop it repeatedly;
- this is **not** the reset command;
- never substitute `19 54`.

On startup, if the camera was initially in `0x02`, later versions followed upstream behavior by clearing once and skipping the separate INIT dry-run before proceeding to config GET_DATA.

This one-shot handling was important to reliable reads.

---

## Configuration/PAT acquisition sequence

A simplified config read is:

```text
1. request initial status
2. if initial 0x02:
       one-shot single-19 clear
       skip INIT dry-run
   else:
       INIT sequence
3. GET_DATA sequence for address 0
4. read exactly 8192 bytes from endpoint 0x82
```

#### INIT sequence

Conceptually:

```text
19 51
19 B5
19 00       address byte 1
19 00       address byte 2
poll status until first byte == 0x0A
19 00       address byte 3
19 00       address byte 4
19 00       address byte 5
19 00       address byte 6
19          final data-intent token
read direct 16 bytes from 0x83
sleep ~10 ms
```

#### Config GET_DATA sequence

```text
status
(one-shot clear if specifically required)

19 51
19 0F
19 00       address byte 1
19 00       address byte 2
poll status until first byte == 0x0A
19 00       address byte 3
19 00       address byte 4
19 00       address byte 5
19 00       address byte 6
19          final data-intent token
read direct 16 bytes from 0x83
sleep ~10 ms

read 0x2000 / 8192 bytes from endpoint 0x82
```

---

## The 8192-byte config block and PAT instability

The config block is:

```text
0x2000 bytes = 8192 bytes
```

A healthy config commonly begins with a marker resembling:

```text
ff 00 ff 00 ff 00 ff 01
```

Historical layouts place the PAT at one of two important offsets:

```text
0x0010  (16 bytes)
0x0090  (144 bytes)
```

This proved to be one of the main practical failure modes.

---

### 8.1 What went wrong in real hardware

The camera did not always return the same useful config page even when USB transport and protocol handshaking succeeded.

Observed responses included:

- valid PAT at `0x0010`;
- valid PAT at `0x0090`;
- image-looking/Bayer-like data instead of config;
- repeating bytes such as `EB 75 AF 5D BA 55 AE 7D ...`;
- nearly/all `FF`;
- only one valid PAT fragment when more photographs existed;
- false plausible bytes at one PAT offset while the real directory was at the other.

This was not merely "USB read failed." The camera could successfully transfer 8192 bytes that were the **wrong internal memory page or an unstable fragment**.

---

### 8.2 The false 796,175-byte photograph

At one stage random RAM/config bytes formed a superficially plausible PAT entry indicating a supposed `352×288` image of:

```text
796,175 bytes
```

That was nonsense.

It was an important lesson: "known resolution nibble + nonzero size" is nowhere near strong enough validation for a PAT record.

---

### 8.3 Strict still-size invariants

For verified still modes:

#### High resolution, non-compressed

```text
352 × 288 = 101,376 Bayer samples
+ 12-byte header
= 101,388 bytes exact
```

#### Low resolution, non-compressed

```text
176 × 144 = 25,344 Bayer samples
+ 12-byte header
= 25,356 bytes exact
```

Therefore:

```text
uncompressed_size = 12 + width * height
```

Validation rule:

```text
if non-compressed still:
    stored size must equal 12 + width*height

if compressed still:
    stored size must be >= 12
    and strictly less than 12 + width*height
```

This single invariant killed a large class of random-memory false positives.

---

## PAT format

Each photo allocation table entry is **8 bytes**.

Current parser interpretation:

```text
byte 0     code / flags / resolution
bytes 1-6 address/position bytes used by camera GET_DATA protocol
bytes 4-6 stored-size field, little endian
byte 7     currently not relied upon / semantics not fully established
```

Yes, bytes `4..6` participate both in the six bytes sent during photo positioning and in the stored-size calculation. This follows the historical Mars behavior.

Stored size:

```text
size = byte4
     | (byte5 << 8)
     | (byte6 << 16)
```

Current parser also sorts merged observations by an address key built from:

```text
byte1
| (byte2 << 8)
| (byte3 << 16)
```

---

### 9.1 Byte 0: mode/resolution/compression

Current verified interpretation:

```text
resolution_code = code & 0x0F
flags           = code & 0xF0
compressed      = bit 0x20
```

Equivalent compression test in source:

```cpp
((code >> 4) & 0x02) != 0
```

Known resolution codes from historical support/parser:

| low nibble | interpretation |
|---:|---|
| `0` | 176×144 |
| `1` | audio on some variants |
| `2` | 352×288 |
| `6` | 320×240 historical/variant support |
| `8` | 640×480 historical/variant support |

Only `0` and `2` have been physically validated on this particular camera.

**Important correction:** `0x80` is **not** the compression bit. It is another flag. Compression is bit `0x20`.

---

## Controlled four-mode capture - the gold-standard mode mapping

A fresh controlled test was performed after clearing/resetting the camera's stored images.

The intended sequence captured exactly one photograph in each still mode:

1. HR + nP
2. HR + CP
3. LR + CP
4. LR + nP

The resulting list was:

```text
Stored items: 4

1
PAT: 02 00 04 00 0c 8c 01 9f
code: 0x02
flags: 0x00
resolution: 352x288
stored size: 101388
compressed: no

2
PAT: a2 82 35 00 25 53 00 d1
code: 0xA2
flags: 0xA0
resolution: 352x288
stored size: 21285
compressed: yes

3
PAT: 20 e7 3f 00 4d 17 00 aa
code: 0x20
flags: 0x20
resolution: 176x144
stored size: 5965
compressed: yes

4
PAT: 80 d1 42 00 0c 63 00 02
code: 0x80
flags: 0x80
resolution: 176x144
stored size: 25356
compressed: no
```

Therefore the physically confirmed mapping is:

| Camera mode | PAT code family | Dimensions | Compression |
|---|---:|---:|---|
| HR + nP | `0x02` | 352×288 | no |
| HR + CP | `0xA2` | 352×288 | yes |
| LR + CP | `0x20` | 176×144 | yes |
| LR + nP | `0x80` / sometimes `0x00`-family depending other flags | 176×144 | no |

The low nibble is the reliable resolution indicator:

```text
...2 → 352×288
...0 → 176×144
```

And bit `0x20` is the reliable compression indicator.

---

## Why a five-observation PAT collector exists

Early behavior stopped at the first valid PAT fragment.

That failed because an aging camera could yield:

```text
observation 1 → one plausible photo
observation 2 → wrong page
observation 3 → full real list
...
```

The robust approach became:

1. perform a bounded set of **five config observations**;
2. before each observation:
   - explicitly close the current WinUSB handle,
   - wait briefly,
   - reopen it;
3. save every successfully received 8192-byte config block;
4. parse both historical PAT offsets;
5. validate entries strictly;
6. collect every exact valid 8-byte PAT record across observations;
7. deduplicate by the exact 8 bytes;
8. sort by address;
9. renumber for this host session;
10. reopen the interface once more before actual photo downloads.

This has proven much more robust than trusting one config read.

---

### 11.1 PAT candidate selection when both offsets look plausible

A further problem: random data at `0x0010` could look like one valid entry while a complete four-entry PAT existed at `0x0090`.

Candidate preference evolved to:

1. more consecutive valid entries;
2. clean `0xFF` termination;
3. canonical marker-backed structure;
4. more conservative total size;
5. lower offset only as a final tie-break.

Never simply pick `0x0010` because it appears first.

---

### 11.2 Partial PAT handling

Aging camera state sometimes produces valid entries followed by unrelated RAM rather than a clean `FF` terminator.

Final philosophy:

> a strongly validated prefix is useful; do not walk beyond the first invalid record.

This permits conservative recovery while refusing garbage.

---

## WinUSB reopen bug: Win32 error 5

The first five-observation implementation encountered:

```text
CreateFile
Win32 error 5
ERROR_ACCESS_DENIED
```

Reason: a new WinUSB `CreateFileW` open was attempted before ownership/destruction of the previous handle had reliably completed.

The fix was explicit:

```text
dev.close()
short settle delay
dev = open(...)
short settle delay
```

Do not rely on a C++ assignment expression's object-lifetime/evaluation behavior to release the old handle "soon enough."

This fix became version 0.5.5.

---

## Photo download protocol

Once a PAT entry has been selected, photo GET_DATA is analogous to config GET_DATA, but the six positioning bytes come from PAT bytes `1..6`.

Simplified:

```text
status
optional one-shot 0x02 clear

19 51
19 0F

19 PAT[1]
19 PAT[2]
poll until status[0] == 0x0A

19 PAT[3]
19 PAT[4]
19 PAT[5]
19 PAT[6]

19
read direct 16-byte final response
sleep ~10 ms

read padded transfer from endpoint 0x82
```

The transfer is read in chunks no larger than:

```text
0x2000 = 8192 bytes
```

---

## Padded transfer size

Historical Mars behavior uses a larger transport allocation than the stored photo length.

Current formula:

```text
padded = ((raw_size + 0x1B0) / 0x2000 + 1) * 0x2000
```

Examples from the controlled four-mode batch:

```text
HR+nP
raw      101388
transfer 106496

HR+CP
raw       21285
transfer  24576

LR+CP
raw        5965
transfer   8192

LR+nP
raw       25356
transfer  32768
```

---

## The 128-byte transport lead-in

The full downloaded USB transfer includes a **128-byte lead-in** before the photo payload.

MarsCam preserves two artifacts:

#### Full transport capture

```text
*-transfer.bin
```

Contains the complete padded USB transfer including the 128-byte lead-in and any padding beyond the declared payload.

#### Camera payload

```text
*.emarsraw
```

Defined by this project as:

```text
transport[128 : 128 + PAT_stored_size]
```

That is the camera payload trimmed exactly to the PAT-declared stored size.

This distinction is important for archival work.

---

## `.emarsraw` - project-defined archival format/extension

`.emarsraw` is **not a standard photographic RAW format**.

The extension was deliberately chosen instead of generic `.raw` so users do not mistake it for a format that Lightroom/RawTherapee/etc. already understand.

Conceptually:

```text
.emarsraw
┌───────────────────────────────┐
│ 12-byte Mars image header     │
├───────────────────────────────┤
│ Bayer bytes, if nP            │
│ OR compressed bitstream, CP   │
└───────────────────────────────┘
```

For nP:

```text
file size = 12 + width*height
```

For CP:

```text
file size = 12 + variable compressed payload
```

---

## Binary inspection notes

A hex editor such as HxD is useful for distinguishing container data from image data.

For a standard PNG, the header begins with:

```text
89 50 4E 47 0D 0A 1A 0A
```

followed by chunks such as `IHDR`, `IDAT`, and `IEND`. PNG pixel data is compressed, so the bytes inside `IDAT` are not direct RGB pixels.

A 24-bit BMP is much more transparent: after the BMP/DIB header and any row padding, the pixel area is stored as direct BGR triples.

An uncompressed nP `.emarsraw` is simpler still:

```text
12-byte Mars header
one raw Bayer sample byte per photosite
```

HxD's "Decoded text" pane may appear to show cycles of punctuation, digits, and letters in an nP file. That is not embedded text. HxD is interpreting sensor bytes as ANSI characters. Values around `0x20–0x7E` happen to map to printable ASCII. The repeating BGGR photosite pattern, scene brightness, and row-pattern noise can therefore produce visually rhythmic pseudo-text. A CP payload looks much less regular because it is a variable-length compressed bitstream rather than one byte per photosite.

HxD's file-byte histogram is likewise not an image luminance histogram. For PNG it includes headers, compressed data, and checksums. Pixel statistics should be calculated after decoding the image.


## Image header observations

The first 12 bytes are a Mars-specific header.

Controlled samples:

#### HR + nP

```text
ff ff 00 ff 96 64 00 20 00 00 0c 60
```

#### HR + CP

```text
ff ff 00 ff 96 64 50 1d 7a 00 0c 60
```

#### LR + CP

```text
ff ff 00 ff 96 64 50 1e 85 00 0c 60
```

#### LR + nP

```text
ff ff 00 ff 96 64 00 1e 7a 00 0c 60
```

The first six bytes are notably stable in this sample set:

```text
ff ff 00 ff 96 64
```

Observed byte 6 differs clearly between compressed and non-compressed examples (`0x50` vs `0x00` in this batch).

Historical libgphoto2 behavior also uses **header byte 7** to derive an image gamma factor:

```text
gamma_factor = sqrt(header[7] / 100)
floor at 0.60
```

Not all header bytes have fully established meanings. Do not invent EXIF-like semantics for them.

---

## Metadata: what the camera does and does not appear to store

There is no evidence in the recovered still format of conventional EXIF metadata such as:

- original capture date/time;
- GPS;
- ISO;
- per-frame shutter speed;
- aperture tag;
- focal length;
- orientation;
- camera make/model EXIF tags;
- user comment.

The timestamps currently embedded in development filenames are **download times created by MarsCam Modern**, not shutter times.

What can be preserved reliably per photo is:

- PAT/session index;
- raw 8-byte PAT entry;
- resolution mode;
- dimensions;
- compression flag;
- stored size;
- the 12-byte image header;
- download/import time generated by the host software;
- raw payload;
- full USB transfer if diagnostics/archive mode is enabled.

A host-generated sidecar can record those explicitly if desired.

---

## Non-compressed image layout

For nP images:

```text
offset 0..11     12-byte header
offset 12..      one 8-bit Bayer sample per pixel
```

Exact sizes:

```text
352×288 → 101376 Bayer bytes + 12 = 101388 total
176×144 →  25344 Bayer bytes + 12 =  25356 total
```

This was crucial both for decoding and for PAT validation.

---

## The Bayer-pattern problem

### 20.1 Historical assumption: RGGB

The early decoder followed the upstream/historical Mars assumption and rendered raw data as **RGGB**.

It produced recognizable structure, proving that:

- dimensions were correct;
- data offset was correct;
- data was indeed Bayer-like image information.

But colors were badly wrong.

---

### 20.2 0.5.6 diagnostic matrix

Version 0.5.6 generated every combination of:

#### Bayer phases

```text
RGGB
BGGR
GRBG
GBRG
```

#### Render modes

```text
baseline
Mars/libgphoto2-inspired
gray-world
neutral
```

Total:

```text
4 × 4 = 16 PNGs per uncompressed image
```

This converted the Bayer question into an empirical comparison.

---

### 20.3 First strong LR+nP result

On the initial low-resolution/non-compressed test:

- **BGGR baseline**:
  - recognizable and close,
  - too dark,
  - too green/yellow;
- **BGGR gray-world**:
  - more corrected color direction,
  - still very dark;
- **BGGR Mars**:
  - strongly warm/yellow/green,
  - bright/overcooked;
- **BGGR neutral**:
  - obvious winner,
  - correct-looking real colors,
  - some overexposure,
  - horizontal stripe/grain artifacts.

Other Bayer phases:

- GBRG / GRBG: purple/gray garbage;
- RGGB: blue/underwater/dark or grayish depending processing.

That strongly suggested **BGGR**.

---

### 20.4 Controlled HR + LR confirmation

The later four-mode test provided both:

```text
HR+nP
LR+nP
```

and the same Bayer experiment was repeated.

Both resolutions showed the same pattern:

- BGGR was the only consistently correct color geometry;
- neutral processing was the most natural family;
- remaining defects were primarily:
  - tone mapping,
  - contrast,
  - row/banding noise,
  - not channel/Bayer arrangement.

Conclusion:

> **The verified physical camera should be treated as BGGR for both HR and LR still images.**

This is an empirical camera finding, even though historical upstream Mars code used RGGB.

That distinction should remain documented forever.

---

## Current bilinear Bayer demosaic

MarsCam Modern uses a simple bilinear demosaic.

For any chosen Bayer phase:

- at an R pixel:
  - R = center;
  - G = average N/S/E/W;
  - B = average four diagonals;
- at a B pixel:
  - B = center;
  - G = average N/S/E/W;
  - R = average four diagonals;
- at G on R-row:
  - G = center;
  - R = horizontal average;
  - B = vertical average;
- at G on B-row:
  - G = center;
  - R = vertical average;
  - B = horizontal average.

Edges are clamped to valid sample coordinates.

This is intentionally uncomplicated.

Historical libgphoto2 may use a more sophisticated demosaic path (including AHD in some processing paths); MarsCam Modern's current output should not be described as pixel-identical to the original proprietary MARSCAM software.

---

## Image-processing paths explored

Several rendering approaches were built.

### 22.1 Baseline

```text
Bayer → bilinear RGB
```

No:

- gamma,
- white balance,
- contrast,
- saturation,
- denoise,
- destriping.

Useful as an archaeological/reference output.

---

### 22.2 Historical Mars/libgphoto2-inspired rendering

An independent behavioral port was built from published libgphoto2 Mars behavior.

High-level stages:

1. derive gamma from image header byte 7:

   ```text
   gamma = sqrt(raw[7] / 100)
   minimum = 0.60
   ```

2. apply gamma lookup;
3. build per-channel histograms;
4. estimate bright points around a 0.5% high-tail population;
5. independently scale R/G/B toward bright range;
6. estimate dark points around a 0.5% low-tail population;
7. apply dark expansion;
8. apply a saturation/color-enhancement stage.

This rendering was historically grounded but did **not** consistently look most natural on the physical camera.

Important wording:

> MarsCam Modern does not claim this was the exact proprietary Windows MARSCAM application's rendering pipeline.

---

### 22.3 Gray-world processing

A straightforward gray-world white balance was tested:

```text
mean R, mean G, mean B
target = average of those means
scale each channel toward target
```

Scale factors are bounded.

It helped diagnose color balance but was often too dark when used without stronger tone processing.

---

### 22.4 Neutral processing

The neutral family became the practical favorite.

General stages:

1. robust per-channel percentile stretch;
2. gray-world channel balancing;
3. display gamma;
4. optional contrast shaping;
5. optional saturation shaping;
6. highlight rolloff;
7. optional conservative horizontal-row correction.

This family is **modern pragmatic processing**, not a historical Mars claim.

---

## Horizontal stripe/banding correction

Correct BGGR/neutral renders exposed noticeable horizontal row streaking/banding.

The implemented destriping experiment:

1. calculate each row's average luminance;
2. compare a row to neighboring rows within roughly ±2;
3. derive a small gain to move the row toward its local expected luminance;
4. clamp correction tightly, approximately:

   ```text
   gain 0.94 .. 1.06
   ```

5. blend correction according to `destripe_strength`;
6. apply equal gain to R/G/B to alter luminance more than hue.

This is intentionally conservative.

It reduces some streaking but should not be mistaken for a scientifically proven sensor calibration.

---

## 0.5.8 processing bake-off

After BGGR was established, 0.5.8 became an offline processing lab.

It generated **24 BGGR-focused variants per nP photograph** so the camera did not need to remain connected.

The experiment explored:

- baseline;
- Mars processing;
- gray-world;
- multiple neutral percentile spans;
- gamma variants;
- contrast variants;
- highlight rolloff;
- saturation;
- partial gray-world;
- row destriping strengths.

Visual evaluation:

#### HR+nP sample

Notable subjective results:

- `00`: too yellow/dark;
- `01`: gray/dark;
- `02`: yellow;
- `03`: neutral original - correct WB/colors but streaky/grainy;
- `04`: similar, less bright;
- `05`: less bright, pretty good, streaky;
- `06`: perhaps low contrast but very good, streaky;
- `07–09`: brighter/higher contrast;
- `10–11`: low contrast;
- `12`: bright;
- `13`: acceptable contrast, slightly dark;
- `14–17`: brightness/contrast/saturation variations;
- `18–20`: progressively less streaking;
- `21`: lower contrast, good highlight behavior, looked good;
- `22`: too low contrast;
- `23`: similar to 21, also good.

Leading family:

```text
20
21
23
```

with `05/06` also useful.

---

## 0.5.9 curated processing set

0.5.9 narrowed the giant sweep.

For nP it retained ten curated BGGR variants.

Visual evaluation:

#### HR

- `06`: nice, lower contrast;
- `08`: nearly identical to 06;
- `09`: nice higher-contrast option;
- difficult to select one universal winner.

#### LR

- `03` and `04` were preferred;
- one gave slightly higher/lower contrast behavior.

Conclusion:

> Do not prematurely declare one universal tone recipe until substantially different lighting has been tested.

That remains the current image-quality situation.

---

## Current 0.6.2/0.6.3 normal recipe set

The current consolidated builds keep four practical comparison recipes.

Internal names/parameters:

| Current file label | low pct | high pct | gamma | contrast | saturation | gray-world | highlight rolloff | destripe |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| `01-normal-proc21` | .0025 | .9975 | .95 | .90 | .98 | 1.00 | .45 | .50 |
| `02-normal-proc20` | .0050 | .9950 | .90 | .95 | 1.00 | 1.00 | .20 | .75 |
| `03-normal-proc23` | .0025 | .9975 | .95 | .90 | .98 | 1.00 | .80 | .50 |
| `04-normal-soft` | .0010 | .9990 | 1.00 | .86 | .96 | 1.00 | .65 | .95 |

These names are development names, **not final user-facing names**.

The broader October 2026 test set confirmed that all four correct-path normal renders are useful in at least some scenes. None is a universal winner. `proc21` is often a good middle/default candidate, `proc20` tends to be the brighter/punchier member of the family, `proc23` changes highlight handling, and `soft` gives the gentlest/lowest-contrast interpretation.

The practical conclusion is no longer "find the one true render." The camera benefits from a small range of sane render intents, with exact final parameters still open to refinement.

---

## Compressed image research

Compressed stills were the second major decoding challenge.

Controlled samples:

### HR + CP

```text
total .emarsraw bytes: 21285
header bytes:          12
compressed payload:    21273
expected Bayer plane:  101376
ratio payload/Bayer:   ~0.210
```

Header:

```text
ff ff 00 ff 96 64 50 1d 7a 00 0c 60
```

First 64 compressed payload bytes:

```text
0c 22 01 42 b5 0a 10 cc cd 80 9d 92 80 28 00 00
32 d0 00 00 2b 14 00 0a 00 30 29 41 95 16 11 4a
28 a0 cd b5 a0 a0 02 da cc 00 18 c6 5d 80 c0 29
85 c0 b6 ec 56 29 5a 50 0c 00 14 03 28 c6 a0 ca
```

Frequent HR payload byte values included:

```text
00  1646
01   410
b6   406
14   384
6d   384
80   373
05   356
28   356
0a   352
a0   328
18   325
0c   321
2d   318
60   318
30   313
50   313
```

### LR + CP

```text
total .emarsraw bytes: 5965
header bytes:          12
compressed payload:    5953
expected Bayer plane:  25344
ratio payload/Bayer:   ~0.235
```

Header:

```text
ff ff 00 ff 96 64 50 1e 85 00 0c 60
```

First 64 payload bytes:

```text
0e 25 14 00 01 0c 14 a0 28 02 81 42 80 00 28 00
15 80 30 a5 da 8c a1 48 4e 1b 24 34 85 85 a5 43
5d b0 60 4c 42 a5 28 50 02 96 2e 56 50 28 51 ad
14 2d 00 a3 21 8b 8a 0c 00 60 5a 32 d6 00 a3 15
```

Frequent LR byte values included:

```text
00
b6
80
0c
28
6d
50
14
0a
2d
05
02
99
a0
30
60
```

The similar distributions and visual behavior strongly indicated the same codec family at both resolutions.

---

## Compression blind alley: the 0.6.0 heuristic bake-off

Before the historical decompressor was recovered/implemented, version 0.6.0 tried multiple speculative decompression families.

These were **deliberately experiments**, not claims.

Candidates:

1. `rle4-delta-hi-run`
   - high nibble = run length
   - low nibble = signed delta
   - left predictor

2. `rle4-delta-lo-run`
   - low nibble = run
   - high nibble = signed delta

3. `rle4-abs-hi-run`
   - high nibble = run
   - low nibble = coarse absolute intensity

4. `rle4-abs-lo-run`
   - inverse nibble roles

5. `tri3-delta`
   - each nibble expanded to three delta-derived samples

6. `tri3-absinterp`
   - each nibble expanded into three absolute/interpolated samples

7. `tri3-delta-rowreset`

8. `tri3-absinterp-rowreset`

9. `bit2-rundelta`
   - 2-bit symbol interpretation

10. `bit2-runabs`

All produced glitches.

Representative failure characteristics:

- horizontal bars / coherent but wrong run structure;
- repeating mosaic/static;
- uniform-ish local texture with no correct scene;
- severe striping;
- blank lower regions from wrong stream-length semantics.

One important mistake in that exploratory build was forcing candidate output to the expected Bayer size by padding/truncation. That made some garbage appear more "complete" than it deserved.

Lesson:

> Failed speculative decodes should be allowed to fail naturally. Do not manufacture a full image length just to render it.

The 0.6.1 lab stopped doing that.

---

## Historical compressed codec breakthrough

The decisive compressed-codec result came from returning to current/historical libgphoto2 Mars source and locating the actual `mars_decompress` behavior.

The Mars codec is not simple byte/nibble RLE.

It is a **bitstream variable-length code + Bayer-aware predictive reconstruction**.

MarsCam Modern 0.6.1 implemented an independent C++ behavioral equivalent.

This produced actual photographs immediately from both:

```text
HR+CP
LR+CP
```

---

## Compressed codec: exact practical codebook

The current decoder reads the payload **MSB-first at the bit level**.

At each predicted sample, inspect the upcoming bits.

The effective codebook implemented is:

| Prefix test | Meaning | Bits consumed |
|---|---|---:|
| `0` | delta `0` | 1 |
| `110` | delta `-3` | 3 |
| `101` | delta `+3` | 3 |
| `1000` | delta `+8` | 4 |
| `1001` | delta `-8` | 4 |
| `1111` | delta `-20` | 4 |
| `11100` | delta `+20` | 5 |
| `11101` | absolute-value escape | 5 + 5 |

For the absolute-value escape:

1. consume prefix `11101`;
2. read another 5 bits;
3. those 5 bits represent the high 5 bits of an 8-bit intensity;
4. practical reconstructed intensity is:

```text
absolute = five_bits << 3
```

Equivalent levels:

```text
0, 8, 16, 24, ... 248
```

Decoded relative samples are clamped to:

```text
0..255
```

This compact codebook is sufficient to reconstruct the core CP still decoding behavior.

---

## Compressed codec predictor

The codec predicts same-color Bayer samples, therefore it commonly works in two-pixel steps.

Let:

```text
lp  = current row, col - 2
tp  = row - 2, same col
tlp = row - 2, col - 2
trp = row - 2, col + 2
```

For the first two rows:

- the first two pixels are literal 8-bit seed values;
- subsequent pixels use the value two samples back (`col - 2`) as predictor.

For later rows:

#### Left edge (`col < 2`)

```text
pred = (tp + trp) / 2
```

#### Right edge (`col > width - 3`)

```text
pred = (tp + lp + tlp + 1) / 3
```

#### Main interior predictor

Current exact implementation:

```text
pred = (lp + tp + (tlp >> 1) + (trp >> 1) + 1) / 3
```

Then:

```text
pixel = clamp(pred + delta, 0, 255)
```

except absolute escape codes, which replace the prediction with their quantized absolute value.

---

## The three-byte lookahead nuance

When the historical decoder was first applied to `.emarsraw` files trimmed exactly to the PAT stored length, it stopped only a tiny number of pixels short:

```text
HR: about 5 pixels short
LR: about 7 pixels short
```

The historical implementation was designed around a padded transfer buffer rather than a file ending exactly at the stored-size boundary.

Current decoder behavior therefore appends:

```text
3 zero bytes
```

to the compressed payload as harmless bitstream lookahead.

This allows the final codes to be read without changing the archived `.emarsraw`.

Important distinction:

- `.emarsraw` remains byte-for-byte the trimmed camera payload;
- lookahead bytes exist only inside the decoder's temporary buffer;
- this is a decoder-boundary accommodation, not invented image metadata.

---

## 0.6.1 variable-length codec lab

0.6.1 also retained alternate probes to test assumptions:

- historical Mars decoder at bit offsets `0..7`;
- reversed delta signs;
- alternate four-neighbor predictor;
- absolute-bucket centering `+4`;
- Golomb-Rice signed residuals with multiple `k`;
- Exp-Golomb variants.

Key result:

```text
00-mars-historical
```

produced the correct photographs.

User observation also found:

```text
10-mars-abscenter-bggr-proc21
```

visually identical to:

```text
00-mars-historical-bggr-proc21
```

for tested samples.

The speculative Rice/Golomb/offset families were not needed for the real codec.

Some deliberately wrong variants produced visually interesting color/glitch effects and were later retained as optional "fun" outputs.

---

## Compressed Bayer phase

After successful decompression, the same color-phase question was tested.

Result:

> CP images also render correctly as **BGGR**.

Thus the entire verified still pipeline is consistent:

```text
nP → raw Bayer → BGGR
CP → Mars decompress → Bayer → BGGR
```

The compression codec reconstructs the Bayer plane; it does not change the underlying mosaic phase.

---

## Historical MARSCAM BMP output: surviving reference files

Several original BMP files exported directly by the period MARSCAM software were recovered from older personal photo archives. No matching camera raws survive, so these files cannot reveal the old rendering pipeline exactly. They are still valuable as end-state reference material.

### BMP structure

The recovered files are extremely plain Windows bitmaps:

```text
High-resolution output:
352 × 288
24-bit RGB
BI_RGB / uncompressed
54-byte BMP + DIB header
304,128 bytes of pixel data
304,182 bytes total

Low-resolution output:
176 × 144
24-bit RGB
BI_RGB / uncompressed
54-byte BMP + DIB header
76,032 bytes of pixel data
76,086 bytes total
```

Common properties observed in the files:

- no palette;
- no EXIF;
- no camera timestamp;
- no embedded camera/model text;
- no proprietary MARSCAM metadata block;
- classic bottom-up BMP row storage;
- nominal resolution field around 1000 pixels/meter (about 25.4 DPI).

This confirms that MARSCAM preserved the camera's two still dimensions as native BMP dimensions. It did not automatically enlarge 176×144 LR photographs to 352×288.

The original CP/nP state cannot be recovered from these BMPs. Once MARSCAM had decoded a still into RGB, the stored compression mode was no longer represented in the BMP structure.

### Historical color behavior

The old software did not consistently neutralize white balance. Some indoor photographs are strongly warm/orange.

Measured examples from one HR set:

```text
003.bmp mean RGB ≈ 132.38, 124.16, 92.09
004.bmp mean RGB ≈ 135.00, 81.92, 2.60
005.bmp mean RGB ≈ 68.72, 28.95, 0.91
006.bmp mean RGB ≈ 120.19, 67.89, 1.11
007.bmp mean RGB ≈ 47.13, 30.17, 0.90
```

The very low blue-channel means in several warm indoor frames are real. A historically faithful MARSCAM-style rendering therefore should not be assumed to look modern, neutral, or white-balanced.

The old output also appears relatively gentle in global tone mapping. Dark or warm frames were sometimes left with compressed histograms rather than stretched aggressively to full black-to-white range. Examples:

```text
005.bmp channel maxima: R 156, G 87, B 38
006.bmp channel maxima: R 223, G 144, B 48
```

This is relevant to current rendering experiments, some of which stretch contrast more strongly than the old application did.

### Horizontal line/banding behavior

The surviving MARSCAM BMPs still contain horizontal row structure, but the lines are visibly and measurably less pronounced than in the current MarsCam Modern renders.

Analysis of multiple old files shows recurring alternating-row components. The exact amplitude varies by image and channel. The effect is not purely luminance-based; red and green row residuals can behave differently. That matters because a simple equal-gain RGB row correction may not reproduce the historical result.

The strongest reference is `018.bmp`, a 176×144 LR photograph aimed almost entirely at a plain blue sky. Its measured mean RGB is approximately:

```text
R 128.80
G 198.24
B 228.61
```

Observed channel ranges are approximately:

```text
R 81–165
G 152–238
B 178–255
```

Because the scene contains very little real horizontal detail, periodic row structure is easier to separate from subject detail. The file shows a measurable fine alternating-row component, including a clear two-row component in green, plus broader low-amplitude row variation. The old software therefore reduced the camera's line pattern without eliminating it.

A useful working hypothesis is that MARSCAM may have applied smoothing or correction on the Bayer plane, or used a softer/different demosaic, rather than performing only a post-demosaic whole-row luminance adjustment. This is an **inference**, not a recovered algorithm.

### HR and LR comparison

A later outdoor set contained eight native 176×144 LR BMPs and one 352×288 HR BMP. The HR example retains the same general family of horizontal structure. This supports the view that the line pattern originates in the sensor/readout/Bayer pipeline rather than being solely an artifact of LR resizing.

The LR files also look softly interpolated rather than like crude nearest-neighbor decimation. The exact camera-versus-software contribution remains unknown.

### Extreme highlight behavior: black-sun artifact

Two surviving HR photographs taken from an aircraft window toward the Sun show a characteristic severe-overload artifact:

- a black central disk;
- a magenta/pink ring around it;
- surrounding saturated highlights.

The BMPs contain actual `0,0,0` pixels as well as `255,255,255` pixels. MARSCAM therefore preserved the sensor's extreme-overload behavior instead of simply mapping every saturated region to white.

This is consistent with a CMOS "black sun" / solarization-type overload artifact. The observation is useful as a historical-rendering constraint: a faithful compatibility mode should not silently invent detail or force such regions to white.

### What these BMPs do not establish

Without matching `.emarsraw` files from the same exposures, the following remain unresolved:

- exact MARSCAM demosaic kernel;
- exact gamma curve;
- exact row-correction algorithm;
- exact color matrix or channel-gain logic;
- whether correction was fixed or image-adaptive;
- whether individual historical images originated in CP or nP;
- how much of a warm color cast came from camera data versus software processing;
- whether any pre-demosaic filtering was used.

The BMPs are reference outputs, not a mathematically invertible record of the old pipeline.

### Recommended control experiment

The most useful modern comparison is a uniform blue-sky series captured within a short interval in all four still modes:

```text
HR + nP
HR + CP
LR + nP
LR + CP
```

Preserve the `.emarsraw`, full transport capture, decompressed Bayer data for CP, and all current render variants. Compare row statistics, color balance, and tone against the historical `018.bmp` sky frame. The skies will not be identical, but the uniform field removes much of the scene-content ambiguity.

A second useful test is a safe bright point source or specular reflection to study overload behavior without deliberately viewing the Sun through the camera.


## October 2026 empirical rendering validation

A larger real-world field test was completed in October 2026 after the historical BMP comparison work. This test used the live `0.6.3` downloader and deliberately covered bright sky, architecture, foliage, hard sun/shadow transitions, dark parking-garage lighting, car headlights, neutral concrete, and skin tone. Several subjects were photographed in both nP and CP at high resolution; LR examples were also retained.

This campaign changed the rendering conclusions in several useful ways.

### Correct-path outputs versus deliberate glitch outputs

For both nP and CP, development build 0.6.3 generates eight images, but only the first four are intended as normal photographic interpretations.

For nP:

```text
01-normal-proc21
02-normal-proc20
03-normal-proc23
04-normal-soft
05-fun-rggb-proc21
06-fun-bggr-mars
07-fun-rggb-baseline
08-fun-gbrg-proc21
```

For CP:

```text
01-normal-proc21
02-normal-proc20
03-normal-proc23
04-normal-soft
05-fun-rggb-proc21
06-fun-offset4-bggr-proc21
07-fun-swapsigns-bggr-baseline
08-fun-swapsigns-bggr-proc21
```

This distinction matters. During visual review, several CP outputs looked dramatically corrupted: rainbow gradients, false color, block-like transitions, and psychedelic geometry. Those are **not evidence that the recovered CP decoder is intermittently failing**. They are the deliberately incorrect diagnostic/fun variants:

- `05-fun-rggb-proc21`: correct Mars decompression, deliberately wrong RGGB Bayer interpretation;
- `06-fun-offset4-bggr-proc21`: deliberately starts the variable-length compressed bitstream four bits out of alignment;
- `07/08-fun-swapsigns-*`: deliberately reverse the signs of predictive residuals.

Because the Mars codec is predictive, an intentionally wrong symbol interpretation can propagate coherently through later pixels and still leave recognizable scene structure. The result can look spectacularly broken while remaining a controlled experiment.

The **01–04 normal CP images use the correct historical Mars decompression and BGGR interpretation**. Across the October set, those four remained geometrically stable and recognizable.

### Bright sky and outdoor architecture

Bright daytime sky scenes showed that the current correct-path normal recipes can be somewhat dark or muted compared with the observed scene. This occurred in both HR and LR examples.

The `06-fun-bggr-mars` nP render, although originally retained as a "fun" historical/Mars-style output, sometimes produced a brighter sky or more vivid architectural mood that subjectively resembled the scene better than the more restrained normal family. It is still not a replacement for a correct neutral render: in other scenes it becomes too green/yellow or otherwise stylized.

This suggests that a useful expressive render needs its own controlled tone/color design rather than simply promoting the current historical-Mars output wholesale.

### Foliage

A high-resolution nP palm-tree scene was a strong color/detail test. The four correct normal outputs all remained plausible; differences were largely matters of tone, highlight rolloff, and preference rather than basic decoding correctness.

The wrong Bayer and historical-Mars variants produced visibly cooler, greener, or more cyan interpretations. This reinforced that the normal BGGR path is stable and that a small set of curated normal outputs is more useful than forcing a single result.

### Hard sun and deep shadow

A walkway scene with direct sunlight and a deep adjacent shadow exposed the limitations of the current tone mapping:

- sunlit areas approached or reached white;
- deep shadow could become very dark or nearly black;
- midtone separation was limited;
- the normal outputs remained structurally correct.

This is a tone-mapping/dynamic-range problem, not a Bayer or decompression problem.

Future refinement should concentrate on shadow lift, highlight protection, and midtone placement without inventing detail that the sensor did not record.

### Dark garage and headlights

A parking-garage scene with oncoming headlights provided the opposite stress case: very dark surroundings with small intense lights.

The correct normal outputs preserved the scene accurately but conservatively. A more aggressive/historical-style render gave the lights more presence and lifted some surrounding structure, but also made horizontal banding more visible.

This supports two conclusions:

1. a more expressive render should differ in tone curve, not merely saturation;
2. stronger tone lift may need correspondingly stronger line/banding suppression.

### Skin tone and neutral concrete

A high-resolution nP photograph of an arm against gray concrete in bright sun, followed by a nearly matched high-resolution CP version, was one of the most useful controls.

In both nP and CP:

- all four normal renders were plausible;
- skin tone remained believable;
- gray concrete remained broadly neutral;
- no catastrophic CP-specific darkness or color failure appeared.

This directly corrected an earlier provisional interpretation that CP might require a globally brighter pipeline. Some sky-heavy CP scenes looked dark, but the matched skin/concrete pair showed that **CP is not intrinsically dark**. Scene content and histogram shape matter substantially.

### nP versus CP in bright exteriors

Some nearly adjacent outdoor nP/CP exposures looked dramatically different: one nP exposure could be badly blown in bright sky while a CP exposure taken seconds later retained a blue sky and a usable scene.

This is a real observation, but it is **not enough to prove that the CP/nP storage setting changes sensor exposure behavior**. The camera's automatic exposure, framing, timing, firmware state, or scene sampling may also contribute. Treat this as an unresolved capture-behavior question, not as a codec fact.

### Horizontal banding after the new tests

The October images reaffirmed the earlier historical-BMP finding:

- current MarsCam renders still show horizontal lines more distinctly than the original MARSCAM BMPs;
- the lines remain visible even when color/tone is otherwise good;
- aggressive brightness/contrast can make them more obvious.

The historical files remain the best evidence that MARSCAM performed some additional smoothing, row correction, demosaic behavior, or related processing beyond the current simple pipeline.

### Rendering conclusion after the October campaign

The evidence now supports a range of sane render intents rather than a single universal output.

Technically, the correct recovery path remains:

```text
PAT / transfer
→ nP Bayer or correct CP decompression
→ BGGR
→ demosaic
→ tone/color/banding treatment
```

The remaining variability is mainly **rendering**, not extraction.

A practical user-facing set can therefore be built around three interpretations:

```text
Restrained
Balanced
Expressive
```

Those are product labels, not protocol facts. The exact recipes may continue to be tuned, but the October campaign is sufficient to proceed with GUI integration rather than continuing to block on more scene collection.

---

## Post-release field observation: apparently reset counter, recoverable old photos, and changing directory (2026-10-23)

**Evidence:** single first-person observation on the verified physical `093A:010E` camera, after the Windows GUI was developed/published. This has **not** been reproduced under controlled conditions. It is important enough to retain without upgrading a hypothesis to a fact.

Observed sequence, in order:

1. Camera was connected to the computer for an extended period, then disconnected.
2. On turning it on, the LCD displayed **`20`**, corresponding to the usual available-photo count for the default HR/uncompressed setting. The owner reasonably interpreted this as a reset/empty state.
3. One new random photograph was captured.
4. On reconnecting and selecting **Refresh Photo List** in MarsCam Modern, the application displayed **all of the previous photographs thought to have disappeared**, but **not** the newly captured photograph. Crucially, those older images were **actually downloadable**, not merely phantom names/indices.
5. On a second **Refresh Photo List**, the photo list changed to contain **only the new photograph**, with the old ones absent.

### Observations versus interpretations

- **Established:** The LCD free-capacity count, the photo directory returned by a given scan, and accessibility of the underlying old payloads did **not** agree at every point during this episode.
- **Established:** At least one scan supplied sufficient old directory/position information to read real old photo bytes successfully. A simple stale preview or cached filename display would not by itself explain successful downloads.
- **Established:** Two successive explicit scans returned materially different views without the user intending to delete or erase anything.
- **Not established:** Which internal memory/buffers/pages supplied the older PAT; whether the camera experienced a true power reset; how long image payloads persisted; whether the new image overlapped old allocations; whether it could be repeated; whether all apparently deleted photographs on MR97310 cameras are recoverable; or whether the behavior was caused by SDRAM retention, incomplete memory initialization, stale firmware state, shifted configuration pages, or a transport/cache interaction.
- **Not established:** Persistence through a complete battery-power loss. This observation must **not** be cited as proof of nonvolatile storage.

This is consistent with, but does not prove, the previously documented unstable/misaligned configuration/PAT behavior. The list collector already performs up to five bounded observations with explicit close/reopen. Its results can only be as stable as the underlying device state at the time. A camera can therefore appear to possess inconsistent photo directories even when the parser itself is working as designed.

### Practical recovery lesson

**If a scan reveals unexpected old photographs, export them immediately, preserving `.emarsraw`, before refreshing again.** The next scan might show a different directory. Do not repeatedly power-cycle, reset, remove the battery, or create new photographs as a speculative recovery strategy; writing new images may overwrite data and resets may destroy valuable state. The software should not introduce automatic/background photo-list refreshes in response to this discovery.

A possible **future, explicitly optional and read-only** forensic feature would retain snapshots of distinct validated PAT observations, their scan time, source configuration offset, and raw entry bytes, rather than silently replacing them. Such a feature would need careful validation of whether a formerly observed entry is still downloadable; preserving old addresses does not guarantee old bytes remain valid. It must not become an automatic reset, brute-force memory scanner, or undocumented camera-command feature.

---

## Safety philosophy

This project intentionally gives up destructive camera management features.

Current safety rules:

1. exact VID/PID filter before active access;
2. descriptor verification again after open;
3. active protocol currently restricted to verified CIF PID `010E`;
4. required endpoint types validated;
5. Mars command writes are small and bounded;
6. data reads bounded;
7. photo length comes from validated PAT records;
8. 16 MiB hard ceiling on photo transfer;
9. finite WinUSB timeouts;
10. positioning loops bounded by wall clock and poll count;
11. `0x02` recovery only as one documented single-byte `0x19` exchange;
12. reset `19 54` not implemented;
13. no erase/delete commands;
14. no config/firmware writes;
15. no arbitrary command console;
16. no endpoint `0x07` operations;
17. no registry modification;
18. no driver-install behavior inside current source;
19. preserve raw bytes before transforming them;
20. output uses non-overwriting names.

Any implementation should preserve this philosophy.

---

## Important blind alleys and why they were wrong

This section exists so a future developer/AI does not repeat old mistakes.

### 40.1 Installing ancient driver binaries

**Temptation:** just make `MR97310c.sys` work.

**Rejected because:** unnecessary kernel risk and modern-signing complexity.

**Correct approach:** Microsoft WinUSB + user-mode protocol.

---

### 40.2 Fake "driver download" updater software

Searches surfaced third-party updater EXEs instead of the actual camera archive.

**Lesson:** archived original packages and source archaeology are more useful than generic driver-download sites.

---

### 40.3 Custom GUID-only discovery

Early 0.1 code expected a custom interface GUID.

**Problem:** after manually binding generic WinUSB, the camera was discoverable through the standard USB interface class rather than the assumed custom interface.

**Fix:** enumerate standard USB device interfaces, filter exact `093A:010E/010F`, then verify the opened descriptor again.

---

### 40.4 Treating endpoint `0x83` as interrupt

The physical descriptor shows it is **bulk IN**.

Always inspect real descriptors.

---

### 40.5 Requiring final response `0xA8`

Early config logic expected one historical response pattern.

Physical camera often returned:

```text
0xA2
```

after successful positioning.

**Fix:** require the meaningful `0x0A` position acknowledgement and log the final 16-byte response instead of forcing one rebadge-specific final value.

---

### 40.6 Refusing all `0x02` handling

Initially safe but made some real states unrecoverable.

**Fix:** adopt exactly one historical single-byte `0x19` clear only in the documented state, then stop if it does not clear.

Still do **not** implement reset.

---

### 40.7 Polling too fast / sleeping too long

Protocol timing needed to match old-device expectations:

- ~10 ms handoff delay;
- small pacing between status polls;
- bounded deadlines.

Both hammering the endpoint and using arbitrary large sleeps were inferior to reproducing historical cadence.

---

### 40.8 Trusting first plausible PAT

Wrong because unstable config memory could produce:

- partial valid fragment;
- random false record;
- shifted real directory elsewhere.

**Fix:** score both offsets and later collect across multiple reads.

---

### 40.9 Loosening PAT validation to accept weird data

Wrong direction.

The better fix was stronger invariants, not permissive parsing.

The 796,175-byte fake still proved this.

---

### 40.10 Repeated config acquisitions on one live handle

Could encourage stale/unstable state.

**Fix:** close/reopen WinUSB between observations.

---

### 40.11 Reopening before old handle was really closed

Produced:

```text
Win32 error 5
ERROR_ACCESS_DENIED
```

**Fix:** explicit `close()` then delay then open.

---

### 40.12 Assuming historical RGGB must be correct

It produced recognizable but wrong colors.

**Fix:** exhaustive phase/render matrix established BGGR empirically.

---

### 40.13 Treating bad color as a tone-processing problem before checking Bayer geometry

Until Bayer phase was correct, white-balance tuning was mostly polishing the wrong interpretation.

Geometry first; processing second.

---

### 40.14 Blind compressed-RLE guessing

0.6.0's nibble/run/tri3/2-bit ideas produced only glitches.

Useful as elimination, but not a path to the codec.

**Correct next move was historical-source research.**

---

### 40.15 Padding failed candidate decodes to image size

Made wrong codecs look superficially complete.

**Fix:** in 0.6.1, incomplete candidates remain partial and are not rendered as if valid.

---

### 40.16 Shipping a build directory inside a source ZIP

0.6.0 accidentally packaged a Linux-created `build/CMakeCache.txt`.

On Windows:

```text
CMake Error:
current CMakeCache directory C:/...
is different from /mnt/data/...
```

Source was fine; stale build artifacts were the problem.

Fix:

```powershell
Remove-Item -Recurse -Force .\build
```

Future source ZIPs exclude `build/`.

---

## Detailed version history

### 0.1.0 - scaffold / parser / initial discovery

- C++20/CMake project;
- PAT parser groundwork;
- early custom-GUID device discovery;
- descriptor-oriented read-only plan;
- no photo download yet.

---

### 0.1.1 - reliable generic WinUSB discovery

- enumerate standard Windows USB interfaces;
- exact `093A:010E/010F` filtering;
- verify VID/PID after open;
- report service, descriptors, endpoint metadata;
- descriptor-only default;
- no Mars writes.

---

### 0.2.0 - first bounded active config probe

- explicit `probe-config`;
- endpoints `0x04/0x83/0x82`;
- timeouts;
- bounded positioning loop;
- 8192-byte config target;
- initially refused `0x02`;
- initially required historical data-ready pattern too strictly.

---

### 0.2.1 - transcript preservation

- detailed per-transfer transcript;
- preserve diagnostics even when protocol stops safely;
- sequence, elapsed time, endpoint, requested/actual, status, error, payload.

---

### 0.2.2 - Windows namespace/build fix

No protocol change.

---

### 0.2.3 - timing correction

- ~10 ms Mars sleep;
- paced polling;
- wall-clock deadlines instead of near-instant tight loop.

---

### 0.2.4 - longer GET_DATA observation

- INIT remains short bound;
- GET_DATA positioning extended to ~2 s;
- status-transition summary;
- discovered real final response could be `A2`, not expected `A8`.

---

### 0.2.5 - accept observed A2 + automatic transcript

- allowed verified `A2` pattern;
- first practical 8192-byte config reads.

---

### 0.2.6 - one-shot jam clear

- single `0x19` when GET_DATA begins specifically in `0x02`;
- direct 16-byte response;
- 10 ms delay;
- check again;
- never `19 54`.

This produced the first solid config/PAT success.

---

### 0.3.0 - list and raw download

Added:

```text
list
dump-raw N
```

- parse PAT;
- report raw 8-byte entries;
- derive per-photo size/mode;
- full padded transfer;
- trimmed raw payload;
- support PAT offsets 16/144;
- 16 MiB cap.

---

### 0.4.0 - first PNGs

Added:

```text
download N
download-all
```

- uncompressed still decoding;
- 12-byte header + Bayer;
- early RGGB assumption;
- bilinear demosaic;
- small dependency-free PNG writer;
- compressed raws preserved but not decoded.

---

### 0.4.1 - safer shifted PAT handling

- markerless/144-byte fallback;
- save every config block;
- diagnostic previews.

---

### 0.4.2 - partial-PAT recovery

- accept strongly validated prefix;
- stop on first invalid entry;
- avoid walking into camera RAM.

---

### 0.5.0 - dual PNG output and `.emarsraw`

- archival extension changed from `.raw` to `.emarsraw`;
- permanent unprocessed + processed outputs;
- libgphoto2-inspired Mars color pipeline port;
- transfer captures retained.

---

### 0.5.1 - strong candidate selection

- inspect both `0x10` and `0x90`;
- choose strongest structured PAT;
- regression test for false one-entry `0x10` vs true four-entry `0x90`.

---

### 0.5.2 - bounded config retries

- up to 3 config acquisitions;
- preserve wrong-page captures;
- do not weaken validation.

---

### 0.5.3 - exact still-size validation

- nP exact size invariant;
- CP must be smaller;
- reject 796,175-byte fake still;
- reopen interface between attempts.

---

### 0.5.4 - five-observation collector

- five fresh observations;
- merge/dedupe valid records;
- sort/renumber;
- mode-aware filenames.

First implementation exposed reopen-handle bug.

---

### 0.5.5 - explicit close/reopen fix

Solved Win32 error 5.

---

### 0.5.6 - 16-way Bayer/color lab

- all 4 Bayer phases;
- all 4 render approaches;
- established BGGR as correct family.

---

### 0.5.7 - staging handoff

Important nuance:

- documented intention to promote BGGR + neutral;
- served as a source/handoff staging release;
- did **not** yet embody every intended runtime behavior;
- led directly to controlled four-mode validation.

---

### 0.5.8 - offline processing lab

- no camera access;
- `C:\MarsTest\Raws`;
- 24 variants per nP sample;
- processing/tone/destripe experiments.

---

### 0.5.9 - curated finalists + CP analysis

- narrowed nP variants;
- recognized CP raws;
- saved compressed payload `.bin`;
- wrote compression reports;
- no fake CP PNGs.

---

### 0.6.0 - speculative compression bake-off

- ten candidate codec families;
- all ultimately wrong;
- visually useful failure evidence;
- source ZIP accidentally included build cache in one package.

---

### 0.6.1 - real historical codec

- historical Mars variable-length predictive decoder;
- bit-offset/predictor/sign experiments;
- no forced padding of failed candidates;
- actual photographs recovered from HR+CP and LR+CP;
- established compressed BGGR path.

---

### 0.6.2 - consolidated offline processor

- both nP and CP from `.emarsraw`;
- eight curated PNG outputs;
- CP decompressed Bayer preservation;
- summary files;
- offline regression workflow.

---

### 0.6.3 - live camera consolidation

- camera plugged in directly;
- `download` and `download-all` process both nP/CP;
- save transfer + `.emarsraw` + CP Bayer + PNG variants;
- confirmed working in a subsequent live run.

This remains the *historical CLI* checkpoint; the later 0.7.0–0.7.4 GUI line was subsequently built, used with the physical camera, and published.

---

### 0.7.0 - first Windows GUI implementation and Windows build repairs (2026-10-23)

The 0.7.0 GUI was **not** simply a wrapper around the 0.6.3 executable. During this build session, the exact 0.6.3 source ZIP was unavailable to the working runtime, so the GUI line was constructed from the available **0.6.1 WinUSB/PAT code**, recovered historical predictive Mars codec, and later 0.6.2/0.6.3 rendering knowledge preserved in the project guides. This provenance matters if tracing diffs across historical source archives.

- Built a native Win32 C++20 graphical application and separate reusable `marscore` library, plus legacy diagnostic CLI/offline lab tools.
- Preserved verified three-pipe USB photo transfer, status/position logic, bounded five-observation PAT acquisition, full read-only boundaries, and preserved raw-first output.
- Implemented Windows PnP physical-presence detection distinct from WinUSB interface readiness; camera presence alone is not evidence that the correct driver is bound.
- Added single-photo preview, photo listing, bulk download, shared correct-path BGGR CP/nP decoder, curated styles, additive render modifiers, optional forensic exports and codec/Bayer experiments.
- Introduced host-local `YYMMDDHHMMSS` download timestamps, stable per-photo suffixes, non-overwriting `CREATE_NEW`, folder selection, and optional batch folders.
- First Windows build launcher erroneously assumed `cmake.exe` was on PATH, then erroneously forced the VS 2022 generator. Local environment was actually **Visual Studio 2026 (18) Community**, its bundled **CMake 4.3.1-msvc1**, MSVC **19.51.36256.0** / tools `14.51.36231`, and Windows SDK **10.0.26100.0**. The corrected launcher discovers bundled CMake and selects `Visual Studio 18 2026` with the default `v145` toolset; it also supports 2022 / `Visual Studio 17 2022` (`v143`). Isolated `build-vs18-x64`/`build-vs17-x64` directories prevent generator-cache conflicts.
- First Windows GUI compile then failed because `FOLDERID_Pictures` needed `<KnownFolders.h>`; adding that include resolved the reported error. Unlike these GUI/launcher issues, the camera core, CLI, and tests had already compiled.
- **User-confirmed:** 0.7.0 ultimately launched and communicated with the physical camera, showed previews, and downloaded photos on Windows 10 x64.

### 0.7.1 - initial post-live-test refinements

- Replaced Ctrl/Shift-style selections with independent per-photo download checkboxes.
- Fixed previously previewed pixels appearing outside the preview frame at larger window sizes by drawing into a clipped child preview control.
- Changed Activity to a read-only RichEdit log; made the main window vertically scrollable; removed unnecessary on-screen labels; simplified headings.
- Added subdued colored Download buttons, clearer gray/white control contrast, Help / Info, improved output-folder controls, and `BGGR (Auto)` label.
- Dated batch folders now begin at `YYYY-MM-DD_01` rather than an unsuffixed initial folder.
- Added five **pixel-domain** Extreme glitch effects for **both nP and CP**: scanline shear, channel tear, solarize/posterize, mosaic shred, and digital storm. These manipulate decoded RGB images offline, not the camera's memory or compressed data.
- Windows C++ compile needed a small additional fix for `std::max` mixing Win32 `LONG` with `int` in `gui.cpp` (and downstream `std::clamp` diagnostics). Cast/match the types rather than modifying compiler/toolset settings.

### 0.7.2 - clearer selection and device details

- Enlarged photo selection checkboxes to about 20 px; checked rows use bold text. Clicking the rest of a row triggers preview, while clicking its checkbox changes selection without fetching the photo.
- Added selection counts to the two primary download buttons; made Refresh explicitly clear the old preview; expanded visible device/USB descriptor data without new camera protocol reads.
- Corrected nontechnical disconnected-state text and made support email plain text in Help.
- Clarified that experimental image **counts depend on data format**, not a failure to export files: in a confirmed batch with Expressive, Brighter, and both experimental groups selected, 11 PNGs were exported for nP and 17 for CP, plus one `.emarsraw` each. The common five pixel-domain effects work for both; CP additionally permits deliberate misdecoding of its bitstream.

### 0.7.3 - final operating-layout polish

- Increased download-button width for three-digit selected/total counts; added outline-pill Select All and Select None, which only set checkbox states.
- Activity log follows the newest message after each append.
- Placed the dated-folder option directly under the output-folder control and above the styles.
- Fixed Refresh Photo List incorrectly leaving the preview placeholder on `Loading preview...`; the idle placeholder now returns unless a preview is actually running.

### 0.7.4 - app identity, dialogs, final public source checkpoint

- Embedded the owner's custom transparent illustration of the **actual silver/black Vivitar Mini Digital Camera**, through a Windows `.rc` resource, in a multi-resolution ICO containing **16, 20, 24, 32, 40, 48, 64, 128, and 256 px** variants. The image is built into the EXE (not a necessary runtime sidecar). Window large/small icons cover the common title-bar/taskbar/Explorer contexts.
- Converted Help / Info and Device Details to silent Windows TaskDialog panels, both with **Copy** and **OK**. Copy captures the full readable text; Help's web links remain clickable and their destinations appear in copied text. The dialogs do not contact the camera.
- Repainted antialiased Select All/None pill corners using GDI+ to correct edge artifacts; increased spacing around the Bayer dropdown.
- Kept camera protocol, PAT parser, actual decompressor, ordinary tone recipes, and file naming unchanged across this finishing pass.
- **User-reported:** the completed 0.7.4 GUI/application was satisfactory and the compiled Windows program was publicly published/downloadable on October 23, 2026. Independent Windows 11 hardware compatibility testing was not reported.

## Current controlled regression samples

The crucial four `.emarsraw` samples used privately during development were:

```text
marscam-photo-001-352x288-hr-np-20260913-001921.emarsraw
marscam-photo-002-352x288-hr-cp-20260913-001922.emarsraw
marscam-photo-003-176x144-lr-cp-20260913-001922.emarsraw
marscam-photo-004-176x144-lr-np-20260913-001922.emarsraw
```

Expected total sizes:

```text
101388
21285
5965
25356
```

These are personal image samples and should **not be redistributed publicly without explicit permission**.

They are excellent private regression fixtures.

---

## Tests a reconstructed implementation should have

At minimum:

### PAT parser

- exact verified four-mode records;
- both PAT offsets:
  - `0x0010`,
  - `0x0090`;
- false one-entry data at one offset vs stronger real directory at the other;
- partial PAT followed by garbage;
- clean `FF` terminator;
- giant false still rejected;
- nP exact-size validation;
- CP less-than-uncompressed validation.

### Transfer math

Expected examples:

```text
padded_download_size(101388) = 106496
padded_download_size(21285)  = 24576
padded_download_size(5965)   = 8192
padded_download_size(25356)  = 32768
```

### Image decoder

- 12-byte header skipped;
- RGGB/BGGR/GRBG/GBRG patterns;
- output dimensions;
- PNG signature;
- no buffer overrun on edges.

### CP decoder

For known private samples:

```text
HR+CP → exactly 101376 reconstructed Bayer samples
LR+CP → exactly 25344 reconstructed Bayer samples
```

### Safety

- wrong VID/PID cannot receive protocol commands;
- wrong endpoint types rejected;
- zero/oversized PAT size rejected;
- 16 MiB limit;
- no reset opcode path;
- no destructive command path.

---

### GUI-line verification at release

The 0.7.4 source tree defines three camera-independent CTest targets: `pat_parser`, `compressed_decode`, and `image_decode`. They passed under the development environment. The four private reference `.emarsraw` files were also exercised offline for correct dimensions and PNG output, **but not placed in any public release archive**.

The user compiled and successfully used the 0.7.x Win32 GUI with the real camera on Windows. Follow-up UI releases were iterated using actual screenshots and exported results. This does not establish universal Win10/Win11 compatibility: another device/rebadge and Windows 11 still need independent testing. Published binary hashes, code signing, SmartScreen reputation and other users' install outcomes were **not** independently verified in these notes.

The native build uses `MarsCam` as the GUI executable and retains `marscam-cli`/`marsrawlab` for diagnostics/development. The CMake configuration requests the MSVC multithreaded runtime (`/MT` in Release) to reduce redistributable requirements. Its Windows GUI links against WinUSB, SetupAPI, `cfgmgr32`, Comctl32, Comdlg32, Shell32, OLE32, GDI32, User32, UUID and GDI+; `.rc` embeds the app icon.

---

## If all source code vanished: minimum rebuild recipe

This section is intentionally redundant. It is the emergency reconstruction path.

### Step 1 - create a C++20 CMake project

Windows libraries:

```text
setupapi
winusb
cfgmgr32
```

Build a core library for:

```text
PAT
image decode
processing
compression
```

Keep WinUSB code isolated.

---

### Step 2 - enumerate the camera safely

Enumerate the standard Windows USB device interface class.

Accept only paths/instance IDs containing:

```text
VID_093A&PID_010E
VID_093A&PID_010F
```

Open with:

```text
CreateFileW(... GENERIC_READ | GENERIC_WRITE ...)
WinUsb_Initialize(...)
```

Then read actual USB device descriptor and verify VID/PID again.

For active protocol initially allow only `010E`.

---

### Step 3 - inspect and require endpoint types

Require:

```text
0x04 bulk OUT
0x83 bulk IN
0x82 bulk IN
```

Do not assume 0x83 is interrupt.

---

### Step 4 - implement tiny transfer wrappers

Every write/read should optionally log:

```text
sequence
elapsed milliseconds
direction
endpoint
requested bytes
actual bytes
success
Win32 error
semantic note
byte preview/full short payload
```

Use finite timeouts.

---

### Step 5 - implement status and command helpers

```pseudo
status(note):
    write 0x21 to ep 0x04
    return read exactly 16 from ep 0x83

command(x, note):
    write [0x19, x] to ep 0x04
    return status(note)
```

Jam clear:

```pseudo
clear_once():
    write [0x19]
    read exactly 16 directly from 0x83
    sleep 10ms
    return status()
```

---

### Step 6 - implement positioning

After sending address byte 2, poll:

```text
status first byte == 0x0A
```

Pace around 2 ms.

Bound by time and absolute poll count.

Do not require a specific final response after the final single `0x19`.

---

### Step 7 - implement config GET_DATA

Config address bytes are all zero.

Read exactly:

```text
8192 bytes from 0x82
```

Save them before parsing.

---

### Step 8 - parse PAT at both 16 and 144

Each record = 8 bytes.

Use low nibble of byte 0 as resolution.

Use bit `0x20` as compression.

Stored size:

```pseudo
size = b4 | b5<<8 | b6<<16
```

Apply strict still invariants.

Compare both offset candidates.

---

### Step 9 - repeat config observation five times

Between attempts:

```pseudo
close WinUSB
sleep briefly
open fresh WinUSB
sleep briefly
```

Collect exact valid PAT records across observations.

Deduplicate exact 8-byte record.

Sort by bytes 1..3 address key.

---

### Step 10 - photo download

Send PAT bytes 1..6 as address parameters.

Compute:

```pseudo
transfer_size = ((raw_size + 0x1B0) / 0x2000 + 1) * 0x2000
```

Read in 8192-byte chunks.

Require:

```text
transfer >= 128 + raw_size
```

Save:

```text
full transport
raw = transport[128 : 128+raw_size]
```

---

### Step 11 - determine nP vs CP

From PAT byte 0:

```pseudo
resolution = code & 0x0F
compressed = (code & 0x20) != 0
```

Map:

```text
0 → 176×144
2 → 352×288
```

---

### Step 12 - nP decode

Verify:

```text
raw.size == 12 + width*height
```

Bayer plane:

```text
raw[12 : 12+width*height]
```

Use **BGGR** for verified camera.

Bilinear demosaic as documented above.

---

### Step 13 - CP decode

Payload:

```text
raw[12:]
```

Append three zero bytes as safe bitstream lookahead.

Read bits MSB-first.

Initialize every first two rows with two literal 8-bit seeds, then decode remainder using the codebook and predictor in sections 30–32.

Require exactly:

```text
width * height
```

Bayer samples.

Then use same BGGR demosaic.

---

### Step 14 - image processing

At minimum preserve:

```text
baseline
current regular candidate(s)
```

For current-development equivalence implement recipe table in section 26.

---

### Step 15 - export PNG

Any safe RGB8 PNG library is fine.

Current project historically included its own tiny dependency-free PNG writer using:

- PNG signature;
- IHDR;
- scanline filter 0;
- zlib "stored" blocks;
- Adler-32;
- PNG chunk CRC32.

Exact PNG encoder is not archaeologically important; recovered pixels are.

---

## Current unknowns / things that should remain labeled unknown

Do not turn these into facts without new evidence:

- exact meaning of every PAT byte, especially byte 7;
- exact meaning of every image-header byte;
- whether the original proprietary MARSCAM application used the same tone/color processing as libgphoto2;
- whether BGGR applies to every MR97310 rebadge or only this physical sensor/layout;
- behavior of PID `010F`;
- actual role of additional historical resolution codes on this camera;
- precise root cause of camera losing photos after long USB sessions;
- whether background pinging would help or hurt that behavior;
- whether 320×240 / 640×480 stills are relevant to this exact model;
- whether audio/video stored items should be supported later;
- exact MARSCAM row/banding-reduction method;
- exact historical MARSCAM tone/color pipeline;
- whether CP/nP mode can indirectly affect capture/exposure behavior on this firmware, or whether observed paired-shot differences were caused by normal automatic-exposure variation;
- exact final color-science parameters for the three implemented modern render intents (presets ship and are useful but can continue to be tuned);

---

## Empirical image-quality status after October 2026 testing

The planned broad lighting test was completed sufficiently to remove it as a gate for software development.

Scenes included:

```text
bright blue/daylight sky
clouds and architecture
sunlit theater facade
foliage / palm tree
hard sun beside deep shadow
dark parking garage
oncoming headlights
downtown street / moving car
skin tone against gray concrete
matched or near-matched HR+nP and HR+CP subjects
LR nP / LR CP examples
```

The main findings are:

- the correct BGGR/decompression path remains stable;
- the four current normal recipes are all usable in at least some scenes;
- no one recipe is best everywhere;
- bright-sky/high-dynamic-range scenes expose tone-mapping weaknesses;
- dark scenes benefit from a more expressive midtone lift, but that can emphasize banding;
- skin/neutral scenes are already handled reasonably well by the normal family;
- CP is not globally darker or less correct than nP;
- the most spectacular CP distortions seen in testing are intentional debug/glitch decodes, not intermittent decoder failure;
- current horizontal banding remains stronger than in surviving original MARSCAM BMPs.

Further photography can still improve tuning, but no additional scene collection is required before building the first GUI/product version.

---

## Licensing / attribution

MarsCam Modern project code is intended as MIT-licensed original work.

Historical/protocol/image-decoding behavior was researched using libgphoto2, whose relevant code is under the LGPL.

The project should retain clear attribution, especially for:

- Mars protocol behavior;
- padded transfer formula;
- 128-byte lead-in behavior;
- PAT layouts;
- historical jam-clear behavior;
- Mars image-processing behavior;
- historical `mars_decompress` variable-length predictive codec.

Do not copy old proprietary Windows driver binaries into the project.

Do not imply the original Windows MARSCAM application source was recovered; it was not.

---

## Important project principles

For reconstruction, the following points are the most important:

1. **Preserve the raw data first.**
2. **Never trust retail branding; trust USB identity and descriptors.**
3. **Do not install the 2002 kernel driver.**
4. **`0x83` is bulk IN on the verified device.**
5. **`0x04` commands, `0x83` status, `0x82` data.**
6. **`0x0A` positioning matters more than one magic final status byte.**
7. **The only automatic state clear is one single `0x19` in the documented `0x02` situation.**
8. **Never send `19 54`.**
9. **Config is flaky even when USB succeeds.**
10. **PAT may appear at `0x10` or `0x90`.**
11. **Use strict size invariants and multiple bounded observations.**
12. **Photo USB transfer has a 128-byte lead-in.**
13. **`.emarsraw` starts at the 12-byte Mars image header, not at the USB lead-in.**
14. **nP = 12-byte header + one Bayer byte per pixel.**
15. **Verified camera Bayer = BGGR, despite historical RGGB assumptions.**
16. **CP uses a variable-length predictive bitstream, not simple RLE.**
17. **The exact compressed codebook/predictor in this document is enough to rebuild decoding.**
18. **Append tiny safe lookahead in the decoder; do not alter the archival raw.**
19. **Processing is still being tuned; do not confuse current tone recipes with protocol truth.**
20. **No destructive camera functions are necessary for the project's purpose.**

---

## Appendix A - constants

```text
VID                    0x093A
verified CIF PID       0x010E
known VGA PID          0x010F

command OUT            0x04
status IN              0x83
data IN                0x82

status request token   0x21
command token          0x19
start                   0x51
INIT operation          0xB5
GET_DATA operation      0x0F
reset                    0x54  ← never send

config bytes            0x2000 = 8192
USB photo lead-in       128 bytes
photo transfer cap      16 MiB

historical PAT offsets  0x0010 / 0x0090
PAT entry size          8 bytes
image header size       12 bytes

HR                      352×288
HR Bayer                101376
HR nP total             101388

LR                      176×144
LR Bayer                25344
LR nP total             25356
```

---

## Appendix B - controlled PAT examples

```text
HR+nP
02 00 04 00 0c 8c 01 9f
stored = 0x018c0c = 101388

HR+CP
a2 82 35 00 25 53 00 d1
stored = 0x005325 = 21285

LR+CP
20 e7 3f 00 4d 17 00 aa
stored = 0x00174d = 5965

LR+nP
80 d1 42 00 0c 63 00 02
stored = 0x00630c = 25356
```

---

## Appendix C - controlled raw headers

```text
HR+nP
ff ff 00 ff 96 64 00 20 00 00 0c 60

HR+CP
ff ff 00 ff 96 64 50 1d 7a 00 0c 60

LR+CP
ff ff 00 ff 96 64 50 1e 85 00 0c 60

LR+nP
ff ff 00 ff 96 64 00 1e 7a 00 0c 60
```

---

## Appendix D - current compressed decoder pseudocode

```text
function decode(payload, width, height):
    stream = payload + 00 00 00
    output = []

    for row in 0 .. height-1:
        col = 0

        if row < 2:
            output.push(read8())
            output.push(read8())
            col = 2

        while col < width:
            pred = predictor(output, width, row, col)

            prefix = inspect upcoming bits MSB-first

            if prefix starts 0:
                consume 1
                value = pred

            else if prefix == 110:
                consume 3
                value = pred - 3

            else if prefix == 101:
                consume 3
                value = pred + 3

            else if prefix == 1000:
                consume 4
                value = pred + 8

            else if prefix == 1001:
                consume 4
                value = pred - 8

            else if prefix == 1111:
                consume 4
                value = pred - 20

            else if prefix == 11100:
                consume 5
                value = pred + 20

            else if prefix == 11101:
                consume 5
                a = read5()
                value = a << 3

            else:
                fail invalid code

            output.push(clamp(value, 0, 255))
            col += 1

    require output.size == width*height
    return output
```

Predictor:

```text
if row < 2:
    pred = output[current_position - 2]

else:
    lp  = pixel(row,   col-2)
    tp  = pixel(row-2, col)
    tlp = pixel(row-2, col-2)
    trp = pixel(row-2, col+2) if available else tp

    if col < 2:
        pred = (tp + trp) / 2

    else if col > width-3:
        pred = (tp + lp + tlp + 1) / 3

    else:
        pred = (lp + tp + (tlp >> 1) + (trp >> 1) + 1) / 3
```

---

## Appendix E - current processing recipes

```text
proc20
low_percentile      0.0050
high_percentile     0.9950
gamma               0.90
contrast            0.95
saturation          1.00
gray_world_strength 1.00
highlight_rolloff   0.20
destripe_strength   0.75

proc21
low_percentile      0.0025
high_percentile     0.9975
gamma               0.95
contrast            0.90
saturation          0.98
gray_world_strength 1.00
highlight_rolloff   0.45
destripe_strength   0.50

proc23
low_percentile      0.0025
high_percentile     0.9975
gamma               0.95
contrast            0.90
saturation          0.98
gray_world_strength 1.00
highlight_rolloff   0.80
destripe_strength   0.50

soft
low_percentile      0.0010
high_percentile     0.9990
gamma               1.00
contrast            0.86
saturation          0.96
gray_world_strength 1.00
highlight_rolloff   0.65
destripe_strength   0.95
```

---

## Appendix F - historical/manual sources retained in the conversation/project

Useful artifacts encountered during development include:

```text
vivitar marscam guide manual.txt
marscam-list-*.txt
marscam-probe-*.txt
marscam-download-all-*.txt
marsrawlab-*.txt
*-compression-report.txt
*-candidate-report.txt
MarsCam-0-6-3-source.zip
```

The manual material was particularly useful for:

- rebadge names;
- LCD mode labels;
- image dimensions;
- capacity estimates;
- battery/volatile-memory warning;
- original supported Windows generations.

USB transcripts were the primary evidence for real device state behavior.

---

## Appendix G - current source/package checkpoint

Historical working CLI line:

```text
MarsCam 0.6.3
```

Current published Windows GUI/source line (user-reported public release):

```text
MarsCam Modern 0.7.4 (2026-10-23)
```

Current source archive: `MarsCam-0-7-4-source.zip`. SHA-256 of the specific local archive inspected for this document:

```text
dd6d4c47e1a83efd404d7c771c193fa9ce50338a93347d3f83589dafc5f51694
```

Known 0.6.3 source ZIP SHA-256 from the development session:

```text
7450bdf6ca94ffc2382761fe57c4d023472ddaffad76d0e82e94743356eed9a6
```

If another copy with a different hash exists, verify whether it is simply a regenerated archive or a materially different source snapshot before assuming corruption.

---

## Public distribution and archival package (2026-10-23)

The owner reports **MarsCam Modern 0.7.4 is now published**, initially through a small download page; an expanded "Everything About MarsCam" resource is planned but not yet represented as complete here. Do not confuse the source archive with the binary public download. The user assembles a portable **Windows x64** ZIP from the successfully compiled GUI EXE plus the support files:

```text
MarsCam-Modern-0.7.4-Windows-x64.zip
    MarsCam.exe
    README.html
    LICENSE.txt
    THIRD_PARTY.txt
```

The self-contained `README.html` is styled for nontechnical users and works offline, with embedded camera artwork. It explains USB cable/power caveats, connecting/listing/previewing, why multiple renderings appear, `.emarsraw` preservation and host-side timestamps, first-time/repair WinUSB setup, Windows 10/11 expectations, troubleshooting, and attribution. The driver instructions explicitly require identifying the camera as `USB\VID_093A&PID_010E` before changing drivers. Users should not select random device drivers, use paid automatic updater utilities, or install old proprietary kernel components. If the expected WinUsb Device selection is not available in Device Manager, stop rather than improvising.

Original source is MIT-licensed with historical libgphoto2 research attributed separately. No proprietary driver `.sys`, source images/private test photography, photo RAWs, or old commercial MARSCAM binaries are included in the release. The Windows program is portable; `resources/MarsCam-Modern.ico` and C++ source belong to the **source archive**, not the four-file end-user ZIP. Publishing does not itself prove third-party installations or Windows 11 hardware support; unsigned-app security warnings are a foreseeable independent distribution issue.

---

## Technical status at this revision

The verified `093A:010E` camera can be read on current Windows through WinUSB. Its unstable photo table can be recovered conservatively, both nP and CP stills can be extracted and preserved, CP decompression works at both verified resolutions, and the test camera's Bayer phase is empirically BGGR.

The October 2026 field tests closed the basic extraction/reconstruction feasibility question. The 0.7.x development cycle then integrated that capability into a native Windows interface, verified actual camera previews/downloads, and culminated in the owner's public 0.7.4 release. Remaining work is optional further color/banding tuning, improving driver onboarding beyond the current manual instructions, and testing other cameras/Windows 11 - **not** a prerequisite for the already published initial utility.

The surviving MARSCAM BMPs remain the historical reference for how much line reduction and how gentle a tone/color pipeline the original software used. Exact historical reproduction is still impossible without a matched old raw/BMP pair.

A later Windows driver-loss incident established that a camera can be physically present, show `PC`, and retain the expected `093A:010E` hardware ID while no WinUSB service is bound. Rebinding WinUSB restored operation, and the GUI now distinguishes detectable physical hardware from a usable WinUSB interface. Public onboarding provides **Device Manager** instructions rather than modifying drivers automatically.

The newly observed **counter-reset / old-directory / new-directory** episode above remains a separate and unresolved firmware-memory finding. It reinforces the policy of manual photo-list refresh, prompt raw export, and never issuing undocumented resets.

### Safety invariant

```text
DO NOT SEND 19 54.
```
