Files
PS5_RetroArch/src/video_ps5.cpp
T
Mihawk 8969e2eb38 Write and register the console's pad input driver, and name the two faults blocking it
src/input_ps5.cpp is a complete input_driver_t for this console: the DualSense is
read with scePadInit/scePadOpen/scePadRead and the 120-byte sample layout
../ProsperoLight verified on hardware, the pad's button words are mapped onto
RetroArch's numbering (CIRCLE is RetroArch's A, so CIRCLE confirms a menu entry),
sticks and triggers are reported as axes, and the table is registered in
input_drivers[] before input_null. tests/test_frontend.py pins the registration
from the frontend object's relocations and the button pairing by reading the map
out of the object the title links.

Two faults were found in front of it, both upstream of the driver.

RetroArch's built-in test input driver is on by default (HAVE_TEST_DRIVERS=yes in
qb/config.params.sh), and while it is on video_driver_init_input returns before
initialising any real input driver whenever the configured driver is not "test".
No input driver was ever initialised, so current_data stayed NULL and every button
read answered 0. --disable-test_drivers is now in tools/retroarch-sources.sh.

The title's -c never reached RetroArch. Content loading rebuilds argv from the
frontend's environment and kept only ["retroarch", "--menu"], so the config file
was never read at all - measured as
  probe config_parse_file: path="(null)"
  probe config_load: after parse input="null" video="ext"
This title has therefore been running on compiled defaults since it first built,
with --verbose dropped as well. A guarded one-line fix restores the title's own
config path.

That fix is parked rather than committed into patches/series: with the config
actually read, the title dies on launch with SIGSEGV, rip=0 - a call through a
null function pointer - both with input_driver="ps5" and with "null", and before
ps5_input_init is entered. Markers through drivers_init and
video_driver_init_internal show the entire video path completing, so the fault is
after driver initialisation. The change and its reasoning are in
parked/config-path.patch.py, and config/retroarch.cfg keeps input_driver="null" so
the title keeps running and showing its menu.

The pad does not work yet and the picture is still one menu frame. What is solid:
the driver exists, is registered, is unit-tested, and the two faults that were
silently blocking every input path are named with their measurements.

gates: bash tools/verify.sh -> PASS (format unit build integration evidence),
13 host tests. reproduce: bash tools/run-title.sh --watch 12.
2026-09-18 18:45:13 -04:00

612 lines
26 KiB
C++

