mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-15 02:49:54 +00:00
close out the restructure's open items
- boot_info: label the ESP32 reset reasons Zephyr's hwinfo driver maps to 0 (USB-Serial-JTAG/JTAG reset -> DEBUG, power glitch -> BROWNOUT, CPU lockup, eFuse -> HARDWARE); get pwrmgt.bootreason no longer says Unknown after a flash - mqtt: the uplink credentials struct moves to adapters/mqtt/uplink_creds.h, so the adapter no longer includes an app header (layout unchanged: the struct is persisted raw) - gps: one off-levels routine for standby/off and System OFF; the sleep line stays asserted in standby and drops at System OFF, as upstream's sleep_gps/stop_gps/powerOff; System OFF now asserts reset before cutting GPS_EN - doom: sprites solid with a one-pixel black halo, boss drawn 1.5x on the floor; the L2 boss was invisible from the spawn on 1-bit panels
This commit is contained in:
@@ -85,6 +85,7 @@ zephcore/
|
||||
│ ├── gps/ZephyrGPSManager.cpp/h # GNSS state machine, fix validation, public API
|
||||
│ ├── gps/gps_power.cpp, gps_module_cfg.cpp, gps_internal.h # module power; module configuration + diag
|
||||
│ ├── mqtt/ZephyrMQTTPublisher.c/h # MQTT packet publisher (observer / uplink)
|
||||
│ ├── mqtt/uplink_creds.h # WiFi/MQTT/location credentials struct (persisted by app/observer_creds)
|
||||
│ ├── ota/wifi_ota.c/h # WiFi SoftAP + HTTP firmware upload
|
||||
│ ├── rng/ZephyrRNG.cpp/h # Hardware CSPRNG with PRNG fallback
|
||||
│ ├── sensors/ # ZephyrSensorManager (upstream SensorManager) over the I2C env sensors + power monitors
|
||||
@@ -1271,7 +1272,10 @@ cleared register. nRF implements `hwinfo_clear_reset_cause()`;
|
||||
ESP32 does not, so the weak stub returns `-ENOSYS` and nothing is cleared
|
||||
there. That is harmless on ESP32, whose cause comes from `esp_reset_reason()`
|
||||
and does not accumulate across boots, but the capture is what readers should
|
||||
rely on rather than the register.
|
||||
rely on rather than the register. Zephyr's ESP32 hwinfo driver returns 0 for
|
||||
the reasons it has no case for; `boot_info` maps those itself (USB-Serial-JTAG
|
||||
and JTAG resets, i.e. every esptool flash, to `DEBUG`; power glitch to
|
||||
`BROWNOUT`; CPU lockup; eFuse error to `HARDWARE`).
|
||||
|
||||
Everything below is software: bounded stall detection in the layer that owns the
|
||||
state machine. Each entry names what it recovers, because several are
|
||||
|
||||
+4
-4
@@ -185,7 +185,7 @@ flowchart TB
|
||||
| V2 | `helpers/CommonCLI.cpp` includes the GPS manager, `ZephyrBoard`, WiFi OTA and UI headers (it serves `gps sync`, `start dfu`, `start ota`, display and input rotation). Sensors and GPS settings go through upstream's `SensorManager` since 2026-09-25. | Accepted: the remaining includes serve ZephCore-only commands. |
|
||||
| V3 | `helpers/boot_prefs.h` includes the GPS manager (applies GPS prefs at boot, as upstream's `applyGpsPrefs()`). | Accepted. |
|
||||
| V4 | The joystick UI calls `CompanionMesh` directly. | **Accepted by decision**: it runs on the main thread, so the calls are safe, and upstream's UITask reads `the_mesh` the same way. One access point (`JoystickUITask::getMesh()`). |
|
||||
| V5 | The MQTT adapter includes `app/observer_creds.h`. | Open, small. |
|
||||
| V5 | The MQTT adapter included `app/observer_creds.h`. | **Resolved 2026-09-26**: the credentials struct lives in `adapters/mqtt/uplink_creds.h`; the roles keep its persistence. |
|
||||
|
||||
### 5.4 Component inventory
|
||||
|
||||
@@ -358,6 +358,6 @@ upstream alignment (§9).
|
||||
|
||||
1. `CompanionMesh.cpp` (~3.5k lines) and `CommonCLI.cpp` (~2k) remain the largest files, though now in upstream's
|
||||
shape (one handler per opcode; upstream command layout).
|
||||
2. V5 (§5.3).
|
||||
3. Merging the GPS standby and shutdown pin sequences needs current measurements first.
|
||||
4. ESP32 reset cause is not reported (`get pwrmgt.bootreason` shows `Unknown`).
|
||||
|
||||
None beyond that: the items left open at the end of the restructure (V5, the GPS off sequences, the ESP32 reset
|
||||
cause) were closed on 2026-09-26.
|
||||
|
||||
@@ -151,6 +151,58 @@ static const struct gpio_dt_spec gps_resetb_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps
|
||||
static bool gps_gpio_configured = false;
|
||||
#endif
|
||||
|
||||
/* The off levels, shared by gps_power_control(false) and System OFF: reset
|
||||
* and resetb asserted (the module held in reset while its supply is gated,
|
||||
* so it starts cleanly on the next power-on), the enable and rtcint
|
||||
* de-asserted, VRTC de-asserted unless keep_vrtc (warm standby).
|
||||
*
|
||||
* The sleep line is the one difference: runtime standby/off leaves it
|
||||
* asserted, System OFF drops it -- as upstream, whose T1000-E sleep_gps() and
|
||||
* stop_gps() drive GPS_SLEEP_INT HIGH and only powerOff() drives it LOW.
|
||||
*
|
||||
* gpio_pin_configure_dt() on every line: a boot with the GPS off never ran
|
||||
* the power-on path, so the pins may not be outputs yet. */
|
||||
static void gps_drive_off_levels(bool keep_vrtc, bool system_off)
|
||||
{
|
||||
#if HAS_GPS_RESET
|
||||
if (gpio_is_ready_dt(&gps_reset_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_reset_gpio, GPIO_OUTPUT_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_VRTC
|
||||
if (!keep_vrtc && gpio_is_ready_dt(&gps_vrtc_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#else
|
||||
ARG_UNUSED(keep_vrtc);
|
||||
#endif
|
||||
#if HAS_GPS_POWER_CONTROL
|
||||
/* INACTIVE, not LOW: physical LOW leaves an active-low enable asserted. */
|
||||
if (gpio_is_ready_dt(&gps_enable_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
gps_gpio_configured = true;
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_SLEEP
|
||||
if (system_off && gpio_is_ready_dt(&gps_sleep_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#else
|
||||
ARG_UNUSED(system_off);
|
||||
#endif
|
||||
#if HAS_GPS_RTCINT
|
||||
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RESETB
|
||||
/* Declared GPIO_ACTIVE_LOW, so ACTIVE drives it LOW. */
|
||||
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* GPS power control with warm standby support.
|
||||
* @param on true = power on, false = power off
|
||||
* @param keep_vrtc When powering off: true = keep VRTC alive (warm standby,
|
||||
@@ -263,62 +315,7 @@ void gps_power_control(bool on, bool keep_vrtc)
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
/* Power off sequence */
|
||||
#if HAS_GPS_RESET
|
||||
/* Hold GPS in reset during power-off — matches Arduino sleep_gps()/stop_gps().
|
||||
* Ensures chip sees RESET asserted when GPS_EN is asserted on next
|
||||
* power-on, preventing uncontrolled startup before the reset pulse.
|
||||
* Configure-on-first-use (mirrors the GPS_EN pin below): on a
|
||||
* boot-with-GPS-off the power-on path never ran, so the pin isn't an
|
||||
* output yet — gpio_pin_set_dt() alone would leave it floating instead
|
||||
* of asserting reset. GPIO_OUTPUT_ACTIVE drives the active (asserted)
|
||||
* level directly. */
|
||||
if (gpio_is_ready_dt(&gps_reset_gpio)) {
|
||||
if (!gps_gpio_configured) {
|
||||
gpio_pin_configure_dt(&gps_reset_gpio, GPIO_OUTPUT_ACTIVE);
|
||||
} else {
|
||||
gpio_pin_set_dt(&gps_reset_gpio, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_VRTC
|
||||
if (!keep_vrtc) {
|
||||
/* Full power-off: VRTC off too (cold start on next wake) */
|
||||
if (gpio_is_ready_dt(&gps_vrtc_gpio)) {
|
||||
if (!gps_gpio_configured) {
|
||||
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
} else {
|
||||
gpio_pin_set_dt(&gps_vrtc_gpio, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* else: warm standby — VRTC stays asserted, preserving
|
||||
* ephemeris/almanac/RTC for fast re-acquisition (~1-2 µA) */
|
||||
#endif
|
||||
if (gpio_is_ready_dt(&gps_enable_gpio)) {
|
||||
if (!gps_gpio_configured) {
|
||||
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
gps_gpio_configured = true;
|
||||
} else {
|
||||
gpio_pin_set_dt(&gps_enable_gpio, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#if HAS_GPS_RESETB
|
||||
/* Assert RESETB when GPS is off (Arduino sleep_gps/stop_gps).
|
||||
* The alias is declared GPIO_ACTIVE_LOW, so this drives it LOW. */
|
||||
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_RTCINT
|
||||
/* GPS_RTC_INT stays de-asserted during sleep/off (same as normal operation) */
|
||||
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#endif
|
||||
gps_drive_off_levels(keep_vrtc, false);
|
||||
|
||||
#if HAS_GPS_VRTC
|
||||
LOG_INF("GPS power OFF (%s)", keep_vrtc ?
|
||||
@@ -332,12 +329,9 @@ void gps_power_control(bool on, bool keep_vrtc)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Put every GPS control line this board declares into its System OFF state:
|
||||
* the power enable, and where present VRTC, sleep and rtcint, de-asserted;
|
||||
* reset and resetb asserted, holding the module in reset while its supply is
|
||||
* gated -- the same levels gps_power_control(false) leaves them at.
|
||||
* Uses gpio_pin_configure_dt() so pins are properly set even if
|
||||
* gps_power_control() was never called (GPIO not yet configured). */
|
||||
/* Put every GPS control line this board declares into its System OFF state
|
||||
* (see gps_drive_off_levels()), the regulator rail off and VRTC off too.
|
||||
* Configures the pins, so it works even if gps_power_control() never ran. */
|
||||
void gps_power_off_for_shutdown(void)
|
||||
{
|
||||
#if HAS_GPS_POWER_REGULATOR
|
||||
@@ -346,40 +340,7 @@ void gps_power_off_for_shutdown(void)
|
||||
gps_reg_enabled = false;
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_POWER_CONTROL
|
||||
/* INACTIVE: physical LOW leaves an active-low enable asserted, i.e. the
|
||||
* module still powered across System OFF. */
|
||||
if (gpio_is_ready_dt(&gps_enable_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_VRTC
|
||||
if (gpio_is_ready_dt(&gps_vrtc_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RESET
|
||||
/* ACTIVE, matching the power-off path: reset stays asserted across
|
||||
* System OFF, as Arduino stop_gps() leaves it. */
|
||||
if (gpio_is_ready_dt(&gps_reset_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_reset_gpio, GPIO_OUTPUT_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_SLEEP
|
||||
if (gpio_is_ready_dt(&gps_sleep_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RTCINT
|
||||
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RESETB
|
||||
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
gps_drive_off_levels(false, true);
|
||||
}
|
||||
|
||||
#if HAS_GNSS
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "ZephyrMQTTPublisher.h"
|
||||
#include "ZephyrWiFiStation.h" /* g_wifi_events, WIFI_READY_BIT, zc_wifi_station_* */
|
||||
#include "observer_creds.h"
|
||||
#include "uplink_creds.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/net/mqtt.h>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Uplink credentials: WiFi, MQTT broker and location for the observer and the
|
||||
* repeater uplink. Read by the WiFi station and the MQTT publisher; loaded,
|
||||
* saved and edited by the roles (app/observer_creds.h).
|
||||
*
|
||||
* The struct is persisted as raw bytes (/lfs/repeater/obs_creds): keep the
|
||||
* field order and sizes.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ObserverCreds {
|
||||
char wifi_ssid[64]; /* WiFi network name */
|
||||
char wifi_psk[64]; /* WiFi password (empty = open network) */
|
||||
char mqtt_host[128]; /* MQTT broker hostname, e.g. "abydos.hu" */
|
||||
uint16_t mqtt_port; /* MQTT broker port, e.g. 8883 */
|
||||
uint8_t mqtt_tls; /* 1 = TLS (no cert verify), 0 = plaintext */
|
||||
char mqtt_user[64]; /* MQTT username */
|
||||
char mqtt_password[64]; /* MQTT password */
|
||||
char mqtt_iata[8]; /* IATA location code, e.g. "BUD", "BTS", "VIE" */
|
||||
uint8_t _reserved[1]; /* alignment / future use */
|
||||
int32_t lat_e6; /* latitude × 1 000 000 (e.g. 47497900 = 47.497900°N) */
|
||||
int32_t lon_e6; /* longitude × 1 000 000 (e.g. 19040200 = 19.040200°E) */
|
||||
};
|
||||
|
||||
/* Apply sensible defaults to a freshly-zeroed creds struct. */
|
||||
static inline void observer_creds_init(struct ObserverCreds *creds)
|
||||
{
|
||||
creds->mqtt_port = 8883;
|
||||
creds->mqtt_tls = 1;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -11,24 +11,12 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "../adapters/mqtt/uplink_creds.h" /* struct ObserverCreds, observer_creds_init() */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ObserverCreds {
|
||||
char wifi_ssid[64]; /* WiFi network name */
|
||||
char wifi_psk[64]; /* WiFi password (empty = open network) */
|
||||
char mqtt_host[128]; /* MQTT broker hostname, e.g. "abydos.hu" */
|
||||
uint16_t mqtt_port; /* MQTT broker port, e.g. 8883 */
|
||||
uint8_t mqtt_tls; /* 1 = TLS (no cert verify), 0 = plaintext */
|
||||
char mqtt_user[64]; /* MQTT username */
|
||||
char mqtt_password[64]; /* MQTT password */
|
||||
char mqtt_iata[8]; /* IATA location code, e.g. "BUD", "BTS", "VIE" */
|
||||
uint8_t _reserved[1]; /* alignment / future use */
|
||||
int32_t lat_e6; /* latitude × 1 000 000 (e.g. 47497900 = 47.497900°N) */
|
||||
int32_t lon_e6; /* longitude × 1 000 000 (e.g. 19040200 = 19.040200°E) */
|
||||
};
|
||||
|
||||
/* Load creds from /lfs/repeater/obs_creds.
|
||||
* Returns true on success; on failure fills struct with safe zero defaults. */
|
||||
bool observer_creds_load(struct ObserverCreds *creds, const char *base_path);
|
||||
@@ -36,13 +24,6 @@ bool observer_creds_load(struct ObserverCreds *creds, const char *base_path);
|
||||
/* Save creds to /lfs/repeater/obs_creds. Returns true on success. */
|
||||
bool observer_creds_save(const struct ObserverCreds *creds, const char *base_path);
|
||||
|
||||
/* Apply sensible defaults to a freshly-zeroed creds struct. */
|
||||
static inline void observer_creds_init(struct ObserverCreds *creds)
|
||||
{
|
||||
creds->mqtt_port = 8883;
|
||||
creds->mqtt_tls = 1;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -12,11 +12,38 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(CONFIG_SOC_FAMILY_ESPRESSIF_ESP32)
|
||||
#include <esp_system.h>
|
||||
#endif
|
||||
|
||||
LOG_MODULE_REGISTER(zephcore_boot, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
|
||||
static uint32_t s_reset_cause;
|
||||
static bool s_reset_cause_valid;
|
||||
|
||||
#if defined(CONFIG_SOC_FAMILY_ESPRESSIF_ESP32)
|
||||
/* Zephyr's ESP32 hwinfo driver reports 0 for the reasons it has no case for,
|
||||
* the commonest being ESP_RST_USB: esptool's reset over USB-Serial-JTAG, i.e.
|
||||
* every flash. Map those onto the nearest RESET_* bit so they are labelled
|
||||
* instead of "Unknown". */
|
||||
static uint32_t esp_unmapped_reset_cause(void)
|
||||
{
|
||||
switch (esp_reset_reason()) {
|
||||
case ESP_RST_USB:
|
||||
case ESP_RST_JTAG:
|
||||
return RESET_DEBUG;
|
||||
case ESP_RST_PWR_GLITCH:
|
||||
return RESET_BROWNOUT;
|
||||
case ESP_RST_CPU_LOCKUP:
|
||||
return RESET_CPU_LOCKUP;
|
||||
case ESP_RST_EFUSE:
|
||||
return RESET_HARDWARE;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* POST_KERNEL: before main() on every role. */
|
||||
static int boot_info_init(void)
|
||||
{
|
||||
@@ -27,6 +54,12 @@ static int boot_info_init(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SOC_FAMILY_ESPRESSIF_ESP32)
|
||||
if (cause == 0) {
|
||||
cause = esp_unmapped_reset_cause();
|
||||
}
|
||||
#endif
|
||||
|
||||
s_reset_cause = cause;
|
||||
s_reset_cause_valid = true;
|
||||
|
||||
|
||||
@@ -606,8 +606,8 @@ static const uint16_t spr_portal_b[16] = {
|
||||
|
||||
/*
|
||||
* Fireball — a solid ball confined to the middle 8 rows of the 16×16 cell, so
|
||||
* it renders at roughly half an enemy's apparent size without the shared
|
||||
* sprite blitter needing a scale argument. Solid, unlike the outlined portal
|
||||
* it renders at eye level, at roughly half an enemy's apparent size, through
|
||||
* the shared blitter at scale 100. Solid, unlike the outlined portal
|
||||
* diamond, so the two never read as the same thing.
|
||||
*/
|
||||
static const uint16_t spr_fireball[16] = {
|
||||
@@ -663,14 +663,41 @@ static const uint16_t *get_sprite_16(const struct doom_enemy *e)
|
||||
|
||||
/* ========== RAYCASTER ========== */
|
||||
|
||||
/* The sprite texel under screen pixel (sc, sr) of a sprite drawn at
|
||||
* [x0, x0 + w) x [y0, y0 + h). */
|
||||
static inline bool sprite_texel_on(const uint16_t *sprite, int sc, int sr,
|
||||
int x0, int y0, int w, int h)
|
||||
{
|
||||
int tex_x = (sc - x0) * 16 / w;
|
||||
int tex_y = (sr - y0) * 16 / h;
|
||||
|
||||
if (tex_x > 15) tex_x = 15;
|
||||
if (tex_y > 15) tex_y = 15;
|
||||
return (sprite[tex_y] & (0x8000 >> tex_x)) != 0;
|
||||
}
|
||||
|
||||
static inline void clear_px(int x, int y)
|
||||
{
|
||||
render_fb[(y / 8) * SCREEN_W + x] &= (uint8_t)~(1U << (y & 7));
|
||||
}
|
||||
|
||||
/*
|
||||
* Render one 16×16 sprite at world position (wx, wy).
|
||||
* Shared by both the portal and all enemy sprites.
|
||||
* Render one 16×16 sprite at world position (wx, wy), scale_pct percent of a
|
||||
* wall's height with its feet on the floor. Shared by the portal, the enemies
|
||||
* and the fireballs.
|
||||
*
|
||||
* Sprites are solid with a one-pixel black halo, not distance-dithered like
|
||||
* the walls. On a 1-bit panel a dithered sprite ORed over the dithered floor
|
||||
* band at the horizon has nothing to contrast against: the L2 boss, which
|
||||
* starts 8-12 tiles away, was a few dots at best and a grey smudge in the
|
||||
* floor's own pattern at 4 tiles. The halo pass clears first, then the fill
|
||||
* sets; both honour the zbuffer, so a nearer wall is never cut into.
|
||||
*/
|
||||
static void render_one_sprite(const uint16_t *sprite,
|
||||
fixed_t wx, fixed_t wy,
|
||||
fixed_t inv_det,
|
||||
struct doom_player *p)
|
||||
struct doom_player *p,
|
||||
int scale_pct)
|
||||
{
|
||||
fixed_t sx = wx - p->x;
|
||||
fixed_t sy = wy - p->y;
|
||||
@@ -686,46 +713,49 @@ static void render_one_sprite(const uint16_t *sprite,
|
||||
fp_to_int(fp_div(fp_mul(transform_x,
|
||||
fp_from_int(SCREEN_W)), transform_y));
|
||||
|
||||
int sprite_h = fp_to_int(fp_abs(fp_div(
|
||||
int base_h = fp_to_int(fp_abs(fp_div(
|
||||
fp_from_int(SCREEN_H), transform_y)));
|
||||
if (sprite_h < 3) return;
|
||||
if (sprite_h > SCREEN_H * 2) sprite_h = SCREEN_H * 2;
|
||||
if (base_h > SCREEN_H * 2) base_h = SCREEN_H * 2;
|
||||
|
||||
int draw_start_y = (SCREEN_H - sprite_h) / 2;
|
||||
int draw_end_y = draw_start_y + sprite_h;
|
||||
int sprite_h = base_h * scale_pct / 100;
|
||||
if (sprite_h < 3) return;
|
||||
|
||||
int draw_end_y = (SCREEN_H + base_h) / 2; /* the floor line */
|
||||
int draw_start_y = draw_end_y - sprite_h;
|
||||
int draw_start_x = sprite_screen_x - sprite_h / 2;
|
||||
int draw_end_x = sprite_screen_x + sprite_h / 2;
|
||||
int sprite_w = draw_end_x - draw_start_x;
|
||||
|
||||
/* Floor for sprites: at 8+ tiles the wall fade (brightness 1) passes one
|
||||
* dither cell in 16, so the boss, which starts ~11 tiles away in a
|
||||
* corner, drew as a few dots or nothing while its fireballs hit. Walls
|
||||
* keep the full fade; sprites keep a silhouette. */
|
||||
int brightness = dist_to_brightness(transform_y);
|
||||
if (brightness < 7) {
|
||||
brightness = 7;
|
||||
}
|
||||
int x_lo = draw_start_x < 0 ? 0 : draw_start_x;
|
||||
int x_hi = draw_end_x > SCREEN_W ? SCREEN_W : draw_end_x;
|
||||
int y_lo = draw_start_y < 0 ? 0 : draw_start_y;
|
||||
int y_hi = draw_end_y > SCREEN_H ? SCREEN_H : draw_end_y;
|
||||
|
||||
for (int sc = draw_start_x; sc < draw_end_x; sc++) {
|
||||
if (sc < 0 || sc >= SCREEN_W) continue;
|
||||
if (transform_y >= zbuffer[sc]) continue;
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
for (int sc = x_lo; sc < x_hi; sc++) {
|
||||
if (transform_y >= zbuffer[sc]) continue;
|
||||
|
||||
int tex_x = (sc - draw_start_x) * 16 / sprite_h;
|
||||
if (tex_x < 0) tex_x = 0;
|
||||
if (tex_x > 15) tex_x = 15;
|
||||
|
||||
for (int sr = draw_start_y; sr < draw_end_y; sr++) {
|
||||
if (sr < 0 || sr >= SCREEN_H) continue;
|
||||
|
||||
int tex_y = (sr - draw_start_y) * 16 / sprite_h;
|
||||
if (tex_y < 0) tex_y = 0;
|
||||
if (tex_y > 15) tex_y = 15;
|
||||
|
||||
if (sprite[tex_y] & (0x8000 >> tex_x)) {
|
||||
uint8_t threshold =
|
||||
bayer4x4[sr & 3][sc & 3];
|
||||
if (brightness > threshold) {
|
||||
for (int sr = y_lo; sr < y_hi; sr++) {
|
||||
if (!sprite_texel_on(sprite, sc, sr,
|
||||
draw_start_x, draw_start_y,
|
||||
sprite_w, sprite_h)) {
|
||||
continue;
|
||||
}
|
||||
if (pass == 1) {
|
||||
render_fb[(sr / 8) * SCREEN_W + sc] |=
|
||||
(1U << (sr & 7));
|
||||
continue;
|
||||
}
|
||||
for (int hx = sc - 1; hx <= sc + 1; hx++) {
|
||||
if (hx < 0 || hx >= SCREEN_W ||
|
||||
transform_y >= zbuffer[hx]) {
|
||||
continue;
|
||||
}
|
||||
for (int hy = sr - 1; hy <= sr + 1; hy++) {
|
||||
if (hy >= 0 && hy < SCREEN_H) {
|
||||
clear_px(hx, hy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,7 +778,7 @@ static void render_sprites(void)
|
||||
render_one_sprite(pspr,
|
||||
fp_from_int(game.portal_map_x) + FP_HALF,
|
||||
fp_from_int(game.portal_map_y) + FP_HALF,
|
||||
inv_det, p);
|
||||
inv_det, p, 100);
|
||||
}
|
||||
|
||||
/* Collect visible enemies and sort farthest-first */
|
||||
@@ -781,7 +811,8 @@ static void render_sprites(void)
|
||||
|
||||
for (int i = 0; i < visible; i++) {
|
||||
struct doom_enemy *e = &game.enemies[order[i].idx];
|
||||
render_one_sprite(get_sprite_16(e), e->x, e->y, inv_det, p);
|
||||
render_one_sprite(get_sprite_16(e), e->x, e->y, inv_det, p,
|
||||
e->type == ENEMY_BOSS ? 150 : 100);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -793,7 +824,7 @@ static void render_sprites(void)
|
||||
for (int i = 0; i < MAX_FIREBALLS; i++) {
|
||||
struct doom_fireball *f = &game.fireballs[i];
|
||||
if (!f->active) continue;
|
||||
render_one_sprite(spr_fireball, f->x, f->y, inv_det, p);
|
||||
render_one_sprite(spr_fireball, f->x, f->y, inv_det, p, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user