/*
* PS5 RetroArch - the video driver, and the poke interface RGUI needs.
*
* Copyright (C) 2026 Mihawk
* SPDX-License-Identifier: GPL-3.0-or-later
*
* RetroArch asks a video driver for very little. From the interface report in
* docs/REFERENCE.md, the mandatory surface is six members: `init`,
* `suppress_screensaver`, `alive`, `frame`, `ident` and `poke_interface`. No
* context driver is involved at all, and RGUI needs no GPU display context: it
* rasterises the menu into its own framebuffer and hands that one texture to the
* driver through `poke->set_texture_frame`, which this file implements.
*
* That is why this is the first menu to target: everything below it is the
* display layer this project has already run on the console.
*
* The structs are filled POSITIONALLY, so field order is the interface. The
* conditional members are honoured: `overlay_interface` exists only under
* HAVE_OVERLAY and sits before `poke_interface`, and `gfx_widgets_enabled` exists
* only under HAVE_GFX_WIDGETS. A build with widgets also needs a matching display
* context, which RGUI does not, so widgets are a later step.
*/
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <new>
#include "display.hpp"
#include "trace.hpp"
/* RetroArch's own headers. `config.h` is generated by its build; until this
* project runs that build, the driver is compiled against the tree with the
* defines the build would set (see tools/build-raylib-free.sh). */
#include <gfx/video_defines.h>
#include <gfx/video_driver.h>
extern "C" int sceKernelUsleep(std::uint32_t microseconds);
/* RetroArch's own: how a driver tells the frontend the size of the display it is
* drawing into. gfx/video_driver.c is C and this is C++. */
extern "C" void video_driver_set_size(unsigned width, unsigned height);
/* This file's own driver table, defined at the bottom. Named here so ps5_init can
* look at what the frontend will see of it. */
extern "C" video_driver_t video_ps5;
namespace
{
using ps5::display::Display;
/* The console's frame, which is what the display registers and what everything
* here is scaled into. */
constexpr unsigned screen_width = 1920;
constexpr unsigned screen_height = 1080;
struct DriverState
{
Display display;
/* The menu's own framebuffer, handed over by set_texture_frame. */
const void *menu_frame = nullptr;
bool have_menu_frame = false;
unsigned menu_width = 0;
unsigned menu_height = 0;
unsigned out_width = 0;
unsigned out_height = 0;
/* The menu framebuffer's format, as the frontend declared it: RGUI sends
* RGB565 (rgb32 = false), a core may send RGB8888. */
bool menu_rgb32 = false;
/* What the driver has actually been handed, reported once so the trace says
* which of the two sources is real instead of leaving it to be inferred. */
bool trace_texture_frame_done = false;
bool seen_core_frame = false;
bool seen_menu_frame = false;
bool trace_sources_done = false;
/* The menu's framebuffer as it was last seen, and how many times it has been
* handed over with different pixels in it. Two different questions live here
* and only the second one is "is the picture frozen": the driver presents a
* frame every vblank whether or not anything changed, and RGUI redraws its
* framebuffer only when something has. */
std::uint32_t menu_sample[16] = {0};
bool menu_sample_valid = false;
std::uint64_t menu_commits = 0;
std::uint64_t menu_changes = 0;
};
DriverState *state_of(void *data) noexcept
{
return static_cast<DriverState *>(data);
}
/* Draws `source` into one whole frame of the console's display, scaled to fill it,
* through the display layer's tiled addressing.
*
* Nearest-neighbour on purpose: the sources are a 4x4 dummy frame and RGUI's
* 320x240 framebuffer, and smooth scaling would cost pixels of work per output
* pixel for an image that is going to be blocky either way. The source is 4 bytes
* per pixel when the frontend says rgb32 and 2 when it does not, so the two
* formats are read where they are - a 16-bit frame read as 32-bit is a picture
* made of the wrong halves of the right pixels, which looks like corruption rather
* than like a format mistake. */
void blit_whole_frame(ps5::display::Surface surface, const void *source, unsigned source_width,
unsigned source_height, unsigned source_pitch, bool source_rgb32) noexcept
{
if (source == nullptr || source_width == 0 || source_height == 0 || source_pitch == 0)
return;
const unsigned source_bytes = source_rgb32 ? 4u : 2u;
const auto *bytes = static_cast<const std::uint8_t *>(source);
for (unsigned y = 0; y < surface.height; ++y)
{
const unsigned source_y = (y * source_height) / surface.height;
const auto *row = bytes + static_cast<std::size_t>(source_y) * source_pitch;
if (source_rgb32)
{
for (unsigned x = 0; x < surface.width; ++x)
{
const auto *pixel =
row +
static_cast<std::size_t>((x * source_width) / surface.width) * source_bytes;
std::uint32_t colour = 0;
std::memcpy(&colour, pixel, sizeof(colour));
Display::write(surface, x, y, colour);
}
continue;
}
/* RGB565. Expanded to 8 bits per channel, then to the console's
* 0xAARRGGBB with full alpha. Many output pixels in a row share one source
* pixel - about six to one for RGUI's 320 columns - so the expander runs
* when the source pixel changes, not once per output pixel. */
unsigned previous_source_x = ~0u;
std::uint32_t colour = 0;
for (unsigned x = 0; x < surface.width; ++x)
{
const unsigned source_x = (x * source_width) / surface.width;
if (source_x != previous_source_x)
{
previous_source_x = source_x;
const auto *pixel = row + static_cast<std::size_t>(source_x) * source_bytes;
std::uint16_t rgb565 = 0;
std::memcpy(&rgb565, pixel, sizeof(rgb565));
const std::uint32_t r = (rgb565 >> 11) & 0x1fu;
const std::uint32_t g = (rgb565 >> 5) & 0x3fu;
const std::uint32_t b = rgb565 & 0x1fu;
colour = 0xff000000u | ((r << 3 | r >> 2) << 16) | ((g << 2 | g >> 4) << 8) |
(b << 3 | b >> 2);
}
Display::write(surface, x, y, colour);
}
}
}
/* The three vertical bands, in the console's own primary colours.
*
* They are the instrument, not the picture. They were the first thing this
* project ever saw come out of its own display path, and they are what tells a
* run apart from another: while they are on the screen the driver is presenting
* and whatever else is supposed to be there is not, which is a distinction a
* blank screen cannot make. They are replaced on screen the moment a real source
* exists, and this whole function goes when the menu does. */
void paint_probe_bands(ps5::display::Surface surface) noexcept
{
const unsigned band = surface.width / 3 ? surface.width / 3 : 1;
for (unsigned y = 0; y < surface.height; ++y)
for (unsigned x = 0; x < surface.width; ++x)
{
const unsigned which = (x / band) % 3;
const std::uint32_t colour = which == 0 ? 0xffff0000u
: which == 1 ? 0xff00ff00u
: 0xff0000ffu;
Display::write(surface, x, y, colour);
}
}
/* --- video_driver_t ------------------------------------------------------- */
void *ps5_init(const video_info_t *video, input_driver_t **input, void **input_data) noexcept
{
ps5::debug::mark_init("ps5_init entered", video != nullptr,
video ? static_cast<int>(video->width) : -1,
video ? static_cast<int>(video->height) : -1);
auto *state = new (std::nothrow) DriverState();
if (state == nullptr)
{
ps5::debug::mark("ps5_init: allocation failed");
return nullptr;
}
state->out_width = screen_width;
state->out_height = screen_height;
/* The console's frame size is fixed by the registration in the display layer;
* a request for another size is refused rather than silently scaled, so the
* mismatch is visible here instead of on the screen. */
if (!state->display.open(screen_width, screen_height))
{
ps5::debug::mark("ps5_init: display.open refused the frame size");
delete state;
return nullptr;
}
/* Tell the frontend how big this display is, because it cannot find out any
* other way and it hands the answer to the menu. runloop.c calls the menu's
* render with video_st->width and video_st->height, and both were zero until
* this call existed: rgui_render was entered every frame with 0,0 and returned
* at its own guard after its first instruction, so the menu - which was alive,
* had its fonts and had a framebuffer - contributed no pixels at all. An
* earlier round had this call and lost the title a quarter of a second after
* EXEC; that build also had the Vulkan driver switched on, and every run in
* that state failed before ps5_init was reached, so the two are not the same
* experiment. It is retried here, and the trace says whether it is reached. */
video_driver_set_size(screen_width, screen_height);
ps5::debug::mark("ps5_init: told the frontend the display is 1920x1080");
/* What the frontend can see of this driver's table, from inside this driver's
* own init. A driver whose poke_interface member is NULL there is a table
* laying its members out in an order the frontend does not agree with, and
* that is a fault no other run of this project could see: the driver still
* opens the display and presents frames, and only the menu's hand-over dies -
* silently, because the frontend only calls poke_interface when it is not
* NULL. */
{
char line[224];
std::snprintf(line, sizeof(line),
"ps5_init: own table ident=%s poke_interface=%p set_viewport=%p "
"wrap_type_to_enum=%p",
video_ps5.ident, reinterpret_cast<const void *>(video_ps5.poke_interface),
reinterpret_cast<const void *>(video_ps5.set_viewport),
reinterpret_cast<const void *>(video_ps5.wrap_type_to_enum));
ps5::debug::mark(line);
}
/* The driver owns no input: leaving these untouched hands input back to the
* frontend's own driver, which is what this step is meant to test. */
(void)input;
(void)input_data;
return state;
}
bool ps5_frame(void *data, const void *frame, unsigned width, unsigned height,
std::uint64_t frame_count, unsigned pitch, const char *msg,
video_frame_info_t *video_info) noexcept
{
(void)frame_count;
(void)msg;
(void)video_info;
DriverState *state = state_of(data);
if (state == nullptr)
return false;
const ps5::display::Surface surface = state->display.back_surface();
/* What to show, in order of preference: the menu's framebuffer if RGUI has
* handed one over, else the core's frame, else the bands. The menu wins because
* at this step the menu is the thing being proven.
*
* The core's frame is a 4x4 dummy whenever no game is loaded - video_driver.c
* sets frame_cache_width/height to 4 and pitch to 8 for exactly that case - so
* a driver that shows the "core frame" while the menu is up is showing four
* pixels stretched across the screen. Which of the two this run is being handed
* is a fact about the frontend and is measured, not assumed; see the trace
* line at the end of this function. */
const void *source = nullptr;
unsigned source_width = 0;
unsigned source_height = 0;
unsigned source_pitch = 0;
bool source_rgb32 = true;
const char *source_name = "none";
if (state->have_menu_frame && state->menu_frame != nullptr)
{
source = state->menu_frame;
source_width = state->menu_width;
source_height = state->menu_height;
source_pitch = state->menu_width * (state->menu_rgb32 ? 4u : 2u);
source_rgb32 = state->menu_rgb32;
source_name = "menu";
state->seen_menu_frame = true;
}
else if (frame != nullptr && width != 0 && height != 0 && pitch != 0)
{
source = frame;
source_width = width;
source_height = height;
source_pitch = pitch;
/* A frame from a core is in the pixel format video_info_t asked for,
* which this driver asks for as RGB8888. */
source_rgb32 = true;
source_name = "core";
state->seen_core_frame = true;
}
/* The bands go down first and the real source over the top of them. They cost
* one frame of paint and they are the difference between "the menu has no
* pixels yet" and "the display has no pixels at all" - the second of which
* this project has already spent several console runs unable to tell from the
* first. Once the menu has ever been drawn they are never painted again, so
* the screen is the menu's and not the instrument's. */
if (state->seen_menu_frame)
{
blit_whole_frame(surface, source, source_width, source_height, source_pitch, source_rgb32);
}
else if (source != nullptr)
{
/* No menu yet, but the frontend is handing something over - the 4x4 dummy
* frame while no game is loaded. Shown as what it is, in its own corner of
* the screen, over the bands, which keep saying the display is alive. */
paint_probe_bands(surface);
const ps5::display::Surface corner{surface.base, surface.width / 4, surface.height / 4};
blit_whole_frame(corner, source, source_width, source_height, source_pitch, source_rgb32);
}
else
{
paint_probe_bands(surface);
}
const bool presented = state->display.present();
/* The first few frames, unconditionally: what the frontend is handing over,
* what this driver decided to draw from it, and whether the display took it.
* The round that assumed the branch instead of measuring it lost two console
* runs to a readback that never printed. */
if (frame_count <= 3)
{
char line[176];
std::snprintf(line, sizeof(line), "ps5_frame %llu: %s %ux%u pitch=%u present=%d",
static_cast<unsigned long long>(frame_count), source_name, source_width,
source_height, source_pitch, presented ? 1 : 0);
ps5::debug::mark(line);
}
/* And once, later, the two facts that decide everything after this point: has
* the menu ever handed over a framebuffer, and did it take longer than the
* first frames to do it. */
if (!state->trace_sources_done && frame_count >= 60)
{
state->trace_sources_done = true;
char line[176];
std::snprintf(line, sizeof(line), "ps5_frame %llu: sources so far: menu=%s core=%s",
static_cast<unsigned long long>(frame_count),
state->seen_menu_frame ? "yes" : "no", state->seen_core_frame ? "yes" : "no");
ps5::debug::mark(line);
}
/* Every six hundred frames, what the menu has drawn and how often its picture
* actually changed. One line per ten seconds says whether this is a live
* frontend or a still image: a menu_changes count that stops at 1 while
* menu_commits climbs is a frozen picture, and one that climbs is not. */
if (frame_count != 0 && frame_count % 600 == 0)
{
char line[176];
std::snprintf(
line, sizeof(line), "ps5_frame %llu: menu commits=%llu changes=%llu presented=%s",
static_cast<unsigned long long>(frame_count),
static_cast<unsigned long long>(state->menu_commits),
static_cast<unsigned long long>(state->menu_changes), presented ? "yes" : "no");
ps5::debug::mark(line);
}
return presented;
}
bool ps5_alive(void *data) noexcept
{
// The frontend ends its runloop the first time this answers false, and it
// calls it only with the pointer init returned, so a null here means the
// frontend lost the state rather than that the display closed.
DriverState *state = state_of(data);
const bool open = state != nullptr && state->display.is_open();
if (!open)
ps5::debug::mark(state == nullptr ? "ps5_alive: data was NULL"
: "ps5_alive: the display is closed");
return open;
}
bool ps5_suppress_screensaver(void *data, bool enable) noexcept
{
/* The console's shell owns the screensaver and there is nothing here to
* suppress, so the answer is always no. The member must exist because the
* frontend calls it with no NULL check; see docs/REFERENCE.md. */
(void)data;
(void)enable;
return false;
}
void ps5_set_viewport(void *data, unsigned width, unsigned height, bool force_full,
bool allow_rotate) noexcept
{
(void)force_full;
(void)allow_rotate;
(void)data;
/* A console's frame is fixed, so a request for another size is reported back as
* the size that exists rather than silently accepted. */
video_driver_set_size(width ? width : 1920, height ? height : 1080);
}
/* --- video_poke_interface_t ----------------------------------------------- */
/* A cheap fingerprint of the menu's framebuffer: sixteen pixels read across it.
*
* Reading all 2,073,600 of them every frame would cost as much as the blit it is
* meant to be watching, and would be measuring the driver rather than the menu.
* Sixteen samples spread over the frame are enough to tell "the same picture" from
* "a re-rendered one", which is the only distinction that matters here. */
bool sample_menu_frame(const void *frame, unsigned width, unsigned height, bool rgb32,
std::uint32_t out[16]) noexcept
{
if (frame == nullptr || width == 0 || height == 0)
return false;
const unsigned bytes = rgb32 ? 4u : 2u;
const auto *base = static_cast<const std::uint8_t *>(frame);
const unsigned stride = width * bytes;
for (unsigned index = 0; index < 16; ++index)
{
const unsigned x = (index * 7u + 3u) % width;
const unsigned y = (index * 13u + 5u) % height;
const std::uint8_t *at =
base + static_cast<std::size_t>(y) * stride + static_cast<std::size_t>(x) * bytes;
std::uint32_t value = 0;
std::memcpy(&value, at, bytes);
out[index] = value;
}
return true;
}
void ps5_set_texture_frame(void *data, const void *frame, bool rgb32, unsigned width,
unsigned height, float alpha) noexcept
{
(void)alpha;
DriverState *state = state_of(data);
/* rgb32 is kept, not filtered on. RGUI calls this with rgb32 = false - its
* framebuffer is RGB565 - and an earlier version of this function returned
* unless rgb32 was true, so the menu's pixels were dropped on every frame and
* the driver fell back to presenting whatever the core had. The format is
* recorded instead, and ps5_frame converts from it. */
if (state == nullptr)
return;
/* The first hand-over and every change of state are recorded, and the state
* being recorded is "the menu is now the thing to draw": when RGUI commits a
* framebuffer, that is the fact the whole menu milestone rests on, and when it
* stops committing one the trace says at which frame that happened. */
const bool was_available = state->have_menu_frame;
state->menu_frame = frame;
state->menu_width = width;
state->menu_height = height;
state->menu_rgb32 = rgb32;
state->have_menu_frame = frame != nullptr && width != 0 && height != 0;
if (!state->trace_texture_frame_done || was_available != state->have_menu_frame)
{
state->trace_texture_frame_done = true;
char line[176];
std::snprintf(line, sizeof(line),
"ps5_set_texture_frame: rgb32=%d %ux%u frame=%s have=%d (was %d)",
rgb32 ? 1 : 0, width, height, frame ? "present" : "NULL",
state->have_menu_frame ? 1 : 0, was_available ? 1 : 0);
ps5::debug::mark(line);
}
/* Is this the same picture the menu handed over last time? The first commit
* and every change are written down, with the commit number, because that
* number is what tells a frozen picture from a live one from the trace alone:
* the driver presents every vblank either way, so only the menu's own output
* answers the question. */
std::uint32_t sample[16];
if (sample_menu_frame(frame, width, height, rgb32, sample))
{
const bool changed = !state->menu_sample_valid ||
std::memcmp(sample, state->menu_sample, sizeof(sample)) != 0;
++state->menu_commits;
if (changed)
{
++state->menu_changes;
std::memcpy(state->menu_sample, sample, sizeof(sample));
state->menu_sample_valid = true;
char line[176];
std::snprintf(line, sizeof(line),
"menu: framebuffer commit %llu is a new picture (%llu of %llu changed "
"so far)",
static_cast<unsigned long long>(state->menu_commits),
static_cast<unsigned long long>(state->menu_changes),
static_cast<unsigned long long>(state->menu_commits));
ps5::debug::mark(line);
}
}
}
void ps5_set_texture_enable(void *data, bool enable, bool full_screen) noexcept
{
(void)full_screen;
DriverState *state = state_of(data);
if (state == nullptr)
return;
/* RGUI turns the texture off when a core is presenting its own frame. */
const bool was_available = state->have_menu_frame;
state->have_menu_frame = enable && state->have_menu_frame;
if (was_available != state->have_menu_frame)
{
char line[176];
std::snprintf(line, sizeof(line), "ps5_set_texture_enable: enable=%d have=%d",
enable ? 1 : 0, state->have_menu_frame ? 1 : 0);
ps5::debug::mark(line);
}
}
void ps5_viewport_info(void *data, video_viewport_t *vp) noexcept
{
DriverState *state = state_of(data);
if (state == nullptr || vp == nullptr)
return;
vp->x = 0;
vp->y = 0;
vp->width = state->display.width();
vp->height = state->display.height();
vp->full_width = vp->width;
vp->full_height = vp->height;
}
void ps5_get_video_output_size(void *data, unsigned *width, unsigned *height, char *s,
std::size_t len) noexcept
{
(void)s;
(void)len;
DriverState *state = state_of(data);
if (state == nullptr)
return;
if (width != nullptr)
*width = state->display.width();
if (height != nullptr)
*height = state->display.height();
}
void ps5_set_video_mode(void *data, unsigned width, unsigned height, bool fullscreen) noexcept
{
(void)data;
(void)width;
(void)height;
(void)fullscreen;
}
static const video_poke_interface_t ps5_poke = {
nullptr, /* get_flags */
nullptr, /* load_texture */
nullptr, /* unload_texture */
ps5_set_video_mode, /* set_video_mode */
nullptr, /* get_refresh_rate */
nullptr, /* set_filtering */
ps5_get_video_output_size, /* get_video_output_size */
nullptr, /* get_video_output_prev */
nullptr, /* get_video_output_next */
nullptr, /* get_current_framebuffer */
nullptr, /* get_proc_address */
nullptr, /* set_aspect_ratio */
nullptr, /* apply_state_changes */
ps5_set_texture_frame, /* set_texture_frame */
ps5_set_texture_enable, /* set_texture_enable */
nullptr, /* set_osd_msg */
nullptr, /* show_mouse */
nullptr, /* grab_mouse_toggle */
nullptr, /* get_current_shader */
nullptr, /* get_current_software_framebuffer */
nullptr, /* get_hw_render_interface */
nullptr, /* set_hdr_max_nits */
nullptr, /* set_hdr_paper_white_nits */
nullptr, /* set_hdr_contrast */
nullptr, /* set_hdr_expand_gamut */
};
void ps5_get_poke_interface(void *data, const video_poke_interface_t **iface) noexcept
{
(void)data;
/* Recorded because a run was spent on a menu that never reached this driver:
* the frontend's own structures said the poke interface was NULL while RGUI
* was drawing the menu into its framebuffer and trying to hand it over. A
* function that is never called and a function that is called and does nothing
* look identical from the other side. */
ps5::debug::mark("ps5_get_poke_interface entered");
*iface = &ps5_poke;
}
} // namespace
/* The driver table. Positions are the interface; see the field list in
* docs/REFERENCE.md. Conditional members are honoured so the same literal works
* with or without HAVE_OVERLAY and HAVE_GFX_WIDGETS. */
/* C linkage: gfx/video_driver.c is C and names this symbol in its driver table. */
extern "C" video_driver_t video_ps5 = {
ps5_init,
ps5_frame,
nullptr, /* set_nonblock_state */
ps5_alive,
nullptr, /* focus */
ps5_suppress_screensaver,
nullptr, /* has_windowed */
nullptr, /* set_shader */
nullptr, /* free */
"ps5",
ps5_set_viewport,
nullptr, /* set_rotation */
ps5_viewport_info,
nullptr, /* read_viewport */
nullptr, /* read_frame_raw */
#ifdef HAVE_OVERLAY
nullptr, /* overlay_interface */
#endif
ps5_get_poke_interface,
#ifdef HAVE_GFX_WIDGETS
nullptr, /* gfx_widgets_enabled */
#endif
nullptr, /* wrap_type_to_enum: never called anywhere in the tree */
